BoundaryML/baml · error
Field access on non-class type
Error message
Field access on non-class type
What it means
During bytecode compilation of a FieldAccess expression, the compiler reads the base expression's static type metadata expecting TypeIR::Class. If the metadata is absent or any other type, it panics with 'Field access on non-class type'. This is a compiler-internal invariant: type checking should have rejected field access on non-class values before codegen.
Source
Thrown at engine/baml-compiler/src/codegen.rs:596
}
thir::Statement::Let { name, value, .. } => {
self.compile_expression_with_block_behavior(value, true);
self.track_local(name);
}
thir::Statement::Declare { name, .. } => {
self.declare_mut(name);
}
thir::Statement::Assign { left, value, .. } => {
match left {
thir::Expr::Var(name, _) => {
self.compile_expression_with_block_behavior(value, true);
self.emit(Instruction::StoreVar(self.locals[name]));
}
thir::Expr::FieldAccess { base, field, meta: _ } => {
// Get class name from type metadata
let class_name = match base.meta().1.as_ref() {
Some(TypeIR::Class { name, .. }) => name,
_ => panic!("Field access on non-class type"),
};
// Resolve field index
let Some(resolved_fields) = self.classes.get(class_name) else {
panic!("undefined class: {class_name}");
};
let Some(&field_index) = resolved_fields.get(field) else {
panic!("undefined field: {class_name}.{field}");
};
// Generate bytecode: load base, load value, store field
self.compile_expression(base);
self.compile_expression_with_block_behavior(value, true);
self.emit(Instruction::StoreField(field_index));
}
thir::Expr::ArrayAccess {base, index, meta: _} => {
self.compile_expression(base);View on GitHub (pinned to bd85ce9dee)
Solutions
- Fix the BAML source: only access fields on class instances (check the value's declared type).
- Run earlier type-check passes and confirm the base expression's TypeIR is Class; if not, the checker should reject it before codegen.
- If metadata is simply missing (None), fix the lowering/type-annotation pass to attach TypeIR::Class.
- Patch the compiler to replace the panic with a proper spanned compile error diagnostic.
Example fix
// before: BAML accessing a field on a non-class\nlet x = "str";\nlet y = x.field; // panics in compiler\n// after\nlet x = MyClass { field: "v" };\nlet y = x.field; Defensive patterns
Strategy: validation
Validate before calling
// reject in the checker before codegen can panic\nif !matches!(base.meta().1.as_ref(), Some(TypeIR::Class { .. })) {\n return Err(TypeError::new(span, "cannot access field on non-class value"));\n} Type guard
fn is_class_type(meta: &Option<TypeIR>) -> bool {\n matches!(meta, Some(TypeIR::Class { .. }))\n} Try / catch
// compiler-side: convert panic to diagnostic\nlet class_name = match base.meta().1.as_ref() {\n Some(TypeIR::Class { name, .. }) => name,\n other => return Err(compile_error!("field access on non-class type: {:?}", other)),\n}; Prevention
- Run the type checker before codegen and reject field access on non-class bases there.
- Attach complete TypeIR metadata in lowering passes for every expression.
- Cover field-access lowering in compiler tests with primitive bases.
- Replace panics with spanned diagnostics for user-facing errors.
When it happens
Trigger: compile_statement handles thir::Expr::FieldAccess where base.meta().1 is None or not TypeIR::Class — i.e. field access on a non-class value or an expression whose type was never attached by earlier passes.
Common situations: BAML source accessing a field on a primitive/string/map instead of a class instance; a lowering bug dropping type metadata; new expression kinds added to THIR without updating codegen; accessing fields on optional types without handling the option.
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
- undefined class: {class_name}
- undefined field: {class_name}.{field}
- array access should be either map or array.
- undefined function: {name}
- watch codegen error: undefined variable: {variable}
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/f03090d5d1d99a64.
Report an issue: GitHub.