diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index 5c4f0a09..c79c96f1 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -1,3 +1,4 @@ +use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; use crate::frontends::pbs::ast::*; use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::contracts::ContractRegistry; @@ -19,6 +20,7 @@ pub struct Lowerer<'a> { type_ids: HashMap, struct_slots: HashMap, contract_registry: ContractRegistry, + diagnostics: Vec, } impl<'a> Lowerer<'a> { @@ -39,10 +41,20 @@ impl<'a> Lowerer<'a> { type_ids: HashMap::new(), struct_slots: HashMap::new(), contract_registry: ContractRegistry::new(), + diagnostics: Vec::new(), } } - pub fn lower_file(mut self, file: &FileNode, module_name: &str) -> Program { + fn error(&mut self, code: &str, message: String, span: crate::common::spans::Span) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some(code.to_string()), + message, + span: Some(span), + }); + } + + pub fn lower_file(mut self, file: &FileNode, module_name: &str) -> Result { // Pre-scan for function declarations to assign IDs for decl in &file.decls { if let Node::FnDecl(n) = decl { @@ -71,7 +83,9 @@ impl<'a> Lowerer<'a> { for decl in &file.decls { match decl { Node::FnDecl(fn_decl) => { - let func = self.lower_function(fn_decl); + let func = self.lower_function(fn_decl).map_err(|_| DiagnosticBundle { + diagnostics: self.diagnostics.clone(), + })?; module.functions.push(func); } _ => {} // Other declarations not handled for now @@ -79,10 +93,10 @@ impl<'a> Lowerer<'a> { } self.program.modules.push(module); - self.program + Ok(self.program) } - fn lower_function(&mut self, n: &FnDeclNode) -> Function { + fn lower_function(&mut self, n: &FnDeclNode) -> Result { let func_id = *self.function_ids.get(&n.name).unwrap(); self.next_block_id = 0; self.local_vars = vec![HashMap::new()]; @@ -113,7 +127,7 @@ impl<'a> Lowerer<'a> { self.current_function = Some(func); self.start_block(); - self.lower_node(&n.body); + self.lower_node(&n.body)?; // Ensure every function ends with a return if not already terminated if let Some(mut block) = self.current_block.take() { @@ -125,10 +139,10 @@ impl<'a> Lowerer<'a> { } } - self.current_function.take().unwrap() + Ok(self.current_function.take().unwrap()) } - fn lower_node(&mut self, node: &Node) { + fn lower_node(&mut self, node: &Node) -> Result<(), ()> { match node { Node::Block(n) => self.lower_block(n), Node::LetStmt(n) => self.lower_let_stmt(n), @@ -137,14 +151,17 @@ impl<'a> Lowerer<'a> { Node::IntLit(n) => { let id = self.program.const_pool.add_int(n.value); self.emit(Instr::PushConst(id)); + Ok(()) } Node::FloatLit(n) => { let id = self.program.const_pool.add_float(n.value); self.emit(Instr::PushConst(id)); + Ok(()) } Node::StringLit(n) => { let id = self.program.const_pool.add_string(n.value.clone()); self.emit(Instr::PushConst(id)); + Ok(()) } Node::Ident(n) => self.lower_ident(n), Node::Call(n) => self.lower_call(n), @@ -155,21 +172,27 @@ impl<'a> Lowerer<'a> { Node::Mutate(n) => self.lower_mutate(n), Node::Borrow(n) => self.lower_borrow(n), Node::Peek(n) => self.lower_peek(n), - _ => {} + _ => { + // For unhandled nodes, we can either ignore or error. + // Given the PR, maybe we should error on things we don't support yet in lowering. + self.error("E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", node), node.span()); + Err(()) + } } } - fn lower_alloc(&mut self, n: &AllocNode) { - let (ty_id, slots) = self.get_type_id_and_slots(&n.ty); + fn lower_alloc(&mut self, n: &AllocNode) -> Result<(), ()> { + let (ty_id, slots) = self.get_type_id_and_slots(&n.ty)?; self.emit(Instr::Alloc { ty: ty_id, slots }); + Ok(()) } - fn get_type_id_and_slots(&mut self, node: &Node) -> (TypeId, u32) { + fn get_type_id_and_slots(&mut self, node: &Node) -> Result<(TypeId, u32), ()> { match node { Node::TypeName(n) => { let slots = self.struct_slots.get(&n.name).cloned().unwrap_or(1); let id = self.get_or_create_type_id(&n.name); - (id, slots) + Ok((id, slots)) } Node::TypeApp(ta) if ta.base == "array" => { let size = if ta.args.len() > 1 { @@ -184,9 +207,12 @@ impl<'a> Lowerer<'a> { let elem_ty = self.lower_type_node(&ta.args[0]); let name = format!("array<{}>[{}]", elem_ty, size); let id = self.get_or_create_type_id(&name); - (id, size) + Ok((id, size)) + } + _ => { + self.error("E_RESOLVE_UNDEFINED", format!("Unknown type in allocation: {:?}", node), node.span()); + Err(()) } - _ => (TypeId(0), 1), } } @@ -201,9 +227,9 @@ impl<'a> Lowerer<'a> { } } - fn lower_peek(&mut self, n: &PeekNode) { + fn lower_peek(&mut self, n: &PeekNode) -> Result<(), ()> { // 1. Evaluate target (gate) - self.lower_node(&n.target); + self.lower_node(&n.target)?; // 2. Preserve gate identity let gate_slot = self.get_next_local_slot(); @@ -220,17 +246,18 @@ impl<'a> Lowerer<'a> { self.emit(Instr::SetLocal(view_slot)); // 5. Body - self.lower_node(&n.body); + self.lower_node(&n.body)?; // 6. End Operation self.emit(Instr::EndPeek); self.local_vars.pop(); + Ok(()) } - fn lower_borrow(&mut self, n: &BorrowNode) { + fn lower_borrow(&mut self, n: &BorrowNode) -> Result<(), ()> { // 1. Evaluate target (gate) - self.lower_node(&n.target); + self.lower_node(&n.target)?; // 2. Preserve gate identity let gate_slot = self.get_next_local_slot(); @@ -247,17 +274,18 @@ impl<'a> Lowerer<'a> { self.emit(Instr::SetLocal(view_slot)); // 5. Body - self.lower_node(&n.body); + self.lower_node(&n.body)?; // 6. End Operation self.emit(Instr::EndBorrow); self.local_vars.pop(); + Ok(()) } - fn lower_mutate(&mut self, n: &MutateNode) { + fn lower_mutate(&mut self, n: &MutateNode) -> Result<(), ()> { // 1. Evaluate target (gate) - self.lower_node(&n.target); + self.lower_node(&n.target)?; // 2. Preserve gate identity let gate_slot = self.get_next_local_slot(); @@ -274,61 +302,104 @@ impl<'a> Lowerer<'a> { self.emit(Instr::SetLocal(view_slot)); // 5. Body - self.lower_node(&n.body); + self.lower_node(&n.body)?; // 6. End Operation self.emit(Instr::EndMutate); self.local_vars.pop(); + Ok(()) } - fn lower_block(&mut self, n: &BlockNode) { + fn lower_block(&mut self, n: &BlockNode) -> Result<(), ()> { self.local_vars.push(HashMap::new()); for stmt in &n.stmts { - self.lower_node(stmt); + self.lower_node(stmt)?; } if let Some(tail) = &n.tail { - self.lower_node(tail); + self.lower_node(tail)?; } self.local_vars.pop(); + Ok(()) } - fn lower_let_stmt(&mut self, n: &LetStmtNode) { - self.lower_node(&n.init); + fn lower_let_stmt(&mut self, n: &LetStmtNode) -> Result<(), ()> { + self.lower_node(&n.init)?; let slot = self.get_next_local_slot(); self.local_vars.last_mut().unwrap().insert(n.name.clone(), slot); self.emit(Instr::SetLocal(slot)); + Ok(()) } - fn lower_return_stmt(&mut self, n: &ReturnStmtNode) { + fn lower_return_stmt(&mut self, n: &ReturnStmtNode) -> Result<(), ()> { if let Some(expr) = &n.expr { - self.lower_node(expr); + self.lower_node(expr)?; } self.terminate(Terminator::Return); + Ok(()) } - fn lower_ident(&mut self, n: &IdentNode) { + fn lower_ident(&mut self, n: &IdentNode) -> Result<(), ()> { if let Some(slot) = self.lookup_local(&n.name) { self.emit(Instr::GetLocal(slot)); + Ok(()) } else { + // Check for special identifiers + match n.name.as_str() { + "true" => { + let id = self.program.const_pool.add_int(1); + self.emit(Instr::PushConst(id)); + return Ok(()); + } + "false" => { + let id = self.program.const_pool.add_int(0); + self.emit(Instr::PushConst(id)); + return Ok(()); + } + "none" => { + // For now, treat none as 0. This should be refined when optional is fully implemented. + let id = self.program.const_pool.add_int(0); + self.emit(Instr::PushConst(id)); + return Ok(()); + } + _ => {} + } + // Check if it's a function (for first-class functions if supported) if let Some(_id) = self.function_ids.get(&n.name) { // Push function reference? Not in v0. + self.error("E_LOWER_UNSUPPORTED", format!("First-class function reference '{}' not supported", n.name), n.span); + Err(()) + } else { + self.error("E_RESOLVE_UNDEFINED", format!("Undefined identifier '{}'", n.name), n.span); + Err(()) } } } - fn lower_call(&mut self, n: &CallNode) { + fn lower_call(&mut self, n: &CallNode) -> Result<(), ()> { for arg in &n.args { - self.lower_node(arg); + self.lower_node(arg)?; } match &*n.callee { Node::Ident(id_node) => { if let Some(func_id) = self.function_ids.get(&id_node.name) { self.emit(Instr::Call(*func_id, n.args.len() as u32)); + Ok(()) } else { - // Unknown function - might be a builtin or diagnostic was missed - self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); + // Check for special built-in functions + match id_node.name.as_str() { + "some" | "ok" | "err" => { + // For now, these are effectively nops in terms of IR emission, + // as they just wrap the already pushed arguments. + // In a real implementation, they might push a tag. + return Ok(()); + } + _ => {} + } + + self.error("E_RESOLVE_UNDEFINED", format!("Undefined function '{}'", id_node.name), id_node.span); + Err(()) } } Node::MemberAccess(ma) => { @@ -344,24 +415,29 @@ impl<'a> Lowerer<'a> { if is_host_contract && !is_shadowed { if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) { self.emit(Instr::Syscall(syscall_id)); - return; + return Ok(()); + } else { + self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span); + return Err(()); } } } // Regular member call (method) or fallback - // In v0 we don't handle this yet, so emit dummy call - self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); + // In v0 we don't handle this yet. + self.error("E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), ma.span); + Err(()) } _ => { - self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); + self.error("E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), n.callee.span()); + Err(()) } } } - fn lower_binary(&mut self, n: &BinaryNode) { - self.lower_node(&n.left); - self.lower_node(&n.right); + fn lower_binary(&mut self, n: &BinaryNode) -> Result<(), ()> { + self.lower_node(&n.left)?; + self.lower_node(&n.right)?; match n.op.as_str() { "+" => self.emit(Instr::Add), "-" => self.emit(Instr::Sub), @@ -375,25 +451,33 @@ impl<'a> Lowerer<'a> { ">=" => self.emit(Instr::Gte), "&&" => self.emit(Instr::And), "||" => self.emit(Instr::Or), - _ => {} + _ => { + self.error("E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), n.span); + return Err(()); + } } + Ok(()) } - fn lower_unary(&mut self, n: &UnaryNode) { - self.lower_node(&n.expr); + fn lower_unary(&mut self, n: &UnaryNode) -> Result<(), ()> { + self.lower_node(&n.expr)?; match n.op.as_str() { "-" => self.emit(Instr::Neg), "!" => self.emit(Instr::Not), - _ => {} + _ => { + self.error("E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), n.span); + return Err(()); + } } + Ok(()) } - fn lower_if_expr(&mut self, n: &IfExprNode) { + fn lower_if_expr(&mut self, n: &IfExprNode) -> Result<(), ()> { let then_id = self.reserve_block_id(); let else_id = self.reserve_block_id(); let merge_id = self.reserve_block_id(); - self.lower_node(&n.cond); + self.lower_node(&n.cond)?; self.terminate(Terminator::JumpIfFalse { target: else_id, else_target: then_id, @@ -401,18 +485,19 @@ impl<'a> Lowerer<'a> { // Then block self.start_block_with_id(then_id); - self.lower_node(&n.then_block); + self.lower_node(&n.then_block)?; self.terminate(Terminator::Jump(merge_id)); // Else block self.start_block_with_id(else_id); if let Some(else_block) = &n.else_block { - self.lower_node(else_block); + self.lower_node(else_block)?; } self.terminate(Terminator::Jump(merge_id)); // Merge block self.start_block_with_id(merge_id); + Ok(()) } fn lower_type_node(&mut self, node: &Node) -> Type { @@ -532,7 +617,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); // Verify program structure assert_eq!(program.modules.len(), 1); @@ -569,7 +654,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let max_func = &program.modules[0].functions[0]; // Should have multiple blocks for if-else @@ -594,7 +679,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); @@ -622,7 +707,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let json = serde_json::to_string_pretty(&program).unwrap(); @@ -663,7 +748,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); @@ -697,7 +782,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); @@ -724,13 +809,11 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); + let result = lowerer.lower_file(&ast, "test"); - // Should NOT be a syscall if not declared as host - assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_)))); + assert!(result.is_err()); + let bundle = result.err().unwrap(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string()))); } #[test] @@ -750,13 +833,11 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); + let result = lowerer.lower_file(&ast, "test"); - // Should NOT be a syscall because Gfx is shadowed by a local - assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_)))); + assert!(result.is_err()); + let bundle = result.err().unwrap(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string()))); } #[test] @@ -775,15 +856,11 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); + let result = lowerer.lower_file(&ast, "test"); - // Should NOT be a syscall if invalid - assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_)))); - // Should be a regular call (which might fail later or be a dummy) - assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _)))); + assert!(result.is_err()); + let bundle = result.err().unwrap(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); } #[test] @@ -806,7 +883,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); @@ -838,7 +915,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); @@ -870,7 +947,7 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let lowerer = Lowerer::new(&module_symbols); - let program = lowerer.lower_file(&ast, "test"); + let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); @@ -886,4 +963,50 @@ mod tests { assert_eq!(*alloc.1, 1, "Primitive int should have 1 slot"); assert!(alloc.0.0 > 0, "Should have a valid TypeId"); } + + #[test] + fn test_missing_function_error() { + let code = " + fn main() { + missing_func(); + } + "; + let mut parser = Parser::new(code, 0); + let ast = parser.parse_file().expect("Failed to parse"); + + let mut collector = SymbolCollector::new(); + let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); + let module_symbols = ModuleSymbols { type_symbols, value_symbols }; + + let lowerer = Lowerer::new(&module_symbols); + let result = lowerer.lower_file(&ast, "test"); + + assert!(result.is_err()); + let bundle = result.err().unwrap(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); + assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined function 'missing_func'"))); + } + + #[test] + fn test_unresolved_ident_error() { + let code = " + fn main() { + let x = undefined_var; + } + "; + let mut parser = Parser::new(code, 0); + let ast = parser.parse_file().expect("Failed to parse"); + + let mut collector = SymbolCollector::new(); + let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); + let module_symbols = ModuleSymbols { type_symbols, value_symbols }; + + let lowerer = Lowerer::new(&module_symbols); + let result = lowerer.lower_file(&ast, "test"); + + assert!(result.is_err()); + let bundle = result.err().unwrap(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); + assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'"))); + } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/prometeu-compiler/src/frontends/pbs/mod.rs index 92776f47..a49d35cf 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/mod.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/mod.rs @@ -63,7 +63,7 @@ impl Frontend for PbsFrontend { // Lower to Core IR let lowerer = Lowerer::new(&module_symbols); let module_name = entry.file_stem().unwrap().to_string_lossy(); - let core_program = lowerer.lower_file(&ast, &module_name); + let core_program = lowerer.lower_file(&ast, &module_name)?; // Lower Core IR to VM IR core_to_vm::lower_program(&core_program).map_err(|e| { diff --git a/docs/specs/pbs/files/PRs para Junie.md b/docs/specs/pbs/files/PRs para Junie.md index fc96d235..64434ee0 100644 --- a/docs/specs/pbs/files/PRs para Junie.md +++ b/docs/specs/pbs/files/PRs para Junie.md @@ -1,24 +1,3 @@ -# PR-23 — Eliminate Invalid Call Fallbacks - -### Goal - -Prevent invalid bytecode generation. - -### Required Changes - -* Remove **all** fallbacks to `FunctionId(0)` or equivalent -* On unresolved symbols during lowering: - - * Emit canonical diagnostic (`E_RESOLVE_UNDEFINED` or `E_LOWER_UNSUPPORTED`) - * Abort lowering - -### Tests - -* PBS program calling missing function → compile error -* No Core IR or VM IR emitted - ---- - # PR-24 — Validate Contract Calls in Frontend (Arity + Types) ### Goal