pr 23
This commit is contained in:
parent
dc3a0268f1
commit
d216918c79
@ -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<String, TypeId>,
|
||||
struct_slots: HashMap<String, u32>,
|
||||
contract_registry: ContractRegistry,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
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<Program, DiagnosticBundle> {
|
||||
// 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<Function, ()> {
|
||||
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 result = 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();
|
||||
|
||||
// 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 result = 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();
|
||||
|
||||
// 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 result = 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();
|
||||
|
||||
// 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'")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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| {
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user