This commit is contained in:
Nilton Constantino 2026-01-28 19:06:42 +00:00
parent ced7ff0607
commit f37c15f3d6
No known key found for this signature in database
9 changed files with 191 additions and 35 deletions

View File

@ -47,6 +47,7 @@ impl SymbolCollector {
namespace: Namespace::Value, namespace: Namespace::Value,
visibility: Visibility::FilePrivate, visibility: Visibility::FilePrivate,
ty: None, // Will be resolved later ty: None, // Will be resolved later
is_host: false,
span: decl.span, span: decl.span,
}; };
self.insert_value_symbol(symbol); self.insert_value_symbol(symbol);
@ -64,6 +65,7 @@ impl SymbolCollector {
namespace: Namespace::Type, // Service is a type namespace: Namespace::Type, // Service is a type
visibility: vis, visibility: vis,
ty: None, ty: None,
is_host: false,
span: decl.span, span: decl.span,
}; };
self.insert_type_symbol(symbol); self.insert_type_symbol(symbol);
@ -87,6 +89,7 @@ impl SymbolCollector {
namespace: Namespace::Type, namespace: Namespace::Type,
visibility: vis, visibility: vis,
ty: None, ty: None,
is_host: decl.is_host,
span: decl.span, span: decl.span,
}; };
self.insert_type_symbol(symbol); self.insert_type_symbol(symbol);

View File

@ -61,6 +61,26 @@ impl ContractRegistry {
log.insert("writeTag".to_string(), 0x5002); log.insert("writeTag".to_string(), 0x5002);
mappings.insert("Log".to_string(), log); 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 } Self { mappings }
} }

View File

@ -7,7 +7,7 @@ use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator,
use std::collections::HashMap; use std::collections::HashMap;
pub struct Lowerer<'a> { pub struct Lowerer<'a> {
_module_symbols: &'a ModuleSymbols, module_symbols: &'a ModuleSymbols,
program: Program, program: Program,
current_function: Option<Function>, current_function: Option<Function>,
current_block: Option<Block>, current_block: Option<Block>,
@ -21,7 +21,7 @@ pub struct Lowerer<'a> {
impl<'a> Lowerer<'a> { impl<'a> Lowerer<'a> {
pub fn new(module_symbols: &'a ModuleSymbols) -> Self { pub fn new(module_symbols: &'a ModuleSymbols) -> Self {
Self { Self {
_module_symbols: module_symbols, module_symbols,
program: Program { program: Program {
const_pool: ir_core::ConstPool::new(), const_pool: ir_core::ConstPool::new(),
modules: Vec::new(), modules: Vec::new(),
@ -224,17 +224,26 @@ impl<'a> Lowerer<'a> {
} }
Node::MemberAccess(ma) => { Node::MemberAccess(ma) => {
if let Node::Ident(obj_id) = &*ma.object { if let Node::Ident(obj_id) = &*ma.object {
// 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) { if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
self.emit(Instr::Syscall(syscall_id)); self.emit(Instr::Syscall(syscall_id));
} else { return;
// Regular member call (method) }
}
}
// Regular member call (method) or fallback
// In v0 we don't handle this yet, so emit dummy call // 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));
} }
} else {
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
}
}
_ => { _ => {
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32)); self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
} }

View File

@ -171,9 +171,17 @@ impl<'a> Resolver<'a> {
self.exit_scope(); self.exit_scope();
} }
Node::MemberAccess(n) => { Node::MemberAccess(n) => {
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); 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.
} }
_ => {} _ => {}
} }
@ -235,21 +243,41 @@ impl<'a> Resolver<'a> {
} }
fn resolve_identifier(&mut self, name: &str, span: Span, namespace: Namespace) -> Option<Symbol> { fn resolve_identifier(&mut self, name: &str, span: Span, namespace: Namespace) -> Option<Symbol> {
// Built-ins (minimal for v0) if self.is_builtin(name, namespace) {
return None;
}
if let Some(sym) = self.lookup_identifier(name, namespace) {
Some(sym)
} else {
if namespace == Namespace::Type { if namespace == Namespace::Type {
match name { self.diagnostics.push(Diagnostic {
"int" | "float" | "string" | "bool" | "void" | "optional" | "result" => return None, 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
} }
} }
if namespace == Namespace::Value { fn is_builtin(&self, name: &str, namespace: Namespace) -> bool {
match name { match namespace {
"none" | "some" | "ok" | "err" | "true" | "false" => return None, 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<Symbol> {
// 1. local bindings // 1. local bindings
if namespace == Namespace::Value { if namespace == Namespace::Value {
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
@ -280,16 +308,6 @@ impl<'a> Resolver<'a> {
return Some(sym.clone()); 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 None
} }
@ -323,6 +341,7 @@ impl<'a> Resolver<'a> {
namespace: Namespace::Value, namespace: Namespace::Value,
visibility: Visibility::FilePrivate, visibility: Visibility::FilePrivate,
ty: None, // Will be set by TypeChecker ty: None, // Will be set by TypeChecker
is_host: false,
span, span,
}); });
} }

View File

@ -19,7 +19,7 @@ pub enum SymbolKind {
Local, Local,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Namespace { pub enum Namespace {
Type, Type,
Value, Value,
@ -32,6 +32,7 @@ pub struct Symbol {
pub namespace: Namespace, pub namespace: Namespace,
pub visibility: Visibility, pub visibility: Visibility,
pub ty: Option<PbsType>, pub ty: Option<PbsType>,
pub is_host: bool,
pub span: Span, pub span: Span,
} }

View File

@ -4,6 +4,7 @@ use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::types::PbsType;
use crate::frontends::pbs::resolver::ModuleProvider; use crate::frontends::pbs::resolver::ModuleProvider;
use crate::frontends::pbs::contracts::ContractRegistry;
use std::collections::HashMap; use std::collections::HashMap;
pub struct TypeChecker<'a> { pub struct TypeChecker<'a> {
@ -13,6 +14,7 @@ pub struct TypeChecker<'a> {
mut_bindings: Vec<HashMap<String, bool>>, mut_bindings: Vec<HashMap<String, bool>>,
current_return_type: Option<PbsType>, current_return_type: Option<PbsType>,
diagnostics: Vec<Diagnostic>, diagnostics: Vec<Diagnostic>,
contract_registry: ContractRegistry,
} }
impl<'a> TypeChecker<'a> { impl<'a> TypeChecker<'a> {
@ -27,6 +29,7 @@ impl<'a> TypeChecker<'a> {
mut_bindings: Vec::new(), mut_bindings: Vec::new(),
current_return_type: None, current_return_type: None,
diagnostics: Vec::new(), diagnostics: Vec::new(),
contract_registry: ContractRegistry::new(),
} }
} }
@ -140,6 +143,24 @@ impl<'a> TypeChecker<'a> {
} }
fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType { 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); 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 // 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. // or resolve it if we have contract info.

View File

@ -7,6 +7,8 @@ use prometeu_compiler::ir_core;
#[test] #[test]
fn test_host_contract_call_lowering() { fn test_host_contract_call_lowering() {
let code = " let code = "
declare contract Gfx host {}
declare contract Log host {}
fn main() { fn main() {
Gfx.clear(0); Gfx.clear(0);
Log.write(\"Hello\"); 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)))); 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] #[test]
fn test_invalid_contract_call_lowering() { fn test_invalid_contract_call_lowering() {
let code = " let code = "
declare contract Gfx host {}
fn main() { fn main() {
Gfx.invalidMethod(0); Gfx.invalidMethod(0);
} }

View File

@ -116,6 +116,7 @@ fn test_visibility_error() {
namespace: Namespace::Type, namespace: Namespace::Type,
visibility: Visibility::FilePrivate, visibility: Visibility::FilePrivate,
ty: None, ty: None,
is_host: false,
span: Span::new(1, 0, 0), span: Span::new(1, 0, 0),
}).unwrap(); }).unwrap();
@ -160,6 +161,7 @@ fn test_import_resolution() {
namespace: Namespace::Type, namespace: Namespace::Type,
visibility: Visibility::Pub, visibility: Visibility::Pub,
ty: None, ty: None,
is_host: false,
span: Span::new(1, 0, 0), span: Span::new(1, 0, 0),
}).unwrap(); }).unwrap();

View File

@ -18,7 +18,9 @@ fn check_code(code: &str) -> Result<(), String> {
let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string()); let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string());
errors.push(format!("{}: {}", code, diag.message)); 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")); 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] #[test]
fn test_void_return_ok() { fn test_void_return_ok() {
let code = "fn main() { return; }"; let code = "fn main() { return; }";