From f56353ce9bef89ec855ac04d90784e0a19587a48 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Thu, 29 Jan 2026 17:56:12 +0000 Subject: [PATCH] pr 24 --- .../src/frontends/pbs/contracts.rs | 237 +++++++++++++++--- .../src/frontends/pbs/lowering.rs | 8 +- .../src/frontends/pbs/typecheck.rs | 33 ++- crates/prometeu-compiler/src/ir_core/instr.rs | 2 +- .../prometeu-compiler/src/ir_vm/validate.rs | 2 +- .../src/lowering/core_to_vm.rs | 6 +- docs/specs/pbs/files/PRs para Junie.md | 22 -- 7 files changed, 239 insertions(+), 71 deletions(-) diff --git a/crates/prometeu-compiler/src/frontends/pbs/contracts.rs b/crates/prometeu-compiler/src/frontends/pbs/contracts.rs index ab7b858f..3e84337e 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/contracts.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/contracts.rs @@ -1,7 +1,14 @@ use std::collections::HashMap; +use crate::frontends::pbs::types::PbsType; + +pub struct ContractMethod { + pub id: u32, + pub params: Vec, + pub return_type: PbsType, +} pub struct ContractRegistry { - mappings: HashMap>, + mappings: HashMap>, } impl ContractRegistry { @@ -10,81 +17,233 @@ impl ContractRegistry { // GFX mappings let mut gfx = HashMap::new(); - gfx.insert("clear".to_string(), 0x1001); - gfx.insert("fillRect".to_string(), 0x1002); - gfx.insert("drawLine".to_string(), 0x1003); - gfx.insert("drawCircle".to_string(), 0x1004); - gfx.insert("drawDisc".to_string(), 0x1005); - gfx.insert("drawSquare".to_string(), 0x1006); - gfx.insert("setSprite".to_string(), 0x1007); - gfx.insert("drawText".to_string(), 0x1008); + gfx.insert("clear".to_string(), ContractMethod { + id: 0x1001, + params: vec![PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("fillRect".to_string(), ContractMethod { + id: 0x1002, + params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("drawLine".to_string(), ContractMethod { + id: 0x1003, + params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("drawCircle".to_string(), ContractMethod { + id: 0x1004, + params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("drawDisc".to_string(), ContractMethod { + id: 0x1005, + params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("drawSquare".to_string(), ContractMethod { + id: 0x1006, + params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("setSprite".to_string(), ContractMethod { + id: 0x1007, + params: vec![PbsType::Int, PbsType::Int, PbsType::Int], + return_type: PbsType::Void, + }); + gfx.insert("drawText".to_string(), ContractMethod { + id: 0x1008, + params: vec![PbsType::Int, PbsType::Int, PbsType::String, PbsType::Int], + return_type: PbsType::Void, + }); mappings.insert("Gfx".to_string(), gfx); // Input mappings let mut input = HashMap::new(); - input.insert("getPad".to_string(), 0x2001); - input.insert("getPadPressed".to_string(), 0x2002); - input.insert("getPadReleased".to_string(), 0x2003); - input.insert("getPadHold".to_string(), 0x2004); + input.insert("getPad".to_string(), ContractMethod { + id: 0x2001, + params: vec![PbsType::Int], + return_type: PbsType::Int, + }); + input.insert("getPadPressed".to_string(), ContractMethod { + id: 0x2002, + params: vec![PbsType::Int], + return_type: PbsType::Int, + }); + input.insert("getPadReleased".to_string(), ContractMethod { + id: 0x2003, + params: vec![PbsType::Int], + return_type: PbsType::Int, + }); + input.insert("getPadHold".to_string(), ContractMethod { + id: 0x2004, + params: vec![PbsType::Int], + return_type: PbsType::Int, + }); mappings.insert("Input".to_string(), input); // Touch mappings let mut touch = HashMap::new(); - touch.insert("getX".to_string(), 0x2101); - touch.insert("getY".to_string(), 0x2102); - touch.insert("isDown".to_string(), 0x2103); - touch.insert("isPressed".to_string(), 0x2104); - touch.insert("isReleased".to_string(), 0x2105); - touch.insert("getHold".to_string(), 0x2106); + touch.insert("getX".to_string(), ContractMethod { + id: 0x2101, + params: vec![], + return_type: PbsType::Int, + }); + touch.insert("getY".to_string(), ContractMethod { + id: 0x2102, + params: vec![], + return_type: PbsType::Int, + }); + touch.insert("isDown".to_string(), ContractMethod { + id: 0x2103, + params: vec![], + return_type: PbsType::Bool, + }); + touch.insert("isPressed".to_string(), ContractMethod { + id: 0x2104, + params: vec![], + return_type: PbsType::Bool, + }); + touch.insert("isReleased".to_string(), ContractMethod { + id: 0x2105, + params: vec![], + return_type: PbsType::Bool, + }); + touch.insert("getHold".to_string(), ContractMethod { + id: 0x2106, + params: vec![], + return_type: PbsType::Int, + }); mappings.insert("Touch".to_string(), touch); // Audio mappings let mut audio = HashMap::new(); - audio.insert("playSample".to_string(), 0x3001); - audio.insert("play".to_string(), 0x3002); + audio.insert("playSample".to_string(), ContractMethod { + id: 0x3001, + params: vec![PbsType::Int], + return_type: PbsType::Void, + }); + audio.insert("play".to_string(), ContractMethod { + id: 0x3002, + params: vec![PbsType::Int], + return_type: PbsType::Void, + }); mappings.insert("Audio".to_string(), audio); // FS mappings let mut fs = HashMap::new(); - fs.insert("open".to_string(), 0x4001); - fs.insert("read".to_string(), 0x4002); - fs.insert("write".to_string(), 0x4003); - fs.insert("close".to_string(), 0x4004); - fs.insert("listDir".to_string(), 0x4005); - fs.insert("exists".to_string(), 0x4006); - fs.insert("delete".to_string(), 0x4007); + fs.insert("open".to_string(), ContractMethod { + id: 0x4001, + params: vec![PbsType::String, PbsType::String], + return_type: PbsType::Int, + }); + fs.insert("read".to_string(), ContractMethod { + id: 0x4002, + params: vec![PbsType::Int], + return_type: PbsType::String, + }); + fs.insert("write".to_string(), ContractMethod { + id: 0x4003, + params: vec![PbsType::Int, PbsType::String], + return_type: PbsType::Int, + }); + fs.insert("close".to_string(), ContractMethod { + id: 0x4004, + params: vec![PbsType::Int], + return_type: PbsType::Void, + }); + fs.insert("listDir".to_string(), ContractMethod { + id: 0x4005, + params: vec![PbsType::String], + return_type: PbsType::String, + }); + fs.insert("exists".to_string(), ContractMethod { + id: 0x4006, + params: vec![PbsType::String], + return_type: PbsType::Bool, + }); + fs.insert("delete".to_string(), ContractMethod { + id: 0x4007, + params: vec![PbsType::String], + return_type: PbsType::Bool, + }); mappings.insert("Fs".to_string(), fs); // Log mappings let mut log = HashMap::new(); - log.insert("write".to_string(), 0x5001); - log.insert("writeTag".to_string(), 0x5002); + log.insert("write".to_string(), ContractMethod { + id: 0x5001, + params: vec![PbsType::Int, PbsType::String], + return_type: PbsType::Void, + }); + log.insert("writeTag".to_string(), ContractMethod { + id: 0x5002, + params: vec![PbsType::Int, PbsType::Int, PbsType::String], + return_type: PbsType::Void, + }); mappings.insert("Log".to_string(), log); // System mappings let mut system = HashMap::new(); - system.insert("hasCart".to_string(), 0x0001); - system.insert("runCart".to_string(), 0x0002); + system.insert("hasCart".to_string(), ContractMethod { + id: 0x0001, + params: vec![], + return_type: PbsType::Bool, + }); + system.insert("runCart".to_string(), ContractMethod { + id: 0x0002, + params: vec![], + return_type: PbsType::Void, + }); mappings.insert("System".to_string(), system); // Asset mappings let mut asset = HashMap::new(); - asset.insert("load".to_string(), 0x6001); - asset.insert("status".to_string(), 0x6002); - asset.insert("commit".to_string(), 0x6003); - asset.insert("cancel".to_string(), 0x6004); + asset.insert("load".to_string(), ContractMethod { + id: 0x6001, + params: vec![PbsType::String], + return_type: PbsType::Int, + }); + asset.insert("status".to_string(), ContractMethod { + id: 0x6002, + params: vec![PbsType::Int], + return_type: PbsType::Int, + }); + asset.insert("commit".to_string(), ContractMethod { + id: 0x6003, + params: vec![PbsType::Int], + return_type: PbsType::Void, + }); + asset.insert("cancel".to_string(), ContractMethod { + id: 0x6004, + params: vec![PbsType::Int], + return_type: PbsType::Void, + }); mappings.insert("Asset".to_string(), asset); // Bank mappings let mut bank = HashMap::new(); - bank.insert("info".to_string(), 0x6101); - bank.insert("slotInfo".to_string(), 0x6102); + bank.insert("info".to_string(), ContractMethod { + id: 0x6101, + params: vec![PbsType::Int], + return_type: PbsType::String, + }); + bank.insert("slotInfo".to_string(), ContractMethod { + id: 0x6102, + params: vec![PbsType::Int, PbsType::Int], + return_type: PbsType::String, + }); mappings.insert("Bank".to_string(), bank); Self { mappings } } pub fn resolve(&self, contract: &str, method: &str) -> Option { - self.mappings.get(contract).and_then(|m| m.get(method)).copied() + self.mappings.get(contract).and_then(|m| m.get(method)).map(|m| m.id) + } + + pub fn get_method(&self, contract: &str, method: &str) -> Option<&ContractMethod> { + self.mappings.get(contract).and_then(|m| m.get(method)) } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index c79c96f1..1dc577e7 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -414,7 +414,7 @@ impl<'a> Lowerer<'a> { if is_host_contract && !is_shadowed { if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) { - self.emit(Instr::Syscall(syscall_id)); + self.emit(Instr::HostCall(syscall_id)); return Ok(()); } else { self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span); @@ -771,7 +771,7 @@ mod tests { declare contract Log host {} fn main() { Gfx.clear(0); - Log.write(\"Hello\"); + Log.write(2, \"Hello\"); } "; let mut parser = Parser::new(code, 0); @@ -788,9 +788,9 @@ mod tests { let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); // Gfx.clear -> 0x1001 - assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x1001)))); + assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x1001)))); // Log.write -> 0x5001 - assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x5001)))); + assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x5001)))); } #[test] diff --git a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs index e3b6c9b0..9d48ddf7 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs @@ -148,7 +148,12 @@ impl<'a> TypeChecker<'a> { if let Some(sym) = self.module_symbols.type_symbols.get(&id.name) { if sym.kind == SymbolKind::Contract && sym.is_host { // Check if the method exists in registry - if self.contract_registry.resolve(&id.name, &n.member).is_none() { + if let Some(method) = self.contract_registry.get_method(&id.name, &n.member) { + return PbsType::Function { + params: method.params.clone(), + return_type: Box::new(method.return_type.clone()), + }; + } else { self.diagnostics.push(Diagnostic { level: DiagnosticLevel::Error, code: Some("E_RESOLVE_UNDEFINED".to_string()), @@ -733,6 +738,32 @@ mod tests { assert!(res.is_ok()); } + #[test] + fn test_host_method_arity_mismatch() { + let code = " + declare contract Gfx host {} + fn main() { + Gfx.clear(0, 1); + } + "; + let res = check_code(code); + assert!(res.is_err()); + assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); + } + + #[test] + fn test_host_method_type_mismatch() { + let code = " + declare contract Gfx host {} + fn main() { + Gfx.clear(\"red\"); + } + "; + let res = check_code(code); + assert!(res.is_err()); + assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); + } + #[test] fn test_void_return_ok() { let code = "fn main() { return; }"; diff --git a/crates/prometeu-compiler/src/ir_core/instr.rs b/crates/prometeu-compiler/src/ir_core/instr.rs index 0641a827..8e2713da 100644 --- a/crates/prometeu-compiler/src/ir_core/instr.rs +++ b/crates/prometeu-compiler/src/ir_core/instr.rs @@ -9,7 +9,7 @@ pub enum Instr { /// Placeholder for function calls. Call(FunctionId, u32), /// Host calls (syscalls). - Syscall(u32), + HostCall(u32), /// Variable access. GetLocal(u32), SetLocal(u32), diff --git a/crates/prometeu-compiler/src/ir_vm/validate.rs b/crates/prometeu-compiler/src/ir_vm/validate.rs index 9afe4fc6..10ca3a54 100644 --- a/crates/prometeu-compiler/src/ir_vm/validate.rs +++ b/crates/prometeu-compiler/src/ir_vm/validate.rs @@ -4,7 +4,7 @@ use crate::ir_vm::module::Module; pub fn validate_module(_module: &Module) -> Result<(), DiagnosticBundle> { // TODO: Implement common IR validations: // - Type checking rules - // - Syscall signatures + // - HostCall signatures // - VM invariants Ok(()) } diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index 3c60046f..e4d0730a 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -52,7 +52,7 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result func_id: *func_id, arg_count: *arg_count }, - ir_core::Instr::Syscall(id) => ir_vm::InstrKind::Syscall(*id), + ir_core::Instr::HostCall(id) => ir_vm::InstrKind::Syscall(*id), ir_core::Instr::GetLocal(slot) => ir_vm::InstrKind::GetLocal(*slot), ir_core::Instr::SetLocal(slot) => ir_vm::InstrKind::SetLocal(*slot), ir_core::Instr::Pop => ir_vm::InstrKind::Pop, @@ -160,7 +160,7 @@ mod tests { Block { id: 1, instrs: vec![ - Instr::Syscall(42), + Instr::HostCall(42), ], terminator: Terminator::Return, }, @@ -202,7 +202,7 @@ mod tests { } match &func.body[5].kind { InstrKind::Syscall(id) => assert_eq!(*id, 42), - _ => panic!("Expected Syscall 42"), + _ => panic!("Expected HostCall 42"), } match &func.body[6].kind { InstrKind::Ret => (), diff --git a/docs/specs/pbs/files/PRs para Junie.md b/docs/specs/pbs/files/PRs para Junie.md index 64434ee0..7d93e547 100644 --- a/docs/specs/pbs/files/PRs para Junie.md +++ b/docs/specs/pbs/files/PRs para Junie.md @@ -1,25 +1,3 @@ -# PR-24 — Validate Contract Calls in Frontend (Arity + Types) - -### Goal - -Move contract validation to compile time. - -### Required Changes - -* During PBS type checking: - - * Validate argument count against contract signature - * Validate argument types - -* Lower only validated calls to `HostCall` - -### Tests - -* Wrong arity → `E_TYPE_MISMATCH` -* Correct call lowers to Core IR `HostCall` - ---- - # PR-25 — Core IR Invariants Test Suite ### Goal