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

  1. Fix the BAML source: only access fields on class instances (check the value's declared type).
  2. Run earlier type-check passes and confirm the base expression's TypeIR is Class; if not, the checker should reject it before codegen.
  3. If metadata is simply missing (None), fix the lowering/type-annotation pass to attach TypeIR::Class.
  4. 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

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


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/f03090d5d1d99a64. Report an issue: GitHub.