BoundaryML/baml · error · DatamodelError
Unknown variable
Error message
Unknown variable {} What it means
During expression-function validation, an assignment's left-hand side identifier is checked against the set of variables declared in the current scope. If the identifier being assigned was never declared (no `let`) or is not otherwise in scope, the compiler pushes this error.
Solutions
- Declare the variable first with `let x = ...` before assigning
- Check spelling of the variable against its declaration or function parameters
- Ensure the declaration is in an enclosing scope, not a sibling block that already closed
- If it was meant to be a parameter, add it to the function signature
Example fix
// before
function foo() {
total = 0 // undeclared
}
// after
function foo() {
let total = 0
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-check assignment LHS is declared
fn assign_ok(lhs: &str, scope: &HashSet<String>) -> bool {
scope.contains(lhs)
} Prevention
- Always declare with `let` before assigning in expression functions
- Enable BAML editor diagnostics to catch undeclared identifiers while typing
- Avoid reusing names across nested blocks
When it happens
Trigger: Writing `x = 5;` inside a BAML expression function or block without first declaring `let x = ...`, when x is not a function parameter or outer-scope variable.
Common situations: Porting code from languages with implicit variable declaration; typos in variable names inside expression functions (e.g. `mesage = msg`); assigning to a variable declared in a sibling branch that is out of scope.
Related errors
- assertion failed
- assign to undeclared variable
- baml.errors.InvalidArgument
- baml.fetch_as: expected header key to be a valid HTTP…
- Cannot generate HIR/bytecode due to validation errors
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/57c7f88029b6bb71.
Report an issue: GitHub.
Appendix: source
Thrown at engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/expr_fns.rs:44
let builtin_classes = [ir::builtin::classes::REQUEST];
HashSet::from_iter(
builtin_functions
.iter()
.chain(builtin_classes.iter())
.map(ToString::to_string),
)
}
/// Validate that the left-hand side of an assignment refers to a declared variable
/// when it is a bare identifier. This is used to enforce that C-style for-loop
/// headers either declare their iterator with `let` in the init statement, or
/// use a variable already declared in the containing scope.
fn validate_assign_lhs_in_scope(ctx: &mut Context<'_>, left: &Expression, scope: &HashSet<String>) {
if let Expression::Identifier(identifier) = left {
if !scope.contains(&identifier.to_string()) {
ctx.push_error(DatamodelError::new_anyhow_error(
anyhow::anyhow!("Unknown variable {}", &identifier.to_string()),
identifier.span().clone(),
));
}
}
}
// An expr_fn is valid if:
// - Its parameters have valid types.
// - Its parameter names are not reserved keywords.
// - Its return type is valid.
// - Its body is a valid function body (series of statements ending in an
// expression). Bodies are valid if they refer only to variables defined
// in the parameter list and in the current scope.
// - It does not share a name with any other expr_fn or LLM function.
pub(super) fn validate_expr_fns(ctx: &mut Context<'_>) {
let mut defined_types = internal_baml_jinja_types::PredefinedTypes::default(
internal_baml_jinja_types::JinjaContext::Prompt,
);View on GitHub (pinned to bd85ce9dee)