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

  1. Declare the variable first with `let x = ...` before assigning
  2. Check spelling of the variable against its declaration or function parameters
  3. Ensure the declaration is in an enclosing scope, not a sibling block that already closed
  4. 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

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


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)