This commit is contained in:
Nilton Constantino 2026-01-29 17:47:01 +00:00
parent dc3a0268f1
commit d216918c79
No known key found for this signature in database
3 changed files with 203 additions and 101 deletions

View File

@ -1,3 +1,4 @@
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
use crate::frontends::pbs::ast::*; use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::contracts::ContractRegistry; use crate::frontends::pbs::contracts::ContractRegistry;
@ -19,6 +20,7 @@ pub struct Lowerer<'a> {
type_ids: HashMap<String, TypeId>, type_ids: HashMap<String, TypeId>,
struct_slots: HashMap<String, u32>, struct_slots: HashMap<String, u32>,
contract_registry: ContractRegistry, contract_registry: ContractRegistry,
diagnostics: Vec<Diagnostic>,
} }
impl<'a> Lowerer<'a> { impl<'a> Lowerer<'a> {
@ -39,10 +41,20 @@ impl<'a> Lowerer<'a> {
type_ids: HashMap::new(), type_ids: HashMap::new(),
struct_slots: HashMap::new(), struct_slots: HashMap::new(),
contract_registry: ContractRegistry::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<Program, DiagnosticBundle> {
// Pre-scan for function declarations to assign IDs // Pre-scan for function declarations to assign IDs
for decl in &file.decls { for decl in &file.decls {
if let Node::FnDecl(n) = decl { if let Node::FnDecl(n) = decl {
@ -71,7 +83,9 @@ impl<'a> Lowerer<'a> {
for decl in &file.decls { for decl in &file.decls {
match decl { match decl {
Node::FnDecl(fn_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); module.functions.push(func);
} }
_ => {} // Other declarations not handled for now _ => {} // Other declarations not handled for now
@ -79,10 +93,10 @@ impl<'a> Lowerer<'a> {
} }
self.program.modules.push(module); 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<Function, ()> {
let func_id = *self.function_ids.get(&n.name).unwrap(); let func_id = *self.function_ids.get(&n.name).unwrap();
self.next_block_id = 0; self.next_block_id = 0;
self.local_vars = vec![HashMap::new()]; self.local_vars = vec![HashMap::new()];
@ -113,7 +127,7 @@ impl<'a> Lowerer<'a> {
self.current_function = Some(func); self.current_function = Some(func);
self.start_block(); self.start_block();
self.lower_node(&n.body); self.lower_node(&n.body)?;
// Ensure every function ends with a return if not already terminated // Ensure every function ends with a return if not already terminated
if let Some(mut block) = self.current_block.take() { 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 { match node {
Node::Block(n) => self.lower_block(n), Node::Block(n) => self.lower_block(n),
Node::LetStmt(n) => self.lower_let_stmt(n), Node::LetStmt(n) => self.lower_let_stmt(n),
@ -137,14 +151,17 @@ impl<'a> Lowerer<'a> {
Node::IntLit(n) => { Node::IntLit(n) => {
let id = self.program.const_pool.add_int(n.value); let id = self.program.const_pool.add_int(n.value);
self.emit(Instr::PushConst(id)); self.emit(Instr::PushConst(id));
Ok(())
} }
Node::FloatLit(n) => { Node::FloatLit(n) => {
let id = self.program.const_pool.add_float(n.value); let id = self.program.const_pool.add_float(n.value);
self.emit(Instr::PushConst(id)); self.emit(Instr::PushConst(id));
Ok(())
} }
Node::StringLit(n) => { Node::StringLit(n) => {
let id = self.program.const_pool.add_string(n.value.clone()); let id = self.program.const_pool.add_string(n.value.clone());
self.emit(Instr::PushConst(id)); self.emit(Instr::PushConst(id));
Ok(())
} }
Node::Ident(n) => self.lower_ident(n), Node::Ident(n) => self.lower_ident(n),
Node::Call(n) => self.lower_call(n), Node::Call(n) => self.lower_call(n),
@ -155,21 +172,27 @@ impl<'a> Lowerer<'a> {
Node::Mutate(n) => self.lower_mutate(n), Node::Mutate(n) => self.lower_mutate(n),
Node::Borrow(n) => self.lower_borrow(n), Node::Borrow(n) => self.lower_borrow(n),
Node::Peek(n) => self.lower_peek(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) { fn lower_alloc(&mut self, n: &AllocNode) -> Result<(), ()> {
let (ty_id, slots) = self.get_type_id_and_slots(&n.ty); let (ty_id, slots) = self.get_type_id_and_slots(&n.ty)?;
self.emit(Instr::Alloc { ty: ty_id, slots }); 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 { match node {
Node::TypeName(n) => { Node::TypeName(n) => {
let slots = self.struct_slots.get(&n.name).cloned().unwrap_or(1); let slots = self.struct_slots.get(&n.name).cloned().unwrap_or(1);
let id = self.get_or_create_type_id(&n.name); let id = self.get_or_create_type_id(&n.name);
(id, slots) Ok((id, slots))
} }
Node::TypeApp(ta) if ta.base == "array" => { Node::TypeApp(ta) if ta.base == "array" => {
let size = if ta.args.len() > 1 { 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 elem_ty = self.lower_type_node(&ta.args[0]);
let name = format!("array<{}>[{}]", elem_ty, size); let name = format!("array<{}>[{}]", elem_ty, size);
let id = self.get_or_create_type_id(&name); 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) // 1. Evaluate target (gate)
self.lower_node(&n.target); self.lower_node(&n.target)?;
// 2. Preserve gate identity // 2. Preserve gate identity
let gate_slot = self.get_next_local_slot(); let gate_slot = self.get_next_local_slot();
@ -220,17 +246,18 @@ impl<'a> Lowerer<'a> {
self.emit(Instr::SetLocal(view_slot)); self.emit(Instr::SetLocal(view_slot));
// 5. Body // 5. Body
self.lower_node(&n.body); self.lower_node(&n.body)?;
// 6. End Operation // 6. End Operation
self.emit(Instr::EndPeek); self.emit(Instr::EndPeek);
self.local_vars.pop(); self.local_vars.pop();
Ok(())
} }
fn lower_borrow(&mut self, n: &BorrowNode) { fn lower_borrow(&mut self, n: &BorrowNode) -> Result<(), ()> {
// 1. Evaluate target (gate) // 1. Evaluate target (gate)
self.lower_node(&n.target); self.lower_node(&n.target)?;
// 2. Preserve gate identity // 2. Preserve gate identity
let gate_slot = self.get_next_local_slot(); let gate_slot = self.get_next_local_slot();
@ -247,17 +274,18 @@ impl<'a> Lowerer<'a> {
self.emit(Instr::SetLocal(view_slot)); self.emit(Instr::SetLocal(view_slot));
// 5. Body // 5. Body
self.lower_node(&n.body); self.lower_node(&n.body)?;
// 6. End Operation // 6. End Operation
self.emit(Instr::EndBorrow); self.emit(Instr::EndBorrow);
self.local_vars.pop(); self.local_vars.pop();
Ok(())
} }
fn lower_mutate(&mut self, n: &MutateNode) { fn lower_mutate(&mut self, n: &MutateNode) -> Result<(), ()> {
// 1. Evaluate target (gate) // 1. Evaluate target (gate)
self.lower_node(&n.target); self.lower_node(&n.target)?;
// 2. Preserve gate identity // 2. Preserve gate identity
let gate_slot = self.get_next_local_slot(); let gate_slot = self.get_next_local_slot();
@ -274,61 +302,104 @@ impl<'a> Lowerer<'a> {
self.emit(Instr::SetLocal(view_slot)); self.emit(Instr::SetLocal(view_slot));
// 5. Body // 5. Body
self.lower_node(&n.body); self.lower_node(&n.body)?;
// 6. End Operation // 6. End Operation
self.emit(Instr::EndMutate); self.emit(Instr::EndMutate);
self.local_vars.pop(); 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()); self.local_vars.push(HashMap::new());
for stmt in &n.stmts { for stmt in &n.stmts {
self.lower_node(stmt); self.lower_node(stmt)?;
} }
if let Some(tail) = &n.tail { if let Some(tail) = &n.tail {
self.lower_node(tail); self.lower_node(tail)?;
} }
self.local_vars.pop(); self.local_vars.pop();
Ok(())
} }
fn lower_let_stmt(&mut self, n: &LetStmtNode) { fn lower_let_stmt(&mut self, n: &LetStmtNode) -> Result<(), ()> {
self.lower_node(&n.init); self.lower_node(&n.init)?;
let slot = self.get_next_local_slot(); let slot = self.get_next_local_slot();
self.local_vars.last_mut().unwrap().insert(n.name.clone(), slot); self.local_vars.last_mut().unwrap().insert(n.name.clone(), slot);
self.emit(Instr::SetLocal(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 { if let Some(expr) = &n.expr {
self.lower_node(expr); self.lower_node(expr)?;
} }
self.terminate(Terminator::Return); 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) { if let Some(slot) = self.lookup_local(&n.name) {
self.emit(Instr::GetLocal(slot)); self.emit(Instr::GetLocal(slot));
Ok(())
} else { } 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) // Check if it's a function (for first-class functions if supported)
if let Some(_id) = self.function_ids.get(&n.name) { if let Some(_id) = self.function_ids.get(&n.name) {
// Push function reference? Not in v0. // 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 { for arg in &n.args {
self.lower_node(arg); self.lower_node(arg)?;
} }
match &*n.callee { match &*n.callee {
Node::Ident(id_node) => { Node::Ident(id_node) => {
if let Some(func_id) = self.function_ids.get(&id_node.name) { if let Some(func_id) = self.function_ids.get(&id_node.name) {
self.emit(Instr::Call(*func_id, n.args.len() as u32)); self.emit(Instr::Call(*func_id, n.args.len() as u32));
Ok(())
} else { } else {
// Unknown function - might be a builtin or diagnostic was missed // Check for special built-in functions
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); 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) => { Node::MemberAccess(ma) => {
@ -344,24 +415,29 @@ impl<'a> Lowerer<'a> {
if is_host_contract && !is_shadowed { if is_host_contract && !is_shadowed {
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) { if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
self.emit(Instr::Syscall(syscall_id)); 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 // Regular member call (method) or fallback
// In v0 we don't handle this yet, so emit dummy call // In v0 we don't handle this yet.
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); 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) { fn lower_binary(&mut self, n: &BinaryNode) -> Result<(), ()> {
self.lower_node(&n.left); self.lower_node(&n.left)?;
self.lower_node(&n.right); self.lower_node(&n.right)?;
match n.op.as_str() { match n.op.as_str() {
"+" => self.emit(Instr::Add), "+" => self.emit(Instr::Add),
"-" => self.emit(Instr::Sub), "-" => self.emit(Instr::Sub),
@ -375,25 +451,33 @@ impl<'a> Lowerer<'a> {
">=" => self.emit(Instr::Gte), ">=" => self.emit(Instr::Gte),
"&&" => self.emit(Instr::And), "&&" => self.emit(Instr::And),
"||" => self.emit(Instr::Or), "||" => 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) { fn lower_unary(&mut self, n: &UnaryNode) -> Result<(), ()> {
self.lower_node(&n.expr); self.lower_node(&n.expr)?;
match n.op.as_str() { match n.op.as_str() {
"-" => self.emit(Instr::Neg), "-" => self.emit(Instr::Neg),
"!" => self.emit(Instr::Not), "!" => 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 then_id = self.reserve_block_id();
let else_id = self.reserve_block_id(); let else_id = self.reserve_block_id();
let merge_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 { self.terminate(Terminator::JumpIfFalse {
target: else_id, target: else_id,
else_target: then_id, else_target: then_id,
@ -401,18 +485,19 @@ impl<'a> Lowerer<'a> {
// Then block // Then block
self.start_block_with_id(then_id); 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)); self.terminate(Terminator::Jump(merge_id));
// Else block // Else block
self.start_block_with_id(else_id); self.start_block_with_id(else_id);
if let Some(else_block) = &n.else_block { if let Some(else_block) = &n.else_block {
self.lower_node(else_block); self.lower_node(else_block)?;
} }
self.terminate(Terminator::Jump(merge_id)); self.terminate(Terminator::Jump(merge_id));
// Merge block // Merge block
self.start_block_with_id(merge_id); self.start_block_with_id(merge_id);
Ok(())
} }
fn lower_type_node(&mut self, node: &Node) -> Type { fn lower_type_node(&mut self, node: &Node) -> Type {
@ -532,7 +617,7 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 // Verify program structure
assert_eq!(program.modules.len(), 1); assert_eq!(program.modules.len(), 1);
@ -569,7 +654,7 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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]; let max_func = &program.modules[0].functions[0];
// Should have multiple blocks for if-else // Should have multiple blocks for if-else
@ -594,7 +679,7 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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 module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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(); let json = serde_json::to_string_pretty(&program).unwrap();
@ -663,7 +748,7 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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 module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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 module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols); let lowerer = Lowerer::new(&module_symbols);
let program = lowerer.lower_file(&ast, "test"); let result = lowerer.lower_file(&ast, "test");
let func = &program.modules[0].functions[0]; assert!(result.is_err());
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let bundle = result.err().unwrap();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string())));
// Should NOT be a syscall if not declared as host
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
} }
#[test] #[test]
@ -750,13 +833,11 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols); let lowerer = Lowerer::new(&module_symbols);
let program = lowerer.lower_file(&ast, "test"); let result = lowerer.lower_file(&ast, "test");
let func = &program.modules[0].functions[0]; assert!(result.is_err());
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let bundle = result.err().unwrap();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string())));
// Should NOT be a syscall because Gfx is shadowed by a local
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
} }
#[test] #[test]
@ -775,15 +856,11 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols); let lowerer = Lowerer::new(&module_symbols);
let program = lowerer.lower_file(&ast, "test"); let result = lowerer.lower_file(&ast, "test");
let func = &program.modules[0].functions[0]; assert!(result.is_err());
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let bundle = result.err().unwrap();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
// 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(_, _))));
} }
#[test] #[test]
@ -806,7 +883,7 @@ mod tests {
let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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 module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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 module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_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 func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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_eq!(*alloc.1, 1, "Primitive int should have 1 slot");
assert!(alloc.0.0 > 0, "Should have a valid TypeId"); 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'")));
}
} }

View File

@ -63,7 +63,7 @@ impl Frontend for PbsFrontend {
// Lower to Core IR // Lower to Core IR
let lowerer = Lowerer::new(&module_symbols); let lowerer = Lowerer::new(&module_symbols);
let module_name = entry.file_stem().unwrap().to_string_lossy(); 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 // Lower Core IR to VM IR
core_to_vm::lower_program(&core_program).map_err(|e| { core_to_vm::lower_program(&core_program).map_err(|e| {

View File

@ -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) # PR-24 — Validate Contract Calls in Frontend (Arity + Types)
### Goal ### Goal