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,
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);

View File

@ -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 }
}

View File

@ -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<Function>,
current_block: Option<Block>,
@ -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));

View File

@ -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<Symbol> {
// 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<Symbol> {
// 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,
});
}

View File

@ -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<PbsType>,
pub is_host: bool,
pub span: Span,
}

View File

@ -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<HashMap<String, bool>>,
current_return_type: Option<PbsType>,
diagnostics: Vec<Diagnostic>,
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.

View File

@ -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);
}

View File

@ -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();

View File

@ -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; }";