{"record":{"id":"f03090d5d1d99a64","repo":"BoundaryML/baml","slug":"field-access-on-non-class-type","errorCode":null,"errorMessage":"Field access on non-class type","messagePattern":"Field access on non-class type","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/baml-compiler/src/codegen.rs","lineNumber":596,"sourceCode":"            }\n            thir::Statement::Let { name, value, .. } => {\n                self.compile_expression_with_block_behavior(value, true);\n                self.track_local(name);\n            }\n            thir::Statement::Declare { name, .. } => {\n                self.declare_mut(name);\n            }\n            thir::Statement::Assign { left, value, .. } => {\n                match left {\n                    thir::Expr::Var(name, _) => {\n                        self.compile_expression_with_block_behavior(value, true);\n                        self.emit(Instruction::StoreVar(self.locals[name]));\n                    }\n                    thir::Expr::FieldAccess { base, field, meta: _ } => {\n                        // Get class name from type metadata\n                        let class_name = match base.meta().1.as_ref() {\n                            Some(TypeIR::Class { name, .. }) => name,\n                            _ => panic!(\"Field access on non-class type\"),\n                        };\n\n                        // Resolve field index\n                        let Some(resolved_fields) = self.classes.get(class_name) else {\n                            panic!(\"undefined class: {class_name}\");\n                        };\n                        let Some(&field_index) = resolved_fields.get(field) else {\n                            panic!(\"undefined field: {class_name}.{field}\");\n                        };\n\n                        // Generate bytecode: load base, load value, store field\n                        self.compile_expression(base);\n                        self.compile_expression_with_block_behavior(value, true);\n                        self.emit(Instruction::StoreField(field_index));\n                    }\n                    thir::Expr::ArrayAccess {base, index, meta: _} => {\n\n                        self.compile_expression(base);","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/baml-compiler/src/codegen.rs#L578-L614","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// 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;","handlingStrategy":"validation","validationCode":"// 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}","typeGuard":"fn is_class_type(meta: &Option<TypeIR>) -> bool {\\n    matches!(meta, Some(TypeIR::Class { .. }))\\n}","tryCatchPattern":"// 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};","preventionTips":["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."],"tags":["compiler","codegen","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}