pr 11
This commit is contained in:
parent
ced7ff0607
commit
f37c15f3d6
@ -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);
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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; }";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user