From dc3a0268f12e940c8d5f6754cbedcd613c32eadd Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Thu, 29 Jan 2026 17:23:16 +0000 Subject: [PATCH] pr 22 --- .../src/frontends/pbs/lowering.rs | 189 +++++++++++++++++- .../src/frontends/pbs/parser.rs | 49 ++++- crates/prometeu-compiler/src/ir_core/types.rs | 31 +++ .../src/lowering/core_to_vm.rs | 13 +- docs/specs/pbs/files/PRs para Junie.md | 26 --- 5 files changed, 265 insertions(+), 43 deletions(-) diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index a2d0d070..5c4f0a09 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -13,8 +13,11 @@ pub struct Lowerer<'a> { 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, contract_registry: ContractRegistry, } @@ -30,8 +33,11 @@ impl<'a> Lowerer<'a> { 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: HashMap::new(), contract_registry: ContractRegistry::new(), } } @@ -44,6 +50,17 @@ impl<'a> Lowerer<'a> { self.next_func_id += 1; self.function_ids.insert(n.name.clone(), id); } + if let Node::TypeDecl(n) = decl { + let id = TypeId(self.next_type_id); + self.next_type_id += 1; + self.type_ids.insert(n.name.clone(), id); + + if n.type_kind == "struct" { + if let Node::TypeBody(body) = &*n.body { + self.struct_slots.insert(n.name.clone(), body.members.len() as u32); + } + } + } } let mut module = Module { @@ -142,11 +159,46 @@ impl<'a> Lowerer<'a> { } } - fn lower_alloc(&mut self, _n: &AllocNode) { - // Allocation: Now requires explicit TypeId and slots. - // v0 approximation: TypeId(0), slots: 1. - // Proper derivation will be added in PR-22. - self.emit(Instr::Alloc { ty: TypeId(0), slots: 1 }); + fn lower_alloc(&mut self, n: &AllocNode) { + let (ty_id, slots) = self.get_type_id_and_slots(&n.ty); + self.emit(Instr::Alloc { ty: ty_id, slots }); + } + + fn get_type_id_and_slots(&mut self, node: &Node) -> (TypeId, u32) { + match node { + Node::TypeName(n) => { + let slots = self.struct_slots.get(&n.name).cloned().unwrap_or(1); + let id = self.get_or_create_type_id(&n.name); + (id, slots) + } + Node::TypeApp(ta) if 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); + (id, size) + } + _ => (TypeId(0), 1), + } + } + + 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) { @@ -363,7 +415,7 @@ impl<'a> Lowerer<'a> { self.start_block_with_id(merge_id); } - fn lower_type_node(&self, node: &Node) -> Type { + fn lower_type_node(&mut self, node: &Node) -> Type { match node { Node::TypeName(n) => match n.name.as_str() { "int" => Type::Int, @@ -373,6 +425,30 @@ impl<'a> Lowerer<'a> { "void" => Type::Void, _ => Type::Struct(n.name.clone()), }, + Node::TypeApp(ta) => { + if ta.base == "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 ta.base == "optional" { + Type::Optional(Box::new(self.lower_type_node(&ta.args[0]))) + } else if ta.base == "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!("{}<{}>", ta.base, ta.args.len())) + } + } _ => Type::Void, } } @@ -709,4 +785,105 @@ mod tests { // Should be a regular call (which might fail later or be a dummy) assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _)))); } + + #[test] + fn test_alloc_struct_slots() { + let code = " + declare struct Vec3 { + x: int, + y: int, + z: int + } + fn main() { + let v = alloc Vec3; + } + "; + let mut parser = Parser::new(code, 0); + let ast = parser.parse_file().expect("Failed to parse"); + + let mut collector = SymbolCollector::new(); + let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); + let module_symbols = ModuleSymbols { type_symbols, value_symbols }; + + let lowerer = Lowerer::new(&module_symbols); + let program = lowerer.lower_file(&ast, "test"); + + let func = &program.modules[0].functions[0]; + let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); + + let alloc = instrs.iter().find_map(|i| { + if let Instr::Alloc { ty, slots } = i { + 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 parser = Parser::new(code, 0); + let ast = parser.parse_file().expect("Failed to parse"); + + let mut collector = SymbolCollector::new(); + let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); + let module_symbols = ModuleSymbols { type_symbols, value_symbols }; + + let lowerer = Lowerer::new(&module_symbols); + let program = lowerer.lower_file(&ast, "test"); + + let func = &program.modules[0].functions[0]; + let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); + + let alloc = instrs.iter().find_map(|i| { + if let Instr::Alloc { ty, slots } = i { + 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 parser = Parser::new(code, 0); + let ast = parser.parse_file().expect("Failed to parse"); + + let mut collector = SymbolCollector::new(); + let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); + let module_symbols = ModuleSymbols { type_symbols, value_symbols }; + + let lowerer = Lowerer::new(&module_symbols); + let program = lowerer.lower_file(&ast, "test"); + + let func = &program.modules[0].functions[0]; + let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); + + let alloc = instrs.iter().find_map(|i| { + if let Instr::Alloc { ty, slots } = i { + 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"); + } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/parser.rs b/crates/prometeu-compiler/src/frontends/pbs/parser.rs index bb883914..8140085e 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/parser.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/parser.rs @@ -328,7 +328,7 @@ impl Parser { fn parse_type_ref(&mut self) -> Result { let id_tok = self.peek().clone(); let name = self.expect_identifier()?; - if self.peek().kind == TokenKind::Lt { + let mut node = if self.peek().kind == TokenKind::Lt { self.advance(); // < let mut args = Vec::new(); loop { @@ -340,17 +340,56 @@ impl Parser { } } let end_tok = self.consume(TokenKind::Gt)?; - Ok(Node::TypeApp(TypeAppNode { + Node::TypeApp(TypeAppNode { span: Span::new(self.file_id, id_tok.span.start, end_tok.span.end), base: name, args, - })) + }) } else { - Ok(Node::TypeName(TypeNameNode { + Node::TypeName(TypeNameNode { span: id_tok.span, name, - })) + }) + }; + + if self.peek().kind == TokenKind::OpenBracket { + self.advance(); + let size_tok = self.peek().clone(); + let size = match size_tok.kind { + TokenKind::IntLit(v) => { + self.advance(); + v as u32 + } + TokenKind::BoundedLit(v) => { + self.advance(); + v + } + _ => return Err(self.error_with_code("integer or bounded literal for array size", Some("E_PARSE_EXPECTED_TOKEN"))), + }; + let end_tok = self.consume(TokenKind::CloseBracket)?; + let span = Span::new(self.file_id, node.span().start, end_tok.span.end); + + // If it's array[N], we want to represent it cleanly. + // Currently TypeAppNode { base: name, args } was created. + // If base was "array", it already has T in args. + // We can just add N to args. + match &mut node { + Node::TypeApp(ta) if ta.base == "array" => { + ta.args.push(Node::IntLit(IntLitNode { span: size_tok.span, value: size as i64 })); + ta.span = span; + } + _ => { + // Fallback for T[N] if we want to support it, but spec says array[N] + node = Node::TypeApp(TypeAppNode { + span, + base: "array".to_string(), + args: vec![node, Node::IntLit(IntLitNode { span: size_tok.span, value: size as i64 })], + }); + } + } } + + Ok(node) } fn parse_block(&mut self) -> Result { diff --git a/crates/prometeu-compiler/src/ir_core/types.rs b/crates/prometeu-compiler/src/ir_core/types.rs index f8a7d8d7..882e4076 100644 --- a/crates/prometeu-compiler/src/ir_core/types.rs +++ b/crates/prometeu-compiler/src/ir_core/types.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use std::fmt; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Type { @@ -13,8 +14,38 @@ pub enum Type { Service(String), Contract(String), ErrorType(String), + Array(Box, u32), Function { params: Vec, return_type: Box, }, } + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Void => write!(f, "void"), + Type::Int => write!(f, "int"), + Type::Float => write!(f, "float"), + Type::Bool => write!(f, "bool"), + Type::String => write!(f, "string"), + Type::Optional(inner) => write!(f, "optional<{}>", inner), + Type::Result(ok, err) => write!(f, "result<{}, {}>", ok, err), + Type::Struct(name) => write!(f, "{}", name), + Type::Service(name) => write!(f, "{}", name), + Type::Contract(name) => write!(f, "{}", name), + Type::ErrorType(name) => write!(f, "{}", name), + Type::Array(inner, size) => write!(f, "array<{}>[{}]", inner, size), + Type::Function { params, return_type } => { + write!(f, "fn(")?; + for (i, param) in params.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", param)?; + } + write!(f, ") -> {}", return_type) + } + } + } +} diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index f2b6a656..3c60046f 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -116,13 +116,14 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type { ir_core::Type::Float => ir_vm::Type::Float, ir_core::Type::Bool => ir_vm::Type::Bool, ir_core::Type::String => ir_vm::Type::String, - ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))), // Approximation - ir_core::Type::Result(ok, _) => lower_type(ok), // Approximation - ir_core::Type::Struct(_) => ir_vm::Type::Object, - ir_core::Type::Service(_) => ir_vm::Type::Object, - ir_core::Type::Contract(_) => ir_vm::Type::Object, - ir_core::Type::ErrorType(_) => ir_vm::Type::Object, + ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))), + ir_core::Type::Result(ok, _) => lower_type(ok), + ir_core::Type::Struct(_) + | ir_core::Type::Service(_) + | ir_core::Type::Contract(_) + | ir_core::Type::ErrorType(_) => ir_vm::Type::Object, ir_core::Type::Function { .. } => ir_vm::Type::Function, + ir_core::Type::Array(inner, _) => ir_vm::Type::Array(Box::new(lower_type(inner))), } } diff --git a/docs/specs/pbs/files/PRs para Junie.md b/docs/specs/pbs/files/PRs para Junie.md index d5f7f2ce..fc96d235 100644 --- a/docs/specs/pbs/files/PRs para Junie.md +++ b/docs/specs/pbs/files/PRs para Junie.md @@ -1,29 +1,3 @@ -# PR-22 — Make Allocation Shape Explicit in Core IR - -### Goal - -Stop implicit / guessed heap layouts. - -### Required Changes - -* Replace any shape-less `Alloc` with: - -```rust -Alloc { ty: TypeId, slots: u32 } -``` - -Rules: - -* `TypeId` comes from frontend type checking -* `slots` is derived deterministically (struct fields / array size) - -### Tests - -* Allocating storage struct emits correct `slots` -* Allocating array emits correct `slots` - ---- - # PR-23 — Eliminate Invalid Call Fallbacks ### Goal