use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; use crate::common::spans::Span; use crate::frontends::pbs::ast::*; use crate::frontends::pbs::contracts::ContractRegistry; use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::resolver::ModuleProvider; use crate::frontends::pbs::types::PbsType; use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId, SigId}; use crate::ir_core::{Block, ConstPool, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type}; use crate::ir_core::signature::{Signature, global_signature_interner}; use prometeu_analysis::{NameInterner, NodeId}; use std::collections::HashMap; #[derive(Clone)] struct LocalInfo { slot: u32, ty: Type, } pub struct Lowerer<'a> { arena: &'a AstArena, module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols, module_provider: &'a dyn ModuleProvider, interner: &'a NameInterner, program: Program, current_function: Option, current_block: Option, next_block_id: u32, next_func_id: u32, next_type_id: u32, local_vars: Vec>, function_ids: HashMap, type_ids: HashMap, struct_slots: HashMap, struct_constructors: HashMap>, type_constants: HashMap>, current_type_context: Option, user_struct_field_offsets: HashMap>, user_struct_field_types: HashMap>, method_self_slot: Option, contract_registry: ContractRegistry, diagnostics: Vec, max_slots_used: u32, current_span: Option, import_bindings: HashMap, } impl<'a> Lowerer<'a> { fn sig_from_pbs_fn(&self, pbs: &PbsType) -> Option { if let PbsType::Function { params, return_type } = pbs { let mut core_params = Vec::with_capacity(params.len()); for p in params { core_params.push(self.convert_pbs_type(p)); } let core_ret = self.convert_pbs_type(return_type); let sig = Signature { params: core_params, return_type: core_ret }; // Global, deterministic interner across the compiler process let mut guard = global_signature_interner().lock().unwrap(); Some(guard.intern(sig)) } else { None } } // #[inline] // fn hash_tag_u16(s: &str) -> u16 { // // FNV-1a 16-bit (simple, deterministic, allows small collisions) // let mut hash: u16 = 0x811C; // offset basis (truncated) // let prime: u16 = 0x0101; // 257 // for &b in s.as_bytes() { // hash ^= b as u16; // hash = hash.wrapping_mul(prime); // } // hash // } pub fn new( arena: &'a AstArena, module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols, module_provider: &'a dyn ModuleProvider, interner: &'a NameInterner, ) -> Self { let mut field_offsets = HashMap::new(); field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation let mut struct_slots = HashMap::new(); struct_slots.insert("Color".to_string(), 1); struct_slots.insert("ButtonState".to_string(), 4); // New service-based input returns `Button` (same layout as legacy ButtonState: 4 slots) struct_slots.insert("Button".to_string(), 4); struct_slots.insert("Pad".to_string(), 48); struct_slots.insert("Touch".to_string(), 6); Self { arena, module_symbols, imported_symbols, module_provider, interner, program: Program { const_pool: ConstPool::new(), modules: Vec::new(), field_offsets, field_types: HashMap::new(), }, current_function: None, current_block: None, next_block_id: 0, next_func_id: 1, next_type_id: 1, local_vars: Vec::new(), function_ids: HashMap::new(), type_ids: HashMap::new(), struct_slots, struct_constructors: HashMap::new(), type_constants: HashMap::new(), current_type_context: None, user_struct_field_offsets: HashMap::new(), user_struct_field_types: HashMap::new(), method_self_slot: None, contract_registry: ContractRegistry::new(), diagnostics: Vec::new(), max_slots_used: 0, current_span: None, import_bindings: HashMap::new(), } } fn error(&mut self, code: &str, message: String, span: Span) { self.diagnostics.push(Diagnostic { severity: Severity::Error, code: code.to_string(), message, span, related: Vec::new(), }); } pub fn lower_file(mut self, root: NodeId, module_name: &str) -> Result { // Ensure per-module function id space starts clean and small self.next_func_id = 1; self.function_ids.clear(); let file = match self.arena.kind(root) { NodeKind::File(file) => file, _ => { return Err(DiagnosticBundle::error( "E_LOWER_INVALID_ROOT", "Expected File node as root".to_string(), self.arena.span(root), )) } }; // Construir mapa de imports: para cada import do arquivo, se vier do formato // "@alias:module", associe cada símbolo importado em `spec.path` ao par (alias,module). self.import_bindings.clear(); for &imp in &file.imports { if let NodeKind::Import(imp_node) = self.arena.kind(imp) { let from = imp_node.from.as_str(); if let Some(rest) = from.strip_prefix('@') { // Espera-se formato @alias:module_path let mut parts = rest.splitn(2, ':'); if let (Some(alias), Some(module_path)) = (parts.next(), parts.next()) { if let NodeKind::ImportSpec(spec) = self.arena.kind(imp_node.spec) { for name in &spec.path { let sym = self.interner.resolve(*name).to_string(); self.import_bindings.insert(sym, (alias.to_string(), module_path.to_string())); } } } } } } // Pre-scan for function declarations to assign IDs for decl in &file.decls { match self.arena.kind(*decl) { NodeKind::FnDecl(n) => { let id = FunctionId(self.next_func_id); self.next_func_id += 1; self.function_ids .insert(self.interner.resolve(n.name).to_string(), id); } NodeKind::ServiceDecl(n) => { let service_name = self.interner.resolve(n.name).to_string(); for m in &n.members { if let NodeKind::ServiceFnDecl(decl) = self.arena.kind(*m) { let full_name = format!("{}.{}", service_name, self.interner.resolve(decl.name)); let id = FunctionId(self.next_func_id); self.next_func_id += 1; self.function_ids.insert(full_name, id); } } } NodeKind::TypeDecl(n) => { let id = TypeId(self.next_type_id); self.next_type_id += 1; self.type_ids .insert(self.interner.resolve(n.name).to_string(), id); // Pré-scan de métodos dentro do tipo (apenas FnDecl com corpo) let type_name = self.interner.resolve(n.name).to_string(); if let Some(body_id) = n.body { if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) { for m in &tb.methods { if let NodeKind::FnDecl(md) = self.arena.kind(*m) { let full_name = format!("{}.{}", type_name, self.interner.resolve(md.name)); let id = FunctionId(self.next_func_id); self.next_func_id += 1; self.function_ids.insert(full_name, id); } } } } } _ => {} } } // Second pre-scan: calculate struct slots (recursive) let mut struct_nodes = HashMap::new(); for decl in &file.decls { if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { if n.type_kind == "struct" { struct_nodes.insert(self.interner.resolve(n.name).to_string(), *decl); } } } let mut changed = true; while changed { changed = false; for (name, node_id) in &struct_nodes { if !self.struct_slots.contains_key(name) { let mut slots = 0; let mut all_known = true; let node = match self.arena.kind(*node_id) { NodeKind::TypeDecl(node) => node, _ => continue, }; for param in &node.params { let member_ty = self.lower_type_node(param.ty); match &member_ty { Type::Struct(sname) => { if let Some(s_slots) = self.get_builtin_struct_slots(sname) { slots += s_slots; } else if let Some(s_slots) = self.struct_slots.get(sname) { slots += s_slots; } else { all_known = false; break; } } _ => slots += self.get_type_slots(&member_ty), } } if all_known { self.struct_slots.insert(name.clone(), slots); changed = true; } } } } for decl in &file.decls { if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { let type_name = self.interner.resolve(n.name).to_string(); let mut constants = HashMap::new(); for c in &n.constants { if let NodeKind::ConstantDecl(constant) = self.arena.kind(*c) { constants.insert( self.interner.resolve(constant.name).to_string(), constant.value, ); } } self.type_constants.insert(type_name.clone(), constants); let mut ctors = HashMap::new(); // Default constructor: TypeName(...) if n.type_kind == "struct" { if let Some(default_ctor) = n.constructors.first() { if let NodeKind::ConstructorDecl(_) = self.arena.kind(*default_ctor) { ctors.insert(type_name.clone(), *default_ctor); } } } for ctor in &n.constructors { if let NodeKind::ConstructorDecl(ctor_node) = self.arena.kind(*ctor) { ctors.insert(self.interner.resolve(ctor_node.name).to_string(), *ctor); } } self.struct_constructors.insert(type_name, ctors); } } // Calcular offsets e tipos de campos para structs de usuário for decl in &file.decls { if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { if n.type_kind == "struct" { let type_name = self.interner.resolve(n.name).to_string(); let mut offsets = HashMap::new(); let mut types = HashMap::new(); let mut acc: u32 = 0; // Campos do cabeçalho for p in &n.params { let ty = self.lower_type_node(p.ty); let slots = self.get_type_slots(&ty); offsets.insert(self.interner.resolve(p.name).to_string(), acc); types.insert(self.interner.resolve(p.name).to_string(), ty); acc += slots; } // Campos adicionais (members) no corpo if let Some(body_id) = n.body { if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) { for m in &tb.members { let ty = self.lower_type_node(m.ty); let slots = self.get_type_slots(&ty); offsets.insert(self.interner.resolve(m.name).to_string(), acc); types.insert(self.interner.resolve(m.name).to_string(), ty); acc += slots; } } } self.user_struct_field_offsets.insert(type_name.clone(), offsets); self.user_struct_field_types.insert(type_name, types); } } } let mut module = Module { name: module_name.to_string(), functions: Vec::new(), }; for decl in &file.decls { match self.arena.kind(*decl) { NodeKind::FnDecl(_) => { let func = self.lower_function(*decl).map_err(|_| DiagnosticBundle { diagnostics: self.diagnostics.clone(), })?; module.functions.push(func); } NodeKind::ServiceDecl(n) => { let service_name = self.interner.resolve(n.name).to_string(); for m in &n.members { if let NodeKind::ServiceFnDecl(_) = self.arena.kind(*m) { let func = self.lower_service_function(&service_name, *m).map_err(|_| DiagnosticBundle { diagnostics: self.diagnostics.clone(), })?; module.functions.push(func); } } } _ => {} } } // Baixar métodos de structs for decl in &file.decls { if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { let type_name = self.interner.resolve(n.name).to_string(); if let Some(body_id) = n.body { if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) { for m in &tb.methods { if let NodeKind::FnDecl(_) = self.arena.kind(*m) { let func = self.lower_method_function(&type_name, *m).map_err(|_| DiagnosticBundle { diagnostics: self.diagnostics.clone(), })?; module.functions.push(func); } } } } } } self.program.modules.push(module); Ok(self.program) } fn lower_function(&mut self, node: NodeId) -> Result { let n = match self.arena.kind(node) { NodeKind::FnDecl(n) => n, _ => return Err(()), }; let func_name = self.interner.resolve(n.name).to_string(); let func_id = *self.function_ids.get(&func_name).unwrap(); self.next_block_id = 0; self.local_vars = vec![HashMap::new()]; self.max_slots_used = 0; let mut params = Vec::new(); let mut local_types = HashMap::new(); let mut param_slots = 0u32; for param in &n.params { let ty = self.lower_type_node(param.ty); let slots = self.get_type_slots(&ty); params.push(Param { name: self.interner.resolve(param.name).to_string(), ty: ty.clone(), }); self.local_vars[0].insert( self.interner.resolve(param.name).to_string(), LocalInfo { slot: param_slots, ty: ty.clone(), }, ); for i in 0..slots { local_types.insert(param_slots + i, ty.clone()); } param_slots += slots; } self.max_slots_used = param_slots; let ret_ty = if let Some(ret) = n.ret { self.lower_type_node(ret) } else { Type::Void }; let return_slots = self.get_type_slots(&ret_ty); // Build Signature and intern to SigId let func_sig = Signature { params: params.iter().map(|p| p.ty.clone()).collect(), return_type: ret_ty.clone(), }; let sig_id = { let mut interner = global_signature_interner().lock().unwrap(); interner.intern(func_sig) }; let func = Function { id: func_id, name: func_name, sig: sig_id, params, return_type: ret_ty, blocks: Vec::new(), local_types, param_slots: param_slots as u16, local_slots: 0, return_slots: return_slots as u16, }; self.current_function = Some(func); self.start_block(); 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() { if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) { block.terminator = Terminator::Return; } if let Some(func) = &mut self.current_function { func.blocks.push(block); } } let mut final_func = self.current_function.take().unwrap(); final_func.local_slots = (self.max_slots_used - param_slots) as u16; Ok(final_func) } fn lower_service_function(&mut self, service_name: &str, node: NodeId) -> Result { let n = match self.arena.kind(node) { NodeKind::ServiceFnDecl(n) => n, _ => return Err(()), }; let method_name = self.interner.resolve(n.name).to_string(); let full_name = format!("{}.{}", service_name, method_name); let func_id = match self.function_ids.get(&full_name) { Some(id) => *id, None => { self.error( "E_LOWER_UNSUPPORTED", format!("Missing function id for service method '{}'", full_name), self.arena.span(node), ); return Err(()); } }; self.next_block_id = 0; self.local_vars = vec![HashMap::new()]; self.max_slots_used = 0; let mut params = Vec::new(); let mut local_types = HashMap::new(); let mut param_slots = 0u32; for p in &n.params { let ty = self.lower_type_node(p.ty); let slots = self.get_type_slots(&ty); params.push(Param { name: self.interner.resolve(p.name).to_string(), ty: ty.clone() }); self.local_vars[0].insert( self.interner.resolve(p.name).to_string(), LocalInfo { slot: param_slots, ty: ty.clone() }, ); for i in 0..slots { local_types.insert(param_slots + i, ty.clone()); } param_slots += slots; } self.max_slots_used = param_slots; let ret_ty = self.lower_type_node(n.ret); let return_slots = self.get_type_slots(&ret_ty); // Build Signature and intern to SigId (espelha lower_function) let func_sig = Signature { params: params.iter().map(|p| p.ty.clone()).collect(), return_type: ret_ty.clone(), }; let sig_id = { let mut interner = global_signature_interner().lock().unwrap(); interner.intern(func_sig) }; // Inicializa a função atual (espelha lower_function) let func = Function { id: func_id, // Nome público da função no módulo: use apenas o nome do método. // O module_path fará a desambiguação durante export/link. name: method_name, sig: sig_id, params, return_type: ret_ty, blocks: Vec::new(), local_types, param_slots: param_slots as u16, local_slots: 0, return_slots: return_slots as u16, }; // Registrar como função corrente para que start_block/lower_node // acumulem instruções corretamente. self.current_function = Some(func); self.start_block(); self.lower_node(n.body)?; // Garantir terminador e empurrar bloco final if let Some(mut block) = self.current_block.take() { if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) { block.terminator = Terminator::Return; } if let Some(func) = &mut self.current_function { func.blocks.push(block); } } // Finalizar função: calcular local_slots e devolver let mut final_func = self.current_function.take().unwrap(); final_func.local_slots = (self.max_slots_used - param_slots) as u16; Ok(final_func) } fn lower_node(&mut self, node: NodeId) -> Result<(), ()> { let old_span = self.current_span.clone(); self.current_span = Some(self.arena.span(node)); let res = match self.arena.kind(node) { NodeKind::Block(n) => self.lower_block(node, n), NodeKind::LetStmt(n) => self.lower_let_stmt(node, n), NodeKind::ExprStmt(n) => self.lower_node(n.expr), NodeKind::ReturnStmt(n) => self.lower_return_stmt(node, n), NodeKind::IntLit(n) => { let id = self.program.const_pool.add_int(n.value); self.emit(InstrKind::PushConst(id)); Ok(()) } NodeKind::FloatLit(n) => { let id = self.program.const_pool.add_float(n.value); self.emit(InstrKind::PushConst(id)); Ok(()) } NodeKind::StringLit(n) => { let id = self.program.const_pool.add_string(n.value.clone()); self.emit(InstrKind::PushConst(id)); Ok(()) } NodeKind::BoundedLit(n) => { self.emit(InstrKind::PushBounded(n.value)); Ok(()) } NodeKind::Ident(n) => self.lower_ident(node, n), NodeKind::MemberAccess(n) => self.lower_member_access(node, n), NodeKind::Call(n) => self.lower_call(node, n), NodeKind::Binary(n) => self.lower_binary(node, n), NodeKind::Unary(n) => self.lower_unary(node, n), NodeKind::IfExpr(n) => self.lower_if_expr(node, n), NodeKind::WhenExpr(n) => self.lower_when_expr(node, n), NodeKind::Alloc(n) => self.lower_alloc(node, n), NodeKind::Mutate(n) => self.lower_mutate(node, n), NodeKind::Borrow(n) => self.lower_borrow(node, n), NodeKind::Peek(n) => self.lower_peek(node, n), _ => { self.error( "E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", self.arena.kind(node)), self.arena.span(node), ); Err(()) } }; self.current_span = old_span; res } fn lower_alloc(&mut self, _node: NodeId, n: &AllocNodeArena) -> Result<(), ()> { let (ty_id, slots) = self.get_type_id_and_slots(n.ty)?; self.emit(InstrKind::Alloc { ty: ty_id, slots }); Ok(()) } fn get_type_id_and_slots(&mut self, node: NodeId) -> Result<(TypeId, u32), ()> { match self.arena.kind(node) { NodeKind::TypeName(n) => { let name = self.interner.resolve(n.name); let slots = self.struct_slots.get(name).cloned().unwrap_or(1); let id = self.get_or_create_type_id(name); Ok((id, slots)) } NodeKind::TypeApp(ta) if self.interner.resolve(ta.base) == "array" => { let size = if ta.args.len() > 1 { if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) { il.value as u32 } else { 1 } } else { 1 }; 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); Ok((id, size)) } _ => { self.error( "E_RESOLVE_UNDEFINED", format!("Unknown type in allocation: {:?}", self.arena.kind(node)), self.arena.span(node), ); Err(()) } } } fn get_or_create_type_id(&mut self, name: &str) -> TypeId { if let Some(id) = self.type_ids.get(name) { *id } else { let id = TypeId(self.next_type_id); self.next_type_id += 1; self.type_ids.insert(name.to_string(), id); id } } fn lower_peek(&mut self, _node: NodeId, n: &PeekNodeArena) -> Result<(), ()> { // 1. Evaluate target (gate) self.lower_node(n.target)?; // 2. Preserve gate identity let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); self.emit(InstrKind::SetLocal(gate_slot)); // 3. Begin Operation self.emit(InstrKind::BeginPeek { gate: ValueId(gate_slot) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); // 4. Bind view to local self.local_vars.push(HashMap::new()); let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); self.emit(InstrKind::SetLocal(view_slot)); // 5. Body self.lower_node(n.body)?; // 6. End Operation self.emit(InstrKind::EndPeek); self.local_vars.pop(); Ok(()) } fn lower_borrow(&mut self, _node: NodeId, n: &BorrowNodeArena) -> Result<(), ()> { // 1. Evaluate target (gate) self.lower_node(n.target)?; // 2. Preserve gate identity let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); self.emit(InstrKind::SetLocal(gate_slot)); // 3. Begin Operation self.emit(InstrKind::BeginBorrow { gate: ValueId(gate_slot) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); // 4. Bind view to local self.local_vars.push(HashMap::new()); let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); self.emit(InstrKind::SetLocal(view_slot)); // 5. Body self.lower_node(n.body)?; // 6. End Operation self.emit(InstrKind::EndBorrow); self.local_vars.pop(); Ok(()) } fn lower_mutate(&mut self, _node: NodeId, n: &MutateNodeArena) -> Result<(), ()> { // 1. Evaluate target (gate) self.lower_node(n.target)?; // 2. Preserve gate identity let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); self.emit(InstrKind::SetLocal(gate_slot)); // 3. Begin Operation self.emit(InstrKind::BeginMutate { gate: ValueId(gate_slot) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); // 4. Bind view to local self.local_vars.push(HashMap::new()); let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); self.emit(InstrKind::SetLocal(view_slot)); // 5. Body self.lower_node(n.body)?; // 6. End Operation self.emit(InstrKind::EndMutate); self.local_vars.pop(); Ok(()) } fn lower_block(&mut self, _node: NodeId, n: &BlockNodeArena) -> Result<(), ()> { self.local_vars.push(HashMap::new()); for stmt in &n.stmts { // Lower the statement normally self.lower_node(*stmt)?; // Guardrail: if the statement is a standalone Call that returns values, // discard them to keep the stack balanced. This is essential for host // contract calls that return multi-slot structs (e.g., Button=4 slots). if let NodeKind::Call(call) = self.arena.kind(*stmt) { // Try to compute return slots conservatively let mut return_slots: u32 = 0; match self.arena.kind(call.callee) { NodeKind::MemberAccess(ma) => { // Static contract call: Contract.method(...) if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let contract_name = self.interner.resolve(obj_id.name); let method_name = self.interner.resolve(ma.member); if let Some(method) = self.contract_registry.get_method(contract_name, method_name) { let ty = self.convert_pbs_type(&method.return_type); return_slots = self.get_type_slots(&ty); } } } NodeKind::Ident(id_node) => { // Calling a local function or imported symbol — attempt best effort via symbol table let callee_name = self.interner.resolve(id_node.name).to_string(); if let Some(func_id) = self.function_ids.get(&callee_name) { let _ = func_id; // unresolved return info in v0 → assume 0 (no discard) } else { // ImportCall or unknown — assume 0 (safe) } } _ => {} } if return_slots > 0 { for _ in 0..return_slots { self.emit(InstrKind::Pop); } } } } if let Some(tail) = n.tail { self.lower_node(tail)?; } self.local_vars.pop(); Ok(()) } fn lower_let_stmt(&mut self, _node: NodeId, n: &LetStmtNodeArena) -> Result<(), ()> { self.lower_node(n.init)?; let ty = if let Some(ty_node) = n.ty { self.lower_type_node(ty_node) } else { // Very basic inference for host calls if let NodeKind::Call(call) = self.arena.kind(n.init) { if let NodeKind::MemberAccess(ma) = self.arena.kind(call.callee) { if let NodeKind::Ident(obj) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj.name); let member_name = self.interner.resolve(ma.member); // 1) Prefer exact contract registry mapping (Pad/Touch services, etc.) if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { self.convert_pbs_type(&method.return_type) } else { // 2) Legacy snapshot helpers match (obj_name, member_name) { ("Input", "pad") => Type::Struct("Pad".to_string()), ("Input", "touch") => Type::Struct("Touch".to_string()), _ => Type::Int, } } } else { Type::Int } } else { Type::Int } } else if let NodeKind::MemberAccess(ma) = self.arena.kind(n.init) { // Inferência para constantes de struct: Type.CONST if let NodeKind::Ident(obj) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj.name); if let Some(consts) = self.type_constants.get(obj_name) { let member_name = self.interner.resolve(ma.member); if consts.contains_key(member_name) { Type::Struct(obj_name.to_string()) } else { Type::Int } } else { Type::Int } } else { Type::Int } } else { Type::Int } }; let slots = self.get_type_slots(&ty); let slot = self.add_local_to_scope(self.interner.resolve(n.name).to_string(), ty); for i in (0..slots).rev() { self.emit(InstrKind::SetLocal(slot + i)); } Ok(()) } fn lower_return_stmt(&mut self, _node: NodeId, n: &ReturnStmtNodeArena) -> Result<(), ()> { if let Some(expr) = n.expr { self.lower_node(expr)?; } self.terminate(Terminator::Return); Ok(()) } fn lower_ident(&mut self, node: NodeId, n: &IdentNodeArena) -> Result<(), ()> { let name_str = self.interner.resolve(n.name); if let Some(info) = self.find_local(name_str) { let slots = self.get_type_slots(&info.ty); for i in 0..slots { self.emit(InstrKind::GetLocal(info.slot + i)); } Ok(()) } else { // Se estamos no corpo de um método, permitir acessar campos de `self` implicitamente if let (Some(struct_name), Some(self_slot)) = (self.current_type_context.as_ref(), self.method_self_slot) { let maybe_off = self .user_struct_field_offsets .get(struct_name) .and_then(|m| m.get(name_str).cloned()); if let Some(off_val) = maybe_off { let ty = self .user_struct_field_types .get(struct_name) .and_then(|m| m.get(name_str)) .cloned() .unwrap_or(Type::Int); let slots = self.get_type_slots(&ty); for i in 0..slots { self.emit(InstrKind::GetLocal(self_slot + off_val + i)); } return Ok(()); } } // Check for special identifiers match name_str { "true" => { let id = self.program.const_pool.add_int(1); self.emit(InstrKind::PushConst(id)); return Ok(()); } "false" => { let id = self.program.const_pool.add_int(0); self.emit(InstrKind::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(InstrKind::PushConst(id)); return Ok(()); } _ => {} } // Check if it's a function (for first-class functions if supported) if let Some(_id) = self.function_ids.get(name_str) { // Push function reference? Not in v0. self.error( "E_LOWER_UNSUPPORTED", format!("First-class function reference '{}' not supported", name_str), self.arena.span(node), ); Err(()) } else { self.error( "E_RESOLVE_UNDEFINED", format!("Undefined identifier '{}'", name_str), self.arena.span(node), ); Err(()) } } } fn lower_member_access(&mut self, node: NodeId, n: &MemberAccessNodeArena) -> Result<(), ()> { if let NodeKind::Ident(id) = self.arena.kind(n.object) { let type_name = self.interner.resolve(id.name); let member_name = self.interner.resolve(n.member); if let Some(constants) = self.type_constants.get(type_name).cloned() { if let Some(const_val) = constants.get(member_name) { let old_ctx = self.current_type_context.replace(type_name.to_string()); let res = self.lower_node(*const_val); self.current_type_context = old_ctx; return res; } } if type_name == "Color" { let val = match member_name { "BLACK" => 0x0000, "WHITE" => 0xFFFF, "RED" => 0xF800, "GREEN" => 0x07E0, "BLUE" => 0x001F, "MAGENTA" => 0xF81F, "TRANSPARENT" => 0x0000, "COLOR_KEY" => 0x0000, _ => { // Check if it's a method call like Color.rgb, handled in lower_call return Ok(()); } }; self.emit(InstrKind::PushBounded(val)); return Ok(()); } } if let Some((slot, ty)) = self.resolve_member_access(node) { let slots = self.get_type_slots(&ty); for i in 0..slots { self.emit(InstrKind::GetLocal(slot + i)); } return Ok(()); } // Fallback: Handle member access where the object is a Call to a host contract method // Example: Pad.a().down — object is a Call(MemberAccess(Pad, a)) if let NodeKind::Call(call_node) = self.arena.kind(n.object) { if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) { if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) { let contract_name = self.interner.resolve(obj_id.name); let method_name = self.interner.resolve(inner_ma.member); if let Some(method) = self.contract_registry.get_method(contract_name, method_name) { // Determine return type and slots let ret_ty = self.convert_pbs_type(&method.return_type); let slots = self.get_type_slots(&ret_ty); if let Type::Struct(struct_name) = &ret_ty { // Lower the call to push all slots self.lower_call(n.object, &call_node)?; // Store into a temp local to avoid leaving extra slots on the operand stack let tmp_slot = self.add_local_to_scope( format!("$tmp_{}", self.get_next_local_slot()), ret_ty.clone(), ); for i in (0..slots).rev() { self.emit(InstrKind::SetLocal(tmp_slot + i)); } // Load only the requested field let field_name = self.interner.resolve(n.member); let field_off = self.get_field_offset(struct_name, field_name); self.emit(InstrKind::GetLocal(tmp_slot + field_off)); return Ok(()); } } } } } Ok(()) } fn resolve_member_access(&self, node: NodeId) -> Option<(u32, Type)> { match self.arena.kind(node) { NodeKind::MemberAccess(n) => match self.arena.kind(n.object) { NodeKind::Ident(id) => { let name_str = self.interner.resolve(id.name); let member_str = self.interner.resolve(n.member); let info = self.find_local(name_str)?; if let Type::Struct(sname) = &info.ty { let offset = self.get_field_offset(sname, member_str); let ty = self.get_field_type(sname, member_str); Some((info.slot + offset, ty)) } else { None } } NodeKind::MemberAccess(_) => { let member_str = self.interner.resolve(n.member); let (base_slot, ty) = self.resolve_member_access(n.object)?; if let Type::Struct(sname) = &ty { let offset = self.get_field_offset(sname, member_str); let final_ty = self.get_field_type(sname, member_str); Some((base_slot + offset, final_ty)) } else { None } } _ => None, }, _ => None, } } fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 { if let Some(map) = self.user_struct_field_offsets.get(struct_name) { if let Some(off) = map.get(field_name) { return *off; } } match struct_name { // New `Button` mirrors legacy `ButtonState` offsets "Button" => match field_name { "pressed" => 0, "released" => 1, "down" => 2, "hold_frames" => 3, _ => 0, }, "ButtonState" => match field_name { "pressed" => 0, "released" => 1, "down" => 2, "hold_frames" => 3, _ => 0, }, "Pad" => match field_name { "up" => 0, "down" => 4, "left" => 8, "right" => 12, "a" => 16, "b" => 20, "x" => 24, "y" => 28, "l" => 32, "r" => 36, "start" => 40, "select" => 44, _ => 0, }, "Touch" => match field_name { "f" => 0, "x" => 4, "y" => 5, _ => 0, }, _ => 0, } } fn get_field_type(&self, struct_name: &str, field_name: &str) -> Type { if let Some(map) = self.user_struct_field_types.get(struct_name) { if let Some(ty) = map.get(field_name) { return ty.clone(); } } match struct_name { // Pad's per-button service returns a `Button` in the new API "Pad" => Type::Struct("Button".to_string()), // Field types for `Button` mirror legacy `ButtonState` "Button" => match field_name { "hold_frames" => Type::Bounded, _ => Type::Bool, }, "ButtonState" => match field_name { "hold_frames" => Type::Bounded, _ => Type::Bool, }, "Touch" => match field_name { // Touch.f() now returns a `Button` "f" => Type::Struct("Button".to_string()), _ => Type::Int, }, _ => Type::Int, } } fn lower_call(&mut self, node: NodeId, n: &CallNodeArena) -> Result<(), ()> { match self.arena.kind(n.callee) { NodeKind::Ident(id_node) => { let callee_name = self.interner.resolve(id_node.name).to_string(); // 1. Check for constructor call: TypeName(...) let ctor = self .struct_constructors .get(&callee_name) .and_then(|ctors| ctors.get(&callee_name)) .copied(); if let Some(ctor) = ctor { return self.lower_constructor_call(ctor, &n.args); } if let Some(ctx) = &self.current_type_context { let ctor = self .struct_constructors .get(ctx) .and_then(|ctors| ctors.get(&callee_name)) .copied(); if let Some(ctor) = ctor { return self.lower_constructor_call(ctor, &n.args); } } for arg in &n.args { self.lower_node(*arg)?; } if let Some(func_id) = self.function_ids.get(&callee_name) { self.emit(InstrKind::Call(*func_id, n.args.len() as u32)); Ok(()) } else if let Some(sym) = self.imported_symbols.value_symbols.get(id_node.name) { if let Some(origin) = &sym.origin { if origin.starts_with('@') { // Format: @dep_alias:module_path let parts: Vec<&str> = origin[1..].splitn(2, ':').collect(); if parts.len() == 2 { let dep_alias = parts[0].to_string(); let module_path = parts[1].to_string(); // Compute signature id from symbol type let base_name = self.interner.resolve(sym.name).to_string(); if let Some(ty) = &sym.ty { if let Some(sig) = self.sig_from_pbs_fn(ty) { self.emit(InstrKind::ImportCall { dep_alias, module_path, owner: None, base_name, sig, arg_count: n.args.len() as u32, }); return Ok(()); } } self.error( "E_LOWER_SIGNATURE", format!("Missing or non-function type for imported symbol '{}' to compute signature id", base_name), self.arena.span(n.callee), ); return Err(()); } } } self.error( "E_LOWER_UNSUPPORTED", format!( "Calling symbol '{}' with origin {:?} is not supported yet in v0", callee_name, sym.origin ), self.arena.span(n.callee), ); Err(()) } else { // Try default constructor for struct calls like TypeName(...) if let Some(ctors) = self.struct_constructors.get(&callee_name) { if let Some(ctor) = ctors.get(&callee_name) { return self.lower_constructor_call(*ctor, &n.args); } } let type_sym = self .module_symbols .type_symbols .get(id_node.name) .or_else(|| self.imported_symbols.type_symbols.get(id_node.name)); if let Some(sym) = type_sym { if sym.kind == SymbolKind::Struct { // TODO: handle implicit struct constructor (lower args as field values) return Ok(()); } } // Check for special built-in functions match callee_name.as_str() { "some" | "ok" | "err" => { for arg in &n.args { self.lower_node(*arg)?; } return Ok(()); } _ => {} } self.error( "E_RESOLVE_UNDEFINED", format!("Undefined function '{}'", callee_name), self.arena.span(n.callee), ); Err(()) } } NodeKind::MemberAccess(ma) => { // Special-case: Member access over a call expression that returns a struct from a host contract, // e.g., Pad.a().down — we must: // 1) lower the call (pushing all struct slots), // 2) store it into a temporary local (all slots), // 3) load only the requested field slot back to the stack. if let NodeKind::Call(call_node) = self.arena.kind(ma.object) { // Try to resolve if callee is a host contract method to infer return struct and slots if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) { if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) { let contract_name = self.interner.resolve(obj_id.name); let method_name = self.interner.resolve(inner_ma.member); if let Some(method) = self.contract_registry.get_method(contract_name, method_name) { // Determine slots from the declared return type BEFORE lowering the call let ret_ty = self.convert_pbs_type(&method.return_type); let slots = self.get_type_slots(&ret_ty); let struct_name = match &ret_ty { Type::Struct(s) => s.clone(), _ => String::new() }; // Lower the call first (this will push all return slots from HostCall) self.lower_call(n.callee, &call_node)?; // Allocate a temp local to capture the struct let tmp_slot = self.add_local_to_scope( format!("$tmp_{}", self.get_next_local_slot()), ret_ty.clone(), ); // Store all slots (top of stack has the last slot). We must store in reverse order. for i in (0..slots).rev() { self.emit(InstrKind::SetLocal(tmp_slot + i)); } // Compute field offset/type and load only that field let field_name = self.interner.resolve(ma.member); let field_off = self.get_field_offset(&struct_name, field_name); let _field_ty = self.get_field_type(&struct_name, field_name); self.emit(InstrKind::GetLocal(tmp_slot + field_off)); return Ok(()); } } } } // Check if it's a constructor alias: TypeName.Alias(...) let ctor = if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); let member_name = self.interner.resolve(ma.member); self.struct_constructors .get(obj_name) .and_then(|ctors| ctors.get(member_name)) .copied() } else { None }; if let Some(ctor) = ctor { return self.lower_constructor_call(ctor, &n.args); } // Check for Pad.any() let member_name = self.interner.resolve(ma.member); if member_name == "any" { if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); if let Some(info) = self.find_local(obj_name) { if let Type::Struct(sname) = &info.ty { if sname == "Pad" { self.lower_pad_any(info.slot); return Ok(()); } } } } } // Host contract static calls: Contract.method(...) if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); let is_local = self.find_local(obj_name).is_some(); if !is_local { // Check type symbol (current or imported) for a host contract let sym_opt = self .module_symbols .type_symbols .get(obj_id.name) .or_else(|| self.imported_symbols.type_symbols.get(obj_id.name)); if let Some(sym) = sym_opt { // Suporte a chamada estática de service: Service.method(...) if sym.kind == SymbolKind::Service { let full_name = format!("{}.{}", obj_name, member_name); for arg in &n.args { self.lower_node(*arg)?; } if let Some(func_id) = self.function_ids.get(&full_name).cloned() { self.emit(InstrKind::Call(func_id, n.args.len() as u32)); } else { // Usar o binding real do import para este Service (ex.: Log -> (sdk, log)) let obj_name_str = obj_name.to_string(); if let Some((dep_alias, module_path)) = self.import_bindings.get(&obj_name_str).cloned() { // Determine the canonical module origin used when we synthesized dependency symbols // Only supported style: "@alias:module" let canonical_origin = format!("@{}:{}", dep_alias, module_path); // Find candidates among imported value symbols matching: // - name in the new canonical form: "Service.member#sigN" (prefix match on qualified base) // - origin equals the bound synthetic module path let mut candidates: Vec<&Symbol> = Vec::new(); for list in self.imported_symbols.value_symbols.symbols.values() { for s in list { let sname = self.interner.resolve(s.name); // Accept both canonical qualified form and legacy simple form for compatibility let qualified_base = format!("{}.{}", obj_name, member_name); let matches_qualified = sname.starts_with(&format!("{}#sig", &qualified_base)); let matches_legacy = sname.starts_with(&format!("{}#sig", member_name)); if matches_qualified || matches_legacy { if let Some(orig) = &s.origin { if *orig == canonical_origin { candidates.push(s); } } } } } // If multiple candidates, try to disambiguate by arity (exact arg count) let mut filtered: Vec<&Symbol> = candidates; if filtered.len() > 1 { let argc = n.args.len(); filtered = filtered.into_iter().filter(|s| { if let Some(PbsType::Function { params, .. }) = &s.ty { params.len() == argc } else { false } }).collect(); } let sig_opt = if filtered.len() == 1 { filtered[0] .ty .as_ref() .and_then(|t| self.sig_from_pbs_fn(t)) } else if filtered.is_empty() { self.error( "E_OVERLOAD_NOT_FOUND", format!( "No matching overload for imported service method '{}.{}' with {} argument(s)", obj_name, member_name, n.args.len() ), self.arena.span(n.callee), ); return Err(()); } else { // Ambiguous within the bound module context; emit deterministic error self.error( "E_OVERLOAD_AMBIGUOUS", format!( "Ambiguous imported service method '{}.{}' ({} candidates in module '{}')", obj_name, member_name, filtered.len(), module_path ), self.arena.span(n.callee), ); return Err(()); }; if let Some(sig) = sig_opt { let base_name = member_name.to_string(); self.emit(InstrKind::ImportCall { dep_alias, module_path, owner: Some(obj_name.to_string()), base_name, sig, arg_count: n.args.len() as u32, }); } else { // Fallback: attempt to compute from service method type if exposed in type_constants or error deterministically self.error( "E_LOWER_SIGNATURE", format!( "Unable to determine signature for imported service method '{}.{}' (missing type info)", obj_name, member_name ), self.arena.span(n.callee), ); return Err(()); } } else { // Sem binding de import conhecido: erro claro de serviço não importado self.error( "E_RESOLVE_UNDEFINED", format!("Undefined service member '{}.{}' (service not imported)", obj_name, member_name), self.arena.span(n.callee), ); return Err(()); } } return Ok(()); } if sym.kind == SymbolKind::Contract && sym.is_host { // 1) Caminho padrão via ContractRegistry if self.contract_registry.get_method(obj_name, member_name).is_some() { // Extrai valores necessários sem manter o empréstimo do registry vivo let (id, return_ty) = { let method = self.contract_registry.get_method(obj_name, member_name).unwrap(); (method.id, method.return_type.clone()) }; // Lower arguments primeiro for arg in &n.args { self.lower_node(*arg)?; } // Compute return slots a partir do tipo retornado let return_slots = match &return_ty { PbsType::Void | PbsType::None => 0, PbsType::Struct(name) => { // Prefer builtin struct slots, then fallback to struct_slots map, default 1 if let Some(bi) = self.get_builtin_struct_slots(name) { bi } else { *self.struct_slots.get(name).unwrap_or(&1) } } other => { let ty = self.convert_pbs_type(other); self.get_type_slots(&ty) } }; self.emit(InstrKind::HostCall(id, return_slots)); return Ok(()); } // (Açúcar de Log movido para antes do branch host) } } } } // Check for .raw() if member_name == "raw" { self.lower_node(ma.object)?; return Ok(()); } // Check for Color.rgb if member_name == "rgb" { if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { if self.interner.resolve(obj_id.name) == "Color" { if n.args.len() == 3 { // Try to get literal values for r, g, b let mut literals = Vec::new(); for arg in &n.args { if let NodeKind::IntLit(lit) = self.arena.kind(*arg) { literals.push(Some(lit.value)); } else if let NodeKind::BoundedLit(lit) = self.arena.kind(*arg) { literals.push(Some(lit.value as i64)); } else { literals.push(None); } } if let (Some(r), Some(g), Some(b)) = (literals[0], literals[1], literals[2]) { let r5 = (r & 0xFF) >> 3; let g6 = (g & 0xFF) >> 2; let b5 = (b & 0xFF) >> 3; let rgb565 = (r5 << 11) | (g6 << 5) | b5; self.emit(InstrKind::PushBounded(rgb565 as u32)); return Ok(()); } else { self.error( "E_LOWER_UNSUPPORTED", "Color.rgb only supports literal arguments in this version" .to_string(), self.arena.span(node), ); return Err(()); } } } } } // Tentativa de chamada de método de instância: obj.method(...) // 1) Descobrir tipo do objeto e slot let mut obj_info: Option<(u32, Type)> = None; match self.arena.kind(ma.object) { NodeKind::Ident(id) => { let obj_name = self.interner.resolve(id.name); if let Some(info) = self.find_local(obj_name) { obj_info = Some((info.slot, info.ty.clone())); } } NodeKind::MemberAccess(_) => { if let Some(info) = self.resolve_member_access(ma.object) { obj_info = Some(info); } } _ => {} } if let Some((base_slot, ty)) = obj_info.clone() { if let Type::Struct(ref sname) = ty { let member_name = self.interner.resolve(ma.member); let full_name = format!("{}.{}", sname, member_name); let func_id_opt = self.function_ids.get(&full_name).cloned(); if let Some(func_id) = func_id_opt { // Empilha self (todas as slots da instância) let self_slots = self.struct_slots.get(sname).cloned().unwrap_or(1); for i in 0..self_slots { self.emit(InstrKind::GetLocal(base_slot + i)); } // Empilha argumentos for arg in &n.args { self.lower_node(*arg)?; } let arg_slots = n.args.len() as u32; self.emit(InstrKind::Call(func_id, self_slots + arg_slots)); return Ok(()); } } } // Fallback original for arg in &n.args { self.lower_node(*arg)?; } if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); let is_host_contract = self .module_symbols .type_symbols .get(obj_id.name) .map(|sym| sym.kind == SymbolKind::Contract && sym.is_host) .unwrap_or(false); let is_shadowed = self.find_local(obj_name).is_some(); if is_host_contract && !is_shadowed { if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { let ir_ty = self.convert_pbs_type(&method.return_type); let return_slots = self.get_type_slots(&ir_ty); self.emit(InstrKind::HostCall(method.id, return_slots)); return Ok(()); } else { self.error( "E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_name, member_name), self.arena.span(n.callee), ); return Err(()); } } } self.error( "E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), self.arena.span(n.callee), ); Err(()) } _ => { for arg in &n.args { self.lower_node(*arg)?; } self.error( "E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), self.arena.span(n.callee), ); Err(()) } } } fn lower_method_function(&mut self, type_name: &str, node: NodeId) -> Result { let n = match self.arena.kind(node) { NodeKind::FnDecl(n) => n, _ => return Err(()), }; let full_name = format!("{}.{}", type_name, self.interner.resolve(n.name)); let func_id = match self.function_ids.get(&full_name) { Some(id) => *id, None => { self.error( "E_LOWER_UNSUPPORTED", format!("Missing function id for method '{}'", full_name), self.arena.span(node), ); return Err(()); } }; self.next_block_id = 0; self.local_vars = vec![HashMap::new()]; self.max_slots_used = 0; let mut params = Vec::new(); let mut local_types = HashMap::new(); let mut param_slots = 0u32; // Guardar contexto anterior let prev_ctx = self.current_type_context.clone(); let prev_self_slot = self.method_self_slot.take(); self.current_type_context = Some(type_name.to_string()); for param in &n.params { let ty = self.lower_type_node(param.ty); let slots = self.get_type_slots(&ty); let param_name = self.interner.resolve(param.name).to_string(); params.push(Param { name: param_name.clone(), ty: ty.clone(), }); // Slot inicial deste parâmetro let this_param_start = param_slots; self.local_vars[0].insert( self.interner.resolve(param.name).to_string(), LocalInfo { slot: this_param_start, ty: ty.clone(), }, ); for i in 0..slots { local_types.insert(this_param_start + i, ty.clone()); } if self.interner.resolve(param.name) == "self" { self.method_self_slot = Some(this_param_start); } param_slots += slots; } self.max_slots_used = param_slots; let ret_ty = if let Some(ret) = n.ret { self.lower_type_node(ret) } else { Type::Void }; let return_slots = self.get_type_slots(&ret_ty); // Build Signature and intern to SigId let func_sig = Signature { params: params.iter().map(|p| p.ty.clone()).collect(), return_type: ret_ty.clone(), }; let sig_id = { let mut interner = global_signature_interner().lock().unwrap(); interner.intern(func_sig) }; let func = Function { id: func_id, name: full_name, sig: sig_id, params, return_type: ret_ty, blocks: Vec::new(), local_types, param_slots: param_slots as u16, local_slots: 0, return_slots: return_slots as u16, }; self.current_function = Some(func); self.start_block(); self.lower_node(n.body)?; if let Some(mut block) = self.current_block.take() { if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) { block.terminator = Terminator::Return; } if let Some(func) = &mut self.current_function { func.blocks.push(block); } } let mut final_func = self.current_function.take().unwrap(); final_func.local_slots = (self.max_slots_used - param_slots) as u16; // Restaurar contexto self.current_type_context = prev_ctx; self.method_self_slot = prev_self_slot; Ok(final_func) } fn lower_constructor_call(&mut self, ctor: NodeId, args: &[NodeId]) -> Result<(), ()> { let ctor_id = ctor; let ctor = match self.arena.kind(ctor) { NodeKind::ConstructorDecl(ctor) => ctor, _ => return Err(()), }; if args.len() != ctor.params.len() { self.error( "E_TYPE_MISMATCH", format!( "Expected {} arguments, found {}", ctor.params.len(), args.len() ), self.arena.span(ctor_id), ); return Err(()); } self.local_vars.push(HashMap::new()); let mut param_slots = Vec::new(); for param in &ctor.params { let ty = self.lower_type_node(param.ty); let slot = self.add_local_to_scope(self.interner.resolve(param.name).to_string(), ty.clone()); param_slots.push((slot, ty)); } for (index, arg) in args.iter().enumerate() { if let Some((slot, ty)) = param_slots.get(index) { self.lower_node(*arg)?; let slots = self.get_type_slots(ty); for i in (0..slots).rev() { self.emit(InstrKind::SetLocal(slot + i)); } } } for init in &ctor.initializers { self.lower_node(*init)?; } self.local_vars.pop(); Ok(()) } fn lower_pad_any(&mut self, base_slot: u32) { for i in 0..12 { let btn_base = base_slot + (i * 4); self.emit(InstrKind::GetLocal(btn_base)); // pressed self.emit(InstrKind::GetLocal(btn_base + 1)); // released self.emit(InstrKind::Or); self.emit(InstrKind::GetLocal(btn_base + 2)); // down self.emit(InstrKind::Or); if i > 0 { self.emit(InstrKind::Or); } } } fn lower_binary(&mut self, node: NodeId, n: &BinaryNodeArena) -> Result<(), ()> { self.lower_node(n.left)?; self.lower_node(n.right)?; match n.op.as_str() { "+" => self.emit(InstrKind::Add), "-" => self.emit(InstrKind::Sub), "*" => self.emit(InstrKind::Mul), "/" => self.emit(InstrKind::Div), "==" => self.emit(InstrKind::Eq), "!=" => self.emit(InstrKind::Neq), "<" => self.emit(InstrKind::Lt), "<=" => self.emit(InstrKind::Lte), ">" => self.emit(InstrKind::Gt), ">=" => self.emit(InstrKind::Gte), "&&" => self.emit(InstrKind::And), "||" => self.emit(InstrKind::Or), _ => { self.error( "E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), self.arena.span(node), ); return Err(()); } } Ok(()) } fn lower_unary(&mut self, node: NodeId, n: &UnaryNodeArena) -> Result<(), ()> { self.lower_node(n.expr)?; match n.op.as_str() { "-" => self.emit(InstrKind::Neg), "!" => self.emit(InstrKind::Not), _ => { self.error( "E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), self.arena.span(node), ); return Err(()); } } Ok(()) } fn lower_if_expr(&mut self, _node: NodeId, n: &IfExprNodeArena) -> 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.terminate(Terminator::JumpIfFalse { target: else_id, else_target: then_id, }); // Then block self.start_block_with_id(then_id); 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.terminate(Terminator::Jump(merge_id)); // Merge block self.start_block_with_id(merge_id); Ok(()) } fn lower_when_expr(&mut self, node: NodeId, n: &WhenExprNodeArena) -> Result<(), ()> { if n.arms.is_empty() { return Ok(()); } let merge_id = self.reserve_block_id(); for (idx, arm_id) in n.arms.iter().enumerate() { let arm = match self.arena.kind(*arm_id) { NodeKind::WhenArm(arm) => arm, _ => { self.error( "E_LOWER_UNSUPPORTED", "Expected when arm".to_string(), self.arena.span(node), ); return Err(()); } }; let body_id = self.reserve_block_id(); let next_cond_id = if idx + 1 < n.arms.len() { self.reserve_block_id() } else { merge_id }; self.lower_node(arm.cond)?; self.terminate(Terminator::JumpIfFalse { target: next_cond_id, else_target: body_id, }); self.start_block_with_id(body_id); self.lower_node(arm.body)?; self.terminate(Terminator::Jump(merge_id)); if idx + 1 < n.arms.len() { self.start_block_with_id(next_cond_id); } } self.start_block_with_id(merge_id); Ok(()) } fn lower_type_node(&mut self, node: NodeId) -> Type { match self.arena.kind(node) { NodeKind::TypeName(n) => match self.interner.resolve(n.name) { "int" => Type::Int, "bounded" => Type::Bounded, "float" => Type::Float, "bool" => Type::Bool, "string" => Type::String, "void" => Type::Void, "this" => { if let Some(ctx) = &self.current_type_context { Type::Struct(ctx.clone()) } else { Type::Void } } _ => { if let Some(sym) = self .module_symbols .type_symbols .get(n.name) .or_else(|| self.imported_symbols.type_symbols.get(n.name)) { let name = self.interner.resolve(n.name).to_string(); match sym.kind { SymbolKind::Struct => Type::Struct(name), SymbolKind::Service => Type::Service(name), SymbolKind::Contract => Type::Contract(name), SymbolKind::ErrorType => Type::ErrorType(name), _ => Type::Struct(name), } } else { Type::Struct(self.interner.resolve(n.name).to_string()) } } }, NodeKind::TypeApp(ta) => { let base_name = self.interner.resolve(ta.base); if base_name == "array" { let elem_ty = self.lower_type_node(ta.args[0]); let size = if ta.args.len() > 1 { if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) { il.value as u32 } else { 0 } } else { 0 }; Type::Array(Box::new(elem_ty), size) } else if base_name == "optional" { Type::Optional(Box::new(self.lower_type_node(ta.args[0]))) } else if base_name == "result" { Type::Result( Box::new(self.lower_type_node(ta.args[0])), Box::new(self.lower_type_node(ta.args[1])) ) } else { Type::Struct(format!("{}<{}>", base_name, ta.args.len())) } } _ => Type::Void, } } fn start_block(&mut self) { let id = self.reserve_block_id(); self.start_block_with_id(id); } fn start_block_with_id(&mut self, id: u32) { if let Some(block) = self.current_block.take() { if let Some(func) = &mut self.current_function { func.blocks.push(block); } } self.current_block = Some(Block { id, instrs: Vec::new(), terminator: Terminator::Return, // Default, will be overwritten }); } fn reserve_block_id(&mut self) -> u32 { let id = self.next_block_id; self.next_block_id += 1; id } fn emit(&mut self, kind: InstrKind) { if let Some(block) = &mut self.current_block { block.instrs.push(Instr::new(kind, self.current_span.clone())); } } fn terminate(&mut self, terminator: Terminator) { if let Some(mut block) = self.current_block.take() { block.terminator = terminator; if let Some(func) = &mut self.current_function { func.blocks.push(block); } } } fn get_next_local_slot(&self) -> u32 { self.local_vars.iter().flat_map(|s| s.values()).map(|info| self.get_type_slots(&info.ty)).sum() } fn add_local_to_scope(&mut self, name: String, ty: Type) -> u32 { let slot = self.get_next_local_slot(); let slots = self.get_type_slots(&ty); if slot + slots > self.max_slots_used { self.max_slots_used = slot + slots; } self.local_vars.last_mut().unwrap().insert(name, LocalInfo { slot, ty: ty.clone() }); if let Some(func) = &mut self.current_function { for i in 0..slots { func.local_types.insert(slot + i, ty.clone()); } } slot } fn find_local(&self, name: &str) -> Option { for scope in self.local_vars.iter().rev() { if let Some(info) = scope.get(name) { return Some(info.clone()); } } None } fn get_builtin_struct_slots(&self, name: &str) -> Option { match name { "Pad" => Some(48), "ButtonState" => Some(4), "Color" => Some(1), "Touch" => Some(6), _ => None, } } fn get_type_slots(&self, ty: &Type) -> u32 { match ty { Type::Void => 0, Type::Struct(name) => { if let Some(slots) = self.get_builtin_struct_slots(name) { slots } else { self.struct_slots.get(name).cloned().unwrap_or(1) } } Type::Array(_, size) => *size, _ => 1, } } fn convert_pbs_type(&self, ty: &PbsType) -> Type { match ty { PbsType::Int => Type::Int, PbsType::Float => Type::Float, PbsType::Bool => Type::Bool, PbsType::String => Type::String, PbsType::Void => Type::Void, PbsType::None => Type::Void, PbsType::Bounded => Type::Bounded, PbsType::Optional(inner) => Type::Optional(Box::new(self.convert_pbs_type(inner))), PbsType::Result(ok, err) => Type::Result( Box::new(self.convert_pbs_type(ok)), Box::new(self.convert_pbs_type(err)), ), PbsType::Struct(name) => Type::Struct(name.clone()), PbsType::Service(name) => Type::Service(name.clone()), PbsType::Contract(name) => Type::Contract(name.clone()), PbsType::ErrorType(name) => Type::ErrorType(name.clone()), PbsType::Function { params, return_type } => Type::Function { params: params.iter().map(|p| self.convert_pbs_type(p)).collect(), return_type: Box::new(self.convert_pbs_type(return_type)), }, } } } #[cfg(test)] mod tests { use super::*; use crate::common::spans::FileId; use crate::frontends::pbs::collector::SymbolCollector; use crate::frontends::pbs::parser::Parser; use crate::frontends::pbs::symbols::ModuleSymbols; use prometeu_analysis::NameInterner; struct NullProvider; impl crate::frontends::pbs::resolver::ModuleProvider for NullProvider { fn get_module_symbols(&self, _from_path: &str) -> Option<&ModuleSymbols> { None } } fn lower_program(code: &str) -> Program { let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); lowerer.lower_file(parsed.root, "test").expect("Lowering failed") } #[test] fn test_basic_lowering() { let code = " fn add(a: int, b: int): int { return a + b; } fn main() { let x = add(10, 20); } "; let program = lower_program(code); // Verify program structure assert_eq!(program.modules.len(), 1); let module = &program.modules[0]; assert_eq!(module.functions.len(), 2); let add_func = module.functions.iter().find(|f| f.name == "add").unwrap(); assert_eq!(add_func.params.len(), 2); assert_eq!(add_func.return_type, Type::Int); // Verify blocks assert!(add_func.blocks.len() >= 1); let first_block = &add_func.blocks[0]; // Check for Add instruction assert!(first_block.instrs.iter().any(|i| matches!(i.kind, InstrKind::Add))); } #[test] fn test_binary_ops_lowering() { let code = " fn main() { let a = 1 + 2; let b = 3 - 1; let c = 2 * 3; let d = 8 / 2; let e = 1 == 1; let f = 1 < 2; let g = 2 > 1; let h = true && false; let i = true || false; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Add))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Sub))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Mul))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Div))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Eq))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Lt))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Gt))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::And))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Or))); } #[test] fn test_unary_ops_lowering() { let code = " fn main() { let a = -1; let b = !true; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Neg))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Not))); } #[test] fn test_control_flow_lowering() { let code = " fn max(a: int, b: int): int { if (a > b) { return a; } else { return b; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let max_func = &program.modules[0].functions[0]; // Should have multiple blocks for if-else assert!(max_func.blocks.len() >= 3); } #[test] fn test_if_expr_lowering() { let code = " fn main(a: int, b: int) { if (a > b) { let x = a; } else { let y = b; } } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect(); assert!(terminators.iter().any(|t| matches!(t, Terminator::JumpIfFalse { .. }))); assert!(terminators.iter().any(|t| matches!(t, Terminator::Jump(_)))); assert!(main_func.blocks.len() >= 3); } #[test] fn test_when_expr_lowering() { let code = " fn main(x: int) { when { x == 0 -> { return; }, x == 1 -> { return; } }; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect(); assert!(terminators.iter().any(|t| matches!(t, Terminator::JumpIfFalse { .. }))); assert!(main_func.blocks.len() >= 5); } #[test] fn test_lower_type_node() { let code = " service MyService { fn ping(): void; } declare contract MyContract host {} declare error MyError {} declare struct Point(x: int) fn main( s: MyService, c: MyContract, e: MyError, p: Point, o: optional, r: result, a: int[3] ) {} "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let params: Vec<_> = main_func.params.iter().map(|p| p.ty.clone()).collect(); assert_eq!(params[0], Type::Service("MyService".to_string())); assert_eq!(params[1], Type::Contract("MyContract".to_string())); assert_eq!(params[2], Type::ErrorType("MyError".to_string())); assert_eq!(params[3], Type::Struct("Point".to_string())); assert_eq!( params[4], Type::Optional(Box::new(Type::Int)) ); assert_eq!( params[5], Type::Result( Box::new(Type::Int), Box::new(Type::String) ) ); assert_eq!( params[6], Type::Array(Box::new(Type::Int), 3) ); } #[test] fn test_call_lowering() { let code = " fn add(a: int, b: int): int { return a + b; } fn main() { let x = add(1, 2); } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs .iter() .any(|i| matches!(i.kind, InstrKind::Call(_, 2)))); } #[test] fn test_host_call_lowering() { let code = " declare contract Gfx host {} fn main() { Gfx.clear(Color.WHITE); } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs .iter() .any(|i| matches!(i.kind, InstrKind::HostCall(_, _)))); assert!(instrs .iter() .any(|i| matches!(i.kind, InstrKind::PushBounded(_)))); } #[test] fn test_member_access_lowering() { let code = " declare contract Input host {} fn main() { let p: Pad = Input.pad(); let b: ButtonState = p.a; let d: bool = p.a.down; let c: Color = Color.WHITE; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs .iter() .any(|i| matches!(i.kind, InstrKind::GetLocal(16)))); assert!(instrs .iter() .any(|i| matches!(i.kind, InstrKind::GetLocal(18)))); assert!(instrs .iter() .any(|i| matches!(i.kind, InstrKind::PushBounded(_)))); } #[test] fn test_constructor_call_lowering() { let code = " declare struct Vec2(x: int, y: int) [ (x: int, y: int): (x, y) as default { } (s: int): (s, s) as square { } ] fn main() { let a = Vec2(1, 2); let b = Vec2.square(3); } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); let push_consts = instrs .iter() .filter(|i| matches!(i.kind, InstrKind::PushConst(_))) .count(); assert_eq!(push_consts, 3); assert!(!instrs.iter().any(|i| matches!(i.kind, InstrKind::Call(_, _)))); assert!(!instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(_, _)))); } #[test] fn test_hip_lowering() { let code = " fn test_hip() { let g = alloc int; mutate g as x { let y = x + 1; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Alloc { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate))); } #[test] fn test_hip_lowering_golden() { let code = " fn test_hip() { let g = alloc int; mutate g as x { let y = x + 1; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .unwrap(); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let json = serde_json::to_string_pretty(&program).unwrap(); // Assertions for PR-20 HIP Semantics: // 1. Gate is preserved in a local (SetLocal(1) after GetLocal(0)) // 2. BeginMutate uses that local (BeginMutate { gate: 1 }) // 3. EndMutate exists // 4. No ReadGate/WriteGate (they were removed from Instr) assert!(json.contains("\"SetLocal\": 1"), "Gate should be stored in a local"); assert!(json.contains("\"BeginMutate\""), "Should have BeginMutate"); assert!(json.contains("\"gate\": 1"), "BeginMutate should use the gate local"); assert!(json.contains("\"EndMutate\""), "Should have EndMutate"); assert!(!json.contains("ReadGate"), "ReadGate should be gone"); assert!(!json.contains("WriteGate"), "WriteGate should be gone"); } #[test] fn test_hip_semantics_distinction() { let code = " fn test_hip(g: int) { peek g as p { let x = p; } borrow g as b { let y = b; } mutate g as m { let z = m; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); // Assert distinct Core IR instruction sequences assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginPeek { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndPeek))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginBorrow { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndBorrow))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate))); } #[test] fn test_host_contract_call_lowering() { let code = " declare contract Gfx host {} declare contract LogHost host {} fn main() { Gfx.clear(0); LogHost.write(2, \"Hello\"); } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); // Gfx.clear -> 0x1010 assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(0x1010, 0)))); // LogHost.write -> 0x5001 (registry updated to LogHost) assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(0x5001, _)))); } #[test] fn test_contract_call_without_host_lowering() { let code = " declare contract Gfx {} fn main() { Gfx.clear(0); } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED")); } #[test] fn test_shadowed_contract_call_lowering() { let code = " declare contract Gfx host {} fn main() { let Gfx = 10; Gfx.clear(0); } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED")); } #[test] fn test_invalid_contract_call_lowering() { let code = " declare contract Gfx host {} fn main() { Gfx.invalidMethod(0); } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); } #[test] fn test_alloc_struct_slots() { let code = " declare struct Vec3(x: int, y: int, z: int) fn main() { let v = alloc Vec3; } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let alloc = instrs.iter().find_map(|i| { if let InstrKind::Alloc { ty, slots } = &i.kind { Some((ty, slots)) } else { None } }).expect("Should have Alloc instruction"); assert_eq!(*alloc.1, 3, "Vec3 should have 3 slots"); assert!(alloc.0.0 > 0, "Should have a valid TypeId"); } #[test] fn test_alloc_array_slots() { let code = " fn main() { let a = alloc array[10b]; } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let alloc = instrs.iter().find_map(|i| { if let InstrKind::Alloc { ty, slots } = &i.kind { Some((ty, slots)) } else { None } }).expect("Should have Alloc instruction"); assert_eq!(*alloc.1, 10, "array[10b] should have 10 slots"); assert!(alloc.0.0 > 0, "Should have a valid TypeId"); } #[test] fn test_alloc_primitive_slots() { let code = " fn main() { let x = alloc int; } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let alloc = instrs.iter().find_map(|i| { if let InstrKind::Alloc { ty, slots } = &i.kind { Some((ty, slots)) } else { None } }).expect("Should have Alloc instruction"); 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 interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); 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 interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let provider = NullProvider; let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'"))); } }