2026-03-24 13:40:25 +00:00

1630 lines
62 KiB
Rust

use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
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::types::PbsType;
use crate::ir_core;
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
use crate::ir_core::{Block, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type};
use prometeu_analysis::NameInterner;
use std::collections::HashMap;
#[derive(Clone)]
struct LocalInfo {
slot: u32,
ty: Type,
}
pub struct Lowerer<'a> {
module_symbols: &'a ModuleSymbols,
imported_symbols: &'a ModuleSymbols,
interner: &'a NameInterner,
program: Program,
current_function: Option<Function>,
current_block: Option<Block>,
next_block_id: u32,
next_func_id: u32,
next_type_id: u32,
local_vars: Vec<HashMap<String, LocalInfo>>,
function_ids: HashMap<String, FunctionId>,
type_ids: HashMap<String, TypeId>,
struct_slots: HashMap<String, u32>,
struct_constructors: HashMap<String, HashMap<String, ConstructorDeclNode>>,
type_constants: HashMap<String, HashMap<String, Node>>,
current_type_context: Option<String>,
contract_registry: ContractRegistry,
diagnostics: Vec<Diagnostic>,
max_slots_used: u32,
current_span: Option<Span>,
}
impl<'a> Lowerer<'a> {
pub fn new(
module_symbols: &'a ModuleSymbols,
imported_symbols: &'a ModuleSymbols,
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);
struct_slots.insert("Pad".to_string(), 48);
struct_slots.insert("Touch".to_string(), 6);
Self {
module_symbols,
imported_symbols,
interner,
program: Program {
const_pool: ir_core::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,
contract_registry: ContractRegistry::new(),
diagnostics: Vec::new(),
max_slots_used: 0,
current_span: None,
}
}
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 {
let id = FunctionId(self.next_func_id);
self.next_func_id += 1;
self.function_ids
.insert(self.interner.resolve(n.name).to_string(), id);
}
if let Node::TypeDecl(n) = decl {
let id = TypeId(self.next_type_id);
self.next_type_id += 1;
self.type_ids
.insert(self.interner.resolve(n.name).to_string(), id);
}
}
// Second pre-scan: calculate struct slots (recursive)
let mut struct_nodes = HashMap::new();
for decl in &file.decls {
if let Node::TypeDecl(n) = decl {
if n.type_kind == "struct" {
struct_nodes.insert(self.interner.resolve(n.name).to_string(), n);
}
}
}
let mut changed = true;
while changed {
changed = false;
for (name, node) in &struct_nodes {
if !self.struct_slots.contains_key(name) {
let mut slots = 0;
let mut all_known = true;
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 Node::TypeDecl(n) = decl {
let type_name = self.interner.resolve(n.name).to_string();
let mut constants = HashMap::new();
for c in &n.constants {
constants.insert(self.interner.resolve(c.name).to_string(), *c.value.clone());
}
self.type_constants.insert(type_name.clone(), constants);
let mut ctors = HashMap::new();
// Default constructor: TypeName(...)
if n.type_kind == "struct" {
let mut params = Vec::new();
let mut initializers = Vec::new();
for p in &n.params {
params.push(p.clone());
initializers.push(Node::Ident(IdentNode {
span: p.span,
name: p.name.clone(),
}));
}
let default_ctor = ConstructorDeclNode {
span: n.span,
params,
initializers,
name: n.name,
body: Box::new(Node::Block(BlockNode {
span: n.span,
stmts: Vec::new(),
tail: None,
})),
};
ctors.insert(type_name.clone(), default_ctor);
}
for ctor in &n.constructors {
ctors.insert(self.interner.resolve(ctor.name).to_string(), ctor.clone());
}
self.struct_constructors.insert(type_name, ctors);
}
}
let mut module = Module {
name: module_name.to_string(),
functions: Vec::new(),
};
for decl in &file.decls {
match decl {
Node::FnDecl(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
}
}
self.program.modules.push(module);
Ok(self.program)
}
fn lower_function(&mut self, n: &FnDeclNode) -> Result<Function, ()> {
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);
let func = Function {
id: func_id,
name: func_name,
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_node(&mut self, node: &Node) -> Result<(), ()> {
let old_span = self.current_span;
self.current_span = Some(node.span());
let res = match node {
Node::Block(n) => self.lower_block(n),
Node::LetStmt(n) => self.lower_let_stmt(n),
Node::ExprStmt(n) => self.lower_node(&n.expr),
Node::ReturnStmt(n) => self.lower_return_stmt(n),
Node::IntLit(n) => {
let id = self.program.const_pool.add_int(n.value);
self.emit(InstrKind::PushConst(id));
Ok(())
}
Node::FloatLit(n) => {
let id = self.program.const_pool.add_float(n.value);
self.emit(InstrKind::PushConst(id));
Ok(())
}
Node::StringLit(n) => {
let id = self.program.const_pool.add_string(n.value.clone());
self.emit(InstrKind::PushConst(id));
Ok(())
}
Node::BoundedLit(n) => {
self.emit(InstrKind::PushBounded(n.value));
Ok(())
}
Node::Ident(n) => self.lower_ident(n),
Node::MemberAccess(n) => self.lower_member_access(n),
Node::Call(n) => self.lower_call(n),
Node::Binary(n) => self.lower_binary(n),
Node::Unary(n) => self.lower_unary(n),
Node::IfExpr(n) => self.lower_if_expr(n),
Node::Alloc(n) => self.lower_alloc(n),
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(())
}
};
self.current_span = old_span;
res
}
fn lower_alloc(&mut self, n: &AllocNode) -> 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: &Node) -> Result<(TypeId, u32), ()> {
match node {
Node::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))
}
Node::TypeApp(ta) if self.interner.resolve(ta.base) == "array" => {
let size = if ta.args.len() > 1 {
if let Node::IntLit(il) = &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: {:?}", node), node.span());
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, n: &PeekNode) -> 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, n: &BorrowNode) -> 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, n: &MutateNode) -> 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, n: &BlockNode) -> Result<(), ()> {
self.local_vars.push(HashMap::new());
for stmt in &n.stmts {
self.lower_node(stmt)?;
}
if let Some(tail) = &n.tail {
self.lower_node(tail)?;
}
self.local_vars.pop();
Ok(())
}
fn lower_let_stmt(&mut self, n: &LetStmtNode) -> 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 Node::Call(call) = &*n.init {
if let Node::MemberAccess(ma) = &*call.callee {
if let Node::Ident(obj) = &*ma.object {
let obj_name = self.interner.resolve(obj.name);
let member_name = self.interner.resolve(ma.member);
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 { 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, n: &ReturnStmtNode) -> Result<(), ()> {
if let Some(expr) = &n.expr {
self.lower_node(expr)?;
}
self.terminate(Terminator::Return);
Ok(())
}
fn lower_ident(&mut self, n: &IdentNode) -> 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 {
// 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),
n.span,
);
Err(())
} else {
self.error(
"E_RESOLVE_UNDEFINED",
format!("Undefined identifier '{}'", name_str),
n.span,
);
Err(())
}
}
}
fn lower_member_access(&mut self, n: &MemberAccessNode) -> Result<(), ()> {
if let Node::Ident(id) = &*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(n) {
let slots = self.get_type_slots(&ty);
for i in 0..slots {
self.emit(InstrKind::GetLocal(slot + i));
}
return Ok(());
}
Ok(())
}
fn resolve_member_access(&self, n: &MemberAccessNode) -> Option<(u32, Type)> {
match &*n.object {
Node::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 }
}
Node::MemberAccess(inner) => {
let member_str = self.interner.resolve(n.member);
let (base_slot, ty) = self.resolve_member_access(inner)?;
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
}
}
fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 {
match struct_name {
"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 {
match struct_name {
"Pad" => Type::Struct("ButtonState".to_string()),
"ButtonState" => match field_name {
"hold_frames" => Type::Bounded,
_ => Type::Bool,
},
"Touch" => match field_name {
"f" => Type::Struct("ButtonState".to_string()),
_ => Type::Int,
},
_ => Type::Int,
}
}
fn lower_call(&mut self, n: &CallNode) -> Result<(), ()> {
match &*n.callee {
Node::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))
.cloned();
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))
.cloned();
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();
self.emit(InstrKind::ImportCall(
dep_alias,
module_path,
self.interner.resolve(sym.name).to_string(),
n.args.len() as u32,
));
return Ok(());
}
}
}
self.error(
"E_LOWER_UNSUPPORTED",
format!(
"Calling symbol '{}' with origin {:?} is not supported yet in v0",
callee_name,
sym.origin
),
id_node.span,
);
Err(())
} else {
// Check for special built-in functions
match callee_name.as_str() {
"some" | "ok" | "err" => {
return Ok(());
}
_ => {}
}
self.error(
"E_RESOLVE_UNDEFINED",
format!("Undefined function '{}'", callee_name),
id_node.span,
);
Err(())
}
}
Node::MemberAccess(ma) => {
// Check if it's a constructor alias: TypeName.Alias(...)
let ctor = if let Node::Ident(obj_id) = &*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))
.cloned()
} 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 Node::Ident(obj_id) = &*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 Node::Ident(obj_id) = &*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 {
if sym.kind == SymbolKind::Contract && sym.is_host {
// Lower arguments first to avoid borrowing conflicts
for arg in &n.args {
self.lower_node(arg)?;
}
if let Some(method) = self.contract_registry.get_method(obj_name, member_name) {
let id = method.id;
let return_slots = if matches!(method.return_type, PbsType::Void) { 0 } else { 1 };
self.emit(InstrKind::HostCall(id, return_slots));
return Ok(());
}
}
}
}
}
// Check for .raw()
if member_name == "raw" {
self.lower_node(&ma.object)?;
return Ok(());
}
// Check for Color.rgb
if member_name == "rgb" {
if let Node::Ident(obj_id) = &*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 Node::IntLit(lit) = arg {
literals.push(Some(lit.value));
} 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(), n.span);
return Err(());
}
}
}
}
}
for arg in &n.args {
self.lower_node(arg)?;
}
if let Node::Ident(obj_id) = &*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),
ma.span,
);
return Err(());
}
}
}
self.error("E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), ma.span);
Err(())
}
_ => {
for arg in &n.args {
self.lower_node(arg)?;
}
self.error("E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), n.callee.span());
Err(())
}
}
}
fn lower_constructor_call(&mut self, ctor: &ConstructorDeclNode, args: &[Node]) -> Result<(), ()> {
let mut param_map = HashMap::new();
for (i, param) in ctor.params.iter().enumerate() {
if i < args.len() {
param_map.insert(self.interner.resolve(param.name).to_string(), args[i].clone());
}
}
for init in &ctor.initializers {
let substituted = self.substitute_node(init, &param_map);
self.lower_node(&substituted)?;
}
Ok(())
}
fn substitute_node(&self, node: &Node, param_map: &HashMap<String, Node>) -> Node {
match node {
Node::Ident(id) => {
if let Some(arg) = param_map.get(self.interner.resolve(id.name)) {
arg.clone()
} else {
node.clone()
}
}
Node::Binary(bin) => {
Node::Binary(BinaryNode {
span: bin.span,
left: Box::new(self.substitute_node(&bin.left, param_map)),
right: Box::new(self.substitute_node(&bin.right, param_map)),
op: bin.op.clone(),
})
}
Node::Unary(un) => {
Node::Unary(UnaryNode {
span: un.span,
op: un.op.clone(),
expr: Box::new(self.substitute_node(&un.expr, param_map)),
})
}
Node::Call(call) => {
Node::Call(CallNode {
span: call.span,
callee: Box::new(self.substitute_node(&call.callee, param_map)),
args: call.args.iter().map(|a| self.substitute_node(a, param_map)).collect(),
})
}
_ => node.clone()
}
}
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, n: &BinaryNode) -> 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), n.span);
return Err(());
}
}
Ok(())
}
fn lower_unary(&mut self, n: &UnaryNode) -> 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), n.span);
return Err(());
}
}
Ok(())
}
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.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_type_node(&mut self, node: &Node) -> Type {
match node {
Node::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,
_ => Type::Struct(self.interner.resolve(n.name).to_string()),
},
Node::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 Node::IntLit(il) = &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));
}
}
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<LocalInfo> {
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<u32> {
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::frontends::pbs::collector::SymbolCollector;
use crate::frontends::pbs::parser::Parser;
use crate::frontends::pbs::symbols::ModuleSymbols;
use crate::ir_core;
use prometeu_analysis::NameInterner;
#[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 mut interner = NameInterner::new();
let mut parser = Parser::new(code, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
// 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, ir_core::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, ir_core::InstrKind::Add)));
}
#[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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
assert!(max_func.blocks.len() >= 3);
}
#[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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();
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Alloc { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).unwrap();
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
let program = lowerer.lower_file(&ast, "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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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();
// 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 Log host {}
fn main() {
Gfx.clear(0);
Log.write(2, \"Hello\");
}
";
let mut interner = NameInterner::new();
let mut parser = Parser::new(code, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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();
// Gfx.clear -> 0x1010
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(0x1010, 0))));
// Log.write -> 0x5001
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(0x5001, 0))));
}
#[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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_LOWER_UNSUPPORTED".to_string())));
}
#[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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_LOWER_UNSUPPORTED".to_string())));
}
#[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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())));
}
#[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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();
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<int>[10b];
}
";
let mut interner = NameInterner::new();
let mut parser = Parser::new(code, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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();
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<int>[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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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();
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, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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 interner = NameInterner::new();
let mut parser = Parser::new(code, 0, &mut interner);
let ast = parser.parse_file().expect("Failed to parse");
let mut collector = SymbolCollector::new(&interner);
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let imported = ModuleSymbols::new();
let lowerer = Lowerer::new(&module_symbols, &imported, &interner);
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'")));
}
}