diff --git a/crates/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/prometeu-compiler/src/frontends/pbs/collector.rs index f999d1cc..ebf22ac2 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/collector.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/collector.rs @@ -47,6 +47,7 @@ impl SymbolCollector { namespace: Namespace::Value, visibility: Visibility::FilePrivate, ty: None, // Will be resolved later + is_host: false, span: decl.span, }; self.insert_value_symbol(symbol); @@ -64,6 +65,7 @@ impl SymbolCollector { namespace: Namespace::Type, // Service is a type visibility: vis, ty: None, + is_host: false, span: decl.span, }; self.insert_type_symbol(symbol); @@ -87,6 +89,7 @@ impl SymbolCollector { namespace: Namespace::Type, visibility: vis, ty: None, + is_host: decl.is_host, span: decl.span, }; self.insert_type_symbol(symbol); diff --git a/crates/prometeu-compiler/src/frontends/pbs/contracts.rs b/crates/prometeu-compiler/src/frontends/pbs/contracts.rs index 542a3971..ab7b858f 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/contracts.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/contracts.rs @@ -61,6 +61,26 @@ impl ContractRegistry { log.insert("writeTag".to_string(), 0x5002); 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); + 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); + 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); + mappings.insert("Bank".to_string(), bank); + Self { mappings } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index b3982730..53911326 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -7,7 +7,7 @@ use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, use std::collections::HashMap; pub struct Lowerer<'a> { - _module_symbols: &'a ModuleSymbols, + module_symbols: &'a ModuleSymbols, program: Program, current_function: Option, current_block: Option, @@ -21,7 +21,7 @@ pub struct Lowerer<'a> { impl<'a> Lowerer<'a> { pub fn new(module_symbols: &'a ModuleSymbols) -> Self { Self { - _module_symbols: module_symbols, + module_symbols, program: Program { const_pool: ir_core::ConstPool::new(), modules: Vec::new(), @@ -224,16 +224,25 @@ impl<'a> Lowerer<'a> { } Node::MemberAccess(ma) => { if let Node::Ident(obj_id) = &*ma.object { - if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) { - self.emit(Instr::Syscall(syscall_id)); - } else { - // Regular member call (method) - // In v0 we don't handle this yet, so emit dummy call - self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); + // Check if it's a host contract according to symbol table + 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); + + // Ensure it's not shadowed by a local variable + let is_shadowed = self.lookup_local(&obj_id.name).is_some(); + + if is_host_contract && !is_shadowed { + if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) { + self.emit(Instr::Syscall(syscall_id)); + return; + } } - } else { - self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); } + + // Regular member call (method) or fallback + // In v0 we don't handle this yet, so emit dummy call + self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); } _ => { self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs index c0b931e8..5dbb469a 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -171,9 +171,17 @@ impl<'a> Resolver<'a> { self.exit_scope(); } Node::MemberAccess(n) => { - self.resolve_node(&n.object); - // For member access, the member name itself isn't resolved in the value namespace - // unless it's a property. In v0, we mostly care about host calls. + if let Node::Ident(id) = &*n.object { + if self.lookup_identifier(&id.name, Namespace::Value).is_none() { + // If not found in Value namespace, try Type namespace (for Contracts/Services) + if self.lookup_identifier(&id.name, Namespace::Type).is_none() { + // Still not found, use resolve_identifier to report error in Value namespace + self.resolve_identifier(&id.name, id.span, Namespace::Value); + } + } + } else { + self.resolve_node(&n.object); + } } _ => {} } @@ -235,21 +243,41 @@ impl<'a> Resolver<'a> { } fn resolve_identifier(&mut self, name: &str, span: Span, namespace: Namespace) -> Option { - // Built-ins (minimal for v0) - if namespace == Namespace::Type { - match name { - "int" | "float" | "string" | "bool" | "void" | "optional" | "result" => return None, - _ => {} - } + if self.is_builtin(name, namespace) { + return None; } - if namespace == Namespace::Value { - match name { - "none" | "some" | "ok" | "err" | "true" | "false" => return None, - _ => {} + if let Some(sym) = self.lookup_identifier(name, namespace) { + Some(sym) + } else { + if namespace == Namespace::Type { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_TYPE_UNKNOWN_TYPE".to_string()), + message: format!("Unknown type: {}", name), + span: Some(span), + }); + } else { + self.error_undefined(name, span); } + None } + } + fn is_builtin(&self, name: &str, namespace: Namespace) -> bool { + match namespace { + Namespace::Type => match name { + "int" | "float" | "string" | "bool" | "void" | "optional" | "result" => true, + _ => false, + }, + Namespace::Value => match name { + "none" | "some" | "ok" | "err" | "true" | "false" => true, + _ => false, + }, + } + } + + fn lookup_identifier(&self, name: &str, namespace: Namespace) -> Option { // 1. local bindings if namespace == Namespace::Value { for scope in self.scopes.iter().rev() { @@ -280,16 +308,6 @@ impl<'a> Resolver<'a> { return Some(sym.clone()); } - if namespace == Namespace::Type { - self.diagnostics.push(Diagnostic { - level: DiagnosticLevel::Error, - code: Some("E_TYPE_UNKNOWN_TYPE".to_string()), - message: format!("Unknown type: {}", name), - span: Some(span), - }); - } else { - self.error_undefined(name, span); - } None } @@ -323,6 +341,7 @@ impl<'a> Resolver<'a> { namespace: Namespace::Value, visibility: Visibility::FilePrivate, ty: None, // Will be set by TypeChecker + is_host: false, span, }); } diff --git a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs index a29097ea..b5c4b03c 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs @@ -19,7 +19,7 @@ pub enum SymbolKind { Local, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Namespace { Type, Value, @@ -32,6 +32,7 @@ pub struct Symbol { pub namespace: Namespace, pub visibility: Visibility, pub ty: Option, + pub is_host: bool, pub span: Span, } diff --git a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs index bf847704..896b217a 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs @@ -4,6 +4,7 @@ use crate::frontends::pbs::ast::*; use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::resolver::ModuleProvider; +use crate::frontends::pbs::contracts::ContractRegistry; use std::collections::HashMap; pub struct TypeChecker<'a> { @@ -13,6 +14,7 @@ pub struct TypeChecker<'a> { mut_bindings: Vec>, current_return_type: Option, diagnostics: Vec, + contract_registry: ContractRegistry, } impl<'a> TypeChecker<'a> { @@ -27,6 +29,7 @@ impl<'a> TypeChecker<'a> { mut_bindings: Vec::new(), current_return_type: None, diagnostics: Vec::new(), + contract_registry: ContractRegistry::new(), } } @@ -140,6 +143,24 @@ impl<'a> TypeChecker<'a> { } fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType { + if let Node::Ident(id) = &*n.object { + // Check if it's a known host contract + 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() { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_UNDEFINED".to_string()), + message: format!("Method '{}' not found on host contract '{}'", n.member, id.name), + span: Some(n.span), + }); + } + return PbsType::Void; + } + } + } + let _obj_ty = self.check_node(&n.object); // For v0, we assume member access on a host contract is valid and return a dummy type // or resolve it if we have contract info. diff --git a/crates/prometeu-compiler/tests/pbs_contract_tests.rs b/crates/prometeu-compiler/tests/pbs_contract_tests.rs index 73a51d6e..b160345b 100644 --- a/crates/prometeu-compiler/tests/pbs_contract_tests.rs +++ b/crates/prometeu-compiler/tests/pbs_contract_tests.rs @@ -7,6 +7,8 @@ use prometeu_compiler::ir_core; #[test] fn test_host_contract_call_lowering() { let code = " + declare contract Gfx host {} + declare contract Log host {} fn main() { Gfx.clear(0); Log.write(\"Hello\"); @@ -31,9 +33,61 @@ fn test_host_contract_call_lowering() { assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x5001)))); } +#[test] +fn test_contract_call_without_host_lowering() { + let code = " + declare contract Gfx {} + fn main() { + Gfx.clear(0); + } + "; + 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(); + + // Should NOT be a syscall if not declared as host + assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_)))); +} + +#[test] +fn test_shadowed_contract_call_lowering() { + let code = " + declare contract Gfx host {} + fn main() { + let Gfx = 10; + Gfx.clear(0); + } + "; + 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(); + + // Should NOT be a syscall because Gfx is shadowed by a local + assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_)))); +} + #[test] fn test_invalid_contract_call_lowering() { let code = " + declare contract Gfx host {} fn main() { Gfx.invalidMethod(0); } diff --git a/crates/prometeu-compiler/tests/pbs_resolver_tests.rs b/crates/prometeu-compiler/tests/pbs_resolver_tests.rs index 468dcde3..ed230404 100644 --- a/crates/prometeu-compiler/tests/pbs_resolver_tests.rs +++ b/crates/prometeu-compiler/tests/pbs_resolver_tests.rs @@ -116,6 +116,7 @@ fn test_visibility_error() { namespace: Namespace::Type, visibility: Visibility::FilePrivate, ty: None, + is_host: false, span: Span::new(1, 0, 0), }).unwrap(); @@ -160,6 +161,7 @@ fn test_import_resolution() { namespace: Namespace::Type, visibility: Visibility::Pub, ty: None, + is_host: false, span: Span::new(1, 0, 0), }).unwrap(); diff --git a/crates/prometeu-compiler/tests/pbs_typecheck_tests.rs b/crates/prometeu-compiler/tests/pbs_typecheck_tests.rs index 15a5bef2..66d8c4aa 100644 --- a/crates/prometeu-compiler/tests/pbs_typecheck_tests.rs +++ b/crates/prometeu-compiler/tests/pbs_typecheck_tests.rs @@ -18,7 +18,9 @@ fn check_code(code: &str) -> Result<(), String> { let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string()); errors.push(format!("{}: {}", code, diag.message)); } - Err(errors.join(", ")) + let err_msg = errors.join(", "); + println!("Compilation failed: {}", err_msg); + Err(err_msg) } } } @@ -101,6 +103,31 @@ fn test_unknown_type() { assert!(res.unwrap_err().contains("E_TYPE_UNKNOWN_TYPE")); } +#[test] +fn test_invalid_host_method() { + let code = " + declare contract Gfx host {} + fn main() { + Gfx.invalidMethod(); + } + "; + let res = check_code(code); + assert!(res.is_err()); + assert!(res.unwrap_err().contains("E_RESOLVE_UNDEFINED")); +} + +#[test] +fn test_valid_host_method() { + let code = " + declare contract Gfx host {} + fn main() { + Gfx.clear(0); + } + "; + let res = check_code(code); + assert!(res.is_ok()); +} + #[test] fn test_void_return_ok() { let code = "fn main() { return; }";