1390 lines
58 KiB
Rust
1390 lines
58 KiB
Rust
use crate::analysis::symbols::{SymbolArena, NodeToSymbol};
|
|
use crate::analysis::types::{TypeArena, TypeFacts, TypeKind};
|
|
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
|
use crate::common::spans::Span;
|
|
use crate::frontends::pbs::ast::*;
|
|
use crate::frontends::pbs::symbols::*;
|
|
use prometeu_analysis::{NameId, NameInterner, SymbolId, ModuleId, TypeId, NodeId};
|
|
use std::collections::HashMap;
|
|
|
|
pub trait ModuleProvider {
|
|
fn get_module_symbols(&self, from_path: &str) -> Option<&ModuleSymbols>;
|
|
}
|
|
|
|
pub struct Resolver<'a> {
|
|
interner: &'a NameInterner,
|
|
module_provider: &'a dyn ModuleProvider,
|
|
current_module: &'a ModuleSymbols,
|
|
scopes: Vec<HashMap<NameId, (Symbol, Option<SymbolId>)>>,
|
|
pub imported_symbols: ModuleSymbols,
|
|
diagnostics: Vec<Diagnostic>,
|
|
pub type_arena: TypeArena,
|
|
pub type_facts: TypeFacts,
|
|
pub symbol_arena: crate::analysis::symbols::SymbolArena,
|
|
pub node_to_symbol: crate::analysis::symbols::NodeToSymbol,
|
|
primitives: HashMap<String, TypeId>,
|
|
}
|
|
|
|
impl<'a> Resolver<'a> {
|
|
pub fn new(
|
|
current_module: &'a ModuleSymbols,
|
|
module_provider: &'a dyn ModuleProvider,
|
|
interner: &'a NameInterner,
|
|
) -> Self {
|
|
Self {
|
|
interner,
|
|
module_provider,
|
|
current_module,
|
|
scopes: Vec::new(),
|
|
imported_symbols: ModuleSymbols::new(),
|
|
diagnostics: Vec::new(),
|
|
type_arena: TypeArena::new(),
|
|
type_facts: TypeFacts::new(),
|
|
symbol_arena: SymbolArena::new(),
|
|
node_to_symbol: NodeToSymbol::new(),
|
|
primitives: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn bootstrap_types(&mut self, interner: &NameInterner) {
|
|
let primitive_names = ["int", "bool", "float", "string", "bounded", "void"];
|
|
for name in primitive_names {
|
|
let name_id = interner.get(name).expect("primitive name not interned");
|
|
let type_id = self.type_arena.intern_type(TypeKind::Primitive { name: name_id });
|
|
self.primitives.insert(name.to_string(), type_id);
|
|
}
|
|
}
|
|
|
|
pub fn resolve(&mut self, arena: &AstArena, root: NodeId) -> Result<(), DiagnosticBundle> {
|
|
let file = match arena.kind(root) {
|
|
NodeKind::File(file) => file,
|
|
_ => {
|
|
return Err(DiagnosticBundle::error(
|
|
"E_RESOLVE_INVALID_ROOT",
|
|
"Expected File node as root".to_string(),
|
|
arena.span(root),
|
|
))
|
|
}
|
|
};
|
|
|
|
// Step 0: Populate symbol_arena with top-level symbols for global lookup
|
|
for (name, list) in &self.current_module.type_symbols.symbols {
|
|
for sym in list {
|
|
self.symbol_arena.insert(crate::analysis::symbols::Symbol {
|
|
name: *name,
|
|
kind: match sym.kind {
|
|
SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function,
|
|
SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service,
|
|
SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct,
|
|
SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract,
|
|
SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType,
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
},
|
|
exported: sym.visibility == Visibility::Pub,
|
|
module: ModuleId(0),
|
|
decl_span: sym.span.clone(),
|
|
});
|
|
}
|
|
}
|
|
for (name, list) in &self.current_module.value_symbols.symbols {
|
|
for sym in list {
|
|
self.symbol_arena.insert(crate::analysis::symbols::Symbol {
|
|
name: *name,
|
|
kind: match sym.kind {
|
|
SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function,
|
|
SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service,
|
|
SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct,
|
|
SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract,
|
|
SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType,
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
},
|
|
exported: sym.visibility == Visibility::Pub,
|
|
module: ModuleId(0),
|
|
decl_span: sym.span.clone(),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Step 1: Process imports to populate imported_symbols
|
|
for imp in &file.imports {
|
|
if let NodeKind::Import(imp_node) = arena.kind(*imp) {
|
|
self.resolve_import(arena, *imp, imp_node);
|
|
}
|
|
}
|
|
|
|
// Add imported symbols to symbol_arena too
|
|
for (name, list) in &self.imported_symbols.type_symbols.symbols {
|
|
for sym in list {
|
|
self.symbol_arena.insert(crate::analysis::symbols::Symbol {
|
|
name: *name,
|
|
kind: match sym.kind {
|
|
SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function,
|
|
SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service,
|
|
SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct,
|
|
SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract,
|
|
SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType,
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
},
|
|
exported: sym.visibility == Visibility::Pub,
|
|
module: ModuleId(0), // Should be target module
|
|
decl_span: sym.span.clone(),
|
|
});
|
|
}
|
|
}
|
|
for (name, list) in &self.imported_symbols.value_symbols.symbols {
|
|
for sym in list {
|
|
self.symbol_arena.insert(crate::analysis::symbols::Symbol {
|
|
name: *name,
|
|
kind: match sym.kind {
|
|
SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function,
|
|
SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service,
|
|
SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct,
|
|
SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract,
|
|
SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType,
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
},
|
|
exported: sym.visibility == Visibility::Pub,
|
|
module: ModuleId(0), // Should be target module
|
|
decl_span: sym.span.clone(),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Step 2: Resolve all top-level declarations
|
|
for decl in &file.decls {
|
|
self.resolve_node(arena, *decl);
|
|
}
|
|
|
|
if !self.diagnostics.is_empty() {
|
|
return Err(DiagnosticBundle {
|
|
diagnostics: self.diagnostics.clone(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn resolve_import(&mut self, arena: &AstArena, imp_id: NodeId, imp: &ImportNodeArena) {
|
|
let provider = self.module_provider;
|
|
if let Some(target_symbols) = provider.get_module_symbols(&imp.from) {
|
|
let spec = match arena.kind(imp.spec) {
|
|
NodeKind::ImportSpec(spec) => spec,
|
|
_ => {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_INVALID_IMPORT".to_string(),
|
|
message: "Invalid import spec".to_string(),
|
|
span: arena.span(imp_id),
|
|
related: Vec::new(),
|
|
});
|
|
return;
|
|
}
|
|
};
|
|
|
|
for name in &spec.path {
|
|
// Try to find in Type namespace
|
|
if let Some(sym) = target_symbols.type_symbols.get(*name) {
|
|
if sym.visibility == Visibility::Pub {
|
|
let is_service = sym.kind == SymbolKind::Service;
|
|
let mut cloned = sym.clone();
|
|
cloned.origin = Some(imp.from.clone());
|
|
if let Err(_) = self.imported_symbols.type_symbols.insert(cloned) {
|
|
self.error_duplicate_import(*name, arena.span(imp_id));
|
|
}
|
|
// If a Service type is imported, also bring its public methods from the same module into value namespace
|
|
if is_service {
|
|
let base = self.interner.resolve(*name);
|
|
let prefix = format!("{}.", base);
|
|
for list in target_symbols.value_symbols.symbols.values() {
|
|
for vs in list {
|
|
// Apenas métodos do service importado: nomes exportados de métodos devem ser no formato "Service.method#sigN"
|
|
let vname = self.interner.resolve(vs.name);
|
|
if vname.starts_with(&prefix) {
|
|
if vs.visibility == Visibility::Pub {
|
|
let mut vsym = vs.clone();
|
|
vsym.origin = Some(imp.from.clone());
|
|
let _ = self.imported_symbols.value_symbols.insert(vsym);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
self.error_visibility(sym, arena.span(imp_id));
|
|
}
|
|
}
|
|
// Try to find in Value namespace
|
|
else if let Some(sym) = target_symbols.value_symbols.get(*name) {
|
|
if sym.visibility == Visibility::Pub {
|
|
let mut sym = sym.clone();
|
|
sym.origin = Some(imp.from.clone());
|
|
if let Err(_) = self.imported_symbols.value_symbols.insert(sym) {
|
|
self.error_duplicate_import(*name, arena.span(imp_id));
|
|
}
|
|
} else {
|
|
self.error_visibility(sym, arena.span(imp_id));
|
|
}
|
|
} else {
|
|
self.error_undefined(*name, arena.span(imp_id));
|
|
}
|
|
}
|
|
} else {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_INVALID_IMPORT".to_string(),
|
|
message: format!("Module not found: {}", imp.from),
|
|
span: arena.span(imp_id),
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
}
|
|
|
|
fn resolve_node(&mut self, arena: &AstArena, node: NodeId) {
|
|
match arena.kind(node) {
|
|
NodeKind::FnDecl(n) => self.resolve_fn_decl(arena, node, n),
|
|
NodeKind::ServiceDecl(n) => self.resolve_service_decl(arena, node, n),
|
|
NodeKind::TypeDecl(n) => self.resolve_type_decl(arena, node, n),
|
|
NodeKind::Block(n) => self.resolve_block(arena, n),
|
|
NodeKind::LetStmt(n) => self.resolve_let_stmt(arena, node, n),
|
|
NodeKind::ExprStmt(n) => self.resolve_node(arena, n.expr),
|
|
NodeKind::ReturnStmt(n) => {
|
|
if let Some(expr) = n.expr {
|
|
self.resolve_node(arena, expr);
|
|
}
|
|
}
|
|
NodeKind::Call(n) => {
|
|
if let NodeKind::Ident(id) = arena.kind(n.callee) {
|
|
if !self.is_builtin(id.name, Namespace::Value) {
|
|
let (value_sym, sym_id) = match self.lookup_with_id(id.name, Namespace::Value) {
|
|
Some((sym, id)) => (Some(sym), id),
|
|
None => (None, None),
|
|
};
|
|
if let Some(sid) = sym_id {
|
|
self.node_to_symbol.bind_node(n.callee, sid);
|
|
}
|
|
if value_sym.is_none() && self.lookup_identifier(id.name, Namespace::Type).is_none() {
|
|
// TODO: Resolver for v0 allows unresolved call targets (e.g. constructors via prelude)
|
|
}
|
|
}
|
|
} else {
|
|
self.resolve_node(arena, n.callee);
|
|
}
|
|
for arg in &n.args {
|
|
self.resolve_node(arena, *arg);
|
|
}
|
|
}
|
|
NodeKind::Unary(n) => {
|
|
self.resolve_node(arena, n.expr);
|
|
match n.op.as_str() {
|
|
"-" => {
|
|
let int_type = self.primitives.get("int").copied();
|
|
if let Some(expr_type) = self.type_facts.get_node_type(n.expr) {
|
|
if Some(expr_type) == int_type {
|
|
if let Some(id) = int_type {
|
|
self.type_facts.set_node_type(node, id);
|
|
}
|
|
} else {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_MISMATCH".to_string(),
|
|
message: "Unary '-' operator expects 'int'".to_string(),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
"!" => {
|
|
let bool_type = self.primitives.get("bool").copied();
|
|
if let Some(expr_type) = self.type_facts.get_node_type(n.expr) {
|
|
if Some(expr_type) == bool_type {
|
|
if let Some(id) = bool_type {
|
|
self.type_facts.set_node_type(node, id);
|
|
}
|
|
} else {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_MISMATCH".to_string(),
|
|
message: "Unary '!' operator expects 'bool'".to_string(),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
NodeKind::Binary(n) => {
|
|
self.resolve_node(arena, n.left);
|
|
self.resolve_node(arena, n.right);
|
|
|
|
let left_type = self.type_facts.get_node_type(n.left);
|
|
let right_type = self.type_facts.get_node_type(n.right);
|
|
let int_type = self.primitives.get("int").copied();
|
|
let bool_type = self.primitives.get("bool").copied();
|
|
|
|
match n.op.as_str() {
|
|
"+" | "-" | "*" | "/" => {
|
|
if let (Some(l), Some(r)) = (left_type, right_type) {
|
|
if Some(l) == int_type && Some(r) == int_type {
|
|
if let Some(id) = int_type {
|
|
self.type_facts.set_node_type(node, id);
|
|
}
|
|
} else {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_MISMATCH".to_string(),
|
|
message: format!("Binary '{}' operator expects 'int' operands", n.op),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
"==" | "<" | ">" | "<=" | ">=" | "!=" => {
|
|
if let (Some(l), Some(r)) = (left_type, right_type) {
|
|
if l == r {
|
|
if let Some(id) = bool_type {
|
|
self.type_facts.set_node_type(node, id);
|
|
}
|
|
} else {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_MISMATCH".to_string(),
|
|
message: format!("Binary '{}' operator expects operands of the same type", n.op),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
NodeKind::TypeName(_) | NodeKind::TypeApp(_) => {
|
|
self.resolve_type_ref(arena, node);
|
|
}
|
|
NodeKind::ConstructorDecl(n) => self.resolve_constructor_decl(arena, node, n),
|
|
NodeKind::ConstantDecl(n) => self.resolve_node(arena, n.value),
|
|
NodeKind::Alloc(n) => {
|
|
self.resolve_type_ref(arena, n.ty);
|
|
}
|
|
NodeKind::Mutate(n) => {
|
|
self.resolve_node(arena, n.target);
|
|
self.enter_scope();
|
|
self.define_local(n.binding, arena.span(node), SymbolKind::Local);
|
|
self.resolve_node(arena, n.body);
|
|
self.exit_scope();
|
|
}
|
|
NodeKind::Borrow(n) => {
|
|
self.resolve_node(arena, n.target);
|
|
self.enter_scope();
|
|
self.define_local(n.binding, arena.span(node), SymbolKind::Local);
|
|
self.resolve_node(arena, n.body);
|
|
self.exit_scope();
|
|
}
|
|
NodeKind::Peek(n) => {
|
|
self.resolve_node(arena, n.target);
|
|
self.enter_scope();
|
|
self.define_local(n.binding, arena.span(node), SymbolKind::Local);
|
|
self.resolve_node(arena, n.body);
|
|
self.exit_scope();
|
|
}
|
|
NodeKind::Cast(n) => {
|
|
self.resolve_node(arena, n.expr);
|
|
self.resolve_type_ref(arena, n.ty);
|
|
}
|
|
NodeKind::IfExpr(n) => {
|
|
self.resolve_node(arena, n.cond);
|
|
self.resolve_node(arena, n.then_block);
|
|
if let Some(else_block) = n.else_block {
|
|
self.resolve_node(arena, else_block);
|
|
}
|
|
}
|
|
NodeKind::WhenExpr(n) => {
|
|
for arm in &n.arms {
|
|
if let NodeKind::WhenArm(arm_node) = arena.kind(*arm) {
|
|
self.resolve_node(arena, arm_node.cond);
|
|
self.resolve_node(arena, arm_node.body);
|
|
}
|
|
}
|
|
}
|
|
NodeKind::Ident(n) => {
|
|
if let Some((_sym, Some(sym_id))) = self.lookup_with_id(n.name, Namespace::Value) {
|
|
self.node_to_symbol.bind_node(node, sym_id);
|
|
if let Some(ty) = self.type_facts.get_symbol_type(sym_id) {
|
|
self.type_facts.set_node_type(node, ty);
|
|
}
|
|
} else {
|
|
self.resolve_identifier(n.name, arena.span(node), Namespace::Value);
|
|
}
|
|
}
|
|
NodeKind::IntLit(_) => {
|
|
if let Some(id) = self.primitives.get("int") {
|
|
self.type_facts.set_node_type(node, *id);
|
|
}
|
|
}
|
|
NodeKind::FloatLit(_) => {
|
|
if let Some(id) = self.primitives.get("float") {
|
|
self.type_facts.set_node_type(node, *id);
|
|
}
|
|
}
|
|
NodeKind::BoundedLit(_) => {
|
|
if let Some(id) = self.primitives.get("bounded") {
|
|
self.type_facts.set_node_type(node, *id);
|
|
}
|
|
}
|
|
NodeKind::StringLit(_) => {
|
|
if let Some(id) = self.primitives.get("string") {
|
|
self.type_facts.set_node_type(node, *id);
|
|
}
|
|
}
|
|
NodeKind::MemberAccess(n) => match arena.kind(n.object) {
|
|
NodeKind::Ident(id) => {
|
|
let ident_span = arena.span(n.object);
|
|
if !self.is_builtin(id.name, Namespace::Type) {
|
|
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, ident_span, Namespace::Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
self.resolve_node(arena, n.object);
|
|
}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn resolve_fn_decl(&mut self, arena: &AstArena, _id: NodeId, n: &FnDeclNodeArena) {
|
|
self.enter_scope();
|
|
for param in &n.params {
|
|
let ty_id = self.resolve_type_ref(arena, param.ty);
|
|
let sym_id = self.define_local(param.name, param.span.clone(), SymbolKind::Local);
|
|
if let (Some(tid), Some(sid)) = (ty_id, sym_id) {
|
|
self.type_facts.set_symbol_type(sid, tid);
|
|
// The Ident node of the parameter in the declaration could also be bound
|
|
// But ParamNodeArena does not have a direct NodeId in the AST that represents the parameter's Ident.
|
|
// `param.name` is a NameId.
|
|
}
|
|
}
|
|
if let Some(ret) = n.ret {
|
|
self.resolve_type_ref(arena, ret);
|
|
}
|
|
self.resolve_node(arena, n.body);
|
|
self.exit_scope();
|
|
}
|
|
|
|
fn resolve_service_decl(&mut self, arena: &AstArena, id: NodeId, n: &ServiceDeclNodeArena) {
|
|
if let Some(ext) = n.extends {
|
|
self.resolve_identifier(ext, arena.span(id), Namespace::Type);
|
|
}
|
|
for member in &n.members {
|
|
match arena.kind(*member) {
|
|
NodeKind::ServiceFnSig(sig) => {
|
|
for param in &sig.params {
|
|
self.resolve_type_ref(arena, param.ty);
|
|
}
|
|
self.resolve_type_ref(arena, sig.ret);
|
|
}
|
|
NodeKind::ServiceFnDecl(decl) => {
|
|
// service com corpo: resolve tipos, define parâmetros como locais e resolve corpo
|
|
for param in &decl.params {
|
|
self.resolve_type_ref(arena, param.ty);
|
|
}
|
|
self.resolve_type_ref(arena, decl.ret);
|
|
|
|
self.enter_scope();
|
|
for param in &decl.params {
|
|
let sym_id = self.define_local(param.name, param.span.clone(), SymbolKind::Local);
|
|
if let Some(sym_id) = sym_id {
|
|
if let Some(ty_id) = self.resolve_type_ref(arena, param.ty) {
|
|
self.type_facts.set_symbol_type(sym_id, ty_id);
|
|
}
|
|
}
|
|
}
|
|
self.resolve_node(arena, decl.body);
|
|
self.exit_scope();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolve_type_decl(&mut self, arena: &AstArena, _id: NodeId, n: &TypeDeclNodeArena) {
|
|
for param in &n.params {
|
|
self.resolve_type_ref(arena, param.ty);
|
|
}
|
|
for constructor in &n.constructors {
|
|
if let NodeKind::ConstructorDecl(ctor) = arena.kind(*constructor) {
|
|
self.resolve_constructor_decl(arena, *constructor, ctor);
|
|
}
|
|
}
|
|
self.enter_scope();
|
|
for ctor_id in &n.constructors {
|
|
if let NodeKind::ConstructorDecl(ctor) = arena.kind(*ctor_id) {
|
|
self.define_local(ctor.name, arena.span(*ctor_id), SymbolKind::Local);
|
|
}
|
|
}
|
|
for constant in &n.constants {
|
|
self.resolve_node(arena, *constant);
|
|
}
|
|
self.exit_scope();
|
|
if let Some(body_node) = n.body {
|
|
if let NodeKind::TypeBody(body) = arena.kind(body_node) {
|
|
for member in &body.members {
|
|
self.resolve_type_ref(arena, member.ty);
|
|
}
|
|
for method in &body.methods {
|
|
if let NodeKind::ServiceFnSig(sig) = arena.kind(*method) {
|
|
for param in &sig.params {
|
|
self.resolve_type_ref(arena, param.ty);
|
|
}
|
|
self.resolve_type_ref(arena, sig.ret);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolve_constructor_decl(
|
|
&mut self,
|
|
arena: &AstArena,
|
|
_id: NodeId,
|
|
n: &ConstructorDeclNodeArena,
|
|
) {
|
|
self.enter_scope();
|
|
for param in &n.params {
|
|
self.resolve_type_ref(arena, param.ty);
|
|
self.define_local(param.name, param.span.clone(), SymbolKind::Local);
|
|
}
|
|
for init in &n.initializers {
|
|
self.resolve_node(arena, *init);
|
|
}
|
|
self.resolve_node(arena, n.body);
|
|
self.exit_scope();
|
|
}
|
|
|
|
fn resolve_block(&mut self, arena: &AstArena, n: &BlockNodeArena) {
|
|
self.enter_scope();
|
|
for stmt in &n.stmts {
|
|
self.resolve_node(arena, *stmt);
|
|
}
|
|
self.exit_scope();
|
|
}
|
|
|
|
fn resolve_let_stmt(&mut self, arena: &AstArena, id: NodeId, n: &LetStmtNodeArena) {
|
|
let ty_id = if let Some(ty) = n.ty {
|
|
self.resolve_type_ref(arena, ty)
|
|
} else {
|
|
None
|
|
};
|
|
self.resolve_node(arena, n.init);
|
|
let sym_id = self.define_local(n.name, arena.span(id), SymbolKind::Local);
|
|
|
|
if let Some(sid) = sym_id {
|
|
self.node_to_symbol.bind_node(id, sid);
|
|
if let Some(tid) = ty_id {
|
|
self.type_facts.set_symbol_type(sid, tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolve_type_ref(&mut self, arena: &AstArena, node: NodeId) -> Option<TypeId> {
|
|
let type_id = self.lower_type_node(arena, node);
|
|
if let Some(tid) = type_id {
|
|
self.type_facts.set_node_type(node, tid);
|
|
}
|
|
type_id
|
|
}
|
|
|
|
fn lower_type_node(&mut self, arena: &AstArena, node: NodeId) -> Option<TypeId> {
|
|
match arena.kind(node) {
|
|
NodeKind::TypeName(n) => {
|
|
let name = self.interner.resolve(n.name);
|
|
if let Some(prim) = self.primitives.get(name) {
|
|
return Some(*prim);
|
|
}
|
|
|
|
// Resolve struct/type symbol
|
|
if let Some((_sym, sym_id)) = self.lookup_with_id(n.name, Namespace::Type) {
|
|
// Try to find if this symbol is a type symbol that can be converted to TypeId
|
|
// For now, we only have TypeKind::Struct
|
|
if let Some(sid) = sym_id {
|
|
let kind = TypeKind::Struct { sym: sid };
|
|
return Some(self.type_arena.intern_type(kind));
|
|
} else {
|
|
// sym_id is None, but _sym exists. This means it's a built-in or something not in analysis arena yet.
|
|
// But for Struct resolution we need a SymbolId.
|
|
// If it's a built-in it should have been handled by self.primitives.
|
|
}
|
|
}
|
|
|
|
// If not found in current or imports, maybe it's a symbol from another module not yet in SymbolArena?
|
|
// Actually, lookup_with_id checks imported_symbols too.
|
|
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
|
message: format!("Unknown type: {}", name),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
None
|
|
}
|
|
NodeKind::TypeApp(n) => {
|
|
let base_name = self.interner.resolve(n.base);
|
|
match base_name {
|
|
"optional" => {
|
|
if n.args.len() != 1 {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_INVALID_ARGS".to_string(),
|
|
message: "optional<T> expects exactly 1 argument".to_string(),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
return None;
|
|
}
|
|
let inner = self.lower_type_node(arena, n.args[0])?;
|
|
Some(self.type_arena.intern_type(TypeKind::Optional { inner }))
|
|
}
|
|
"result" => {
|
|
if n.args.len() != 2 {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_INVALID_ARGS".to_string(),
|
|
message: "result<T, E> expects exactly 2 arguments".to_string(),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
return None;
|
|
}
|
|
let ok = self.lower_type_node(arena, n.args[0])?;
|
|
let err = self.lower_type_node(arena, n.args[1])?;
|
|
Some(self.type_arena.intern_type(TypeKind::Result { ok, err }))
|
|
}
|
|
"array" => {
|
|
if n.args.len() < 1 || n.args.len() > 2 {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_INVALID_ARGS".to_string(),
|
|
message: "array<T> or array<T>[N] expects 1 or 2 arguments".to_string(),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
return None;
|
|
}
|
|
let inner = self.lower_type_node(arena, n.args[0])?;
|
|
let len = if n.args.len() == 2 {
|
|
match arena.kind(n.args[1]) {
|
|
NodeKind::IntLit(lit) => Some(lit.value as u32),
|
|
_ => {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_INVALID_ARGS".to_string(),
|
|
message: "Array length must be an integer literal".to_string(),
|
|
span: arena.span(n.args[1]),
|
|
related: Vec::new(),
|
|
});
|
|
None
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
Some(self.type_arena.intern_type(TypeKind::Array { inner, len }))
|
|
}
|
|
_ => {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
|
message: format!("Unknown generic type: {}", base_name),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
None
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_NOT_A_TYPE".to_string(),
|
|
message: "Expected a type node".to_string(),
|
|
span: arena.span(node),
|
|
related: Vec::new(),
|
|
});
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolve_identifier(&mut self, name: NameId, span: Span, namespace: Namespace) -> Option<Symbol> {
|
|
if self.is_builtin(name, namespace) {
|
|
return None;
|
|
}
|
|
|
|
if let Some(sym) = self.lookup_identifier(name, namespace) {
|
|
Some(sym)
|
|
} else {
|
|
if namespace == Namespace::Type {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
|
message: format!("Unknown type: {}", self.interner.resolve(name)),
|
|
span,
|
|
related: Vec::new(),
|
|
});
|
|
} else {
|
|
self.error_undefined(name, span);
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
fn is_builtin(&self, name: NameId, namespace: Namespace) -> bool {
|
|
let name = self.interner.resolve(name);
|
|
match namespace {
|
|
Namespace::Type => match name {
|
|
"int" | "float" | "string" | "bool" | "void" | "optional" | "result" | "bounded" |
|
|
"Color" | "ButtonState" | "Pad" | "Touch" => true,
|
|
_ => false,
|
|
},
|
|
Namespace::Value => match name {
|
|
"none" | "some" | "ok" | "err" | "true" | "false" => true,
|
|
_ => false,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn lookup_identifier(&self, name: NameId, namespace: Namespace) -> Option<Symbol> {
|
|
self.lookup_with_id(name, namespace).map(|(sym, _)| sym)
|
|
}
|
|
|
|
fn lookup_with_id(&self, name: NameId, namespace: Namespace) -> Option<(Symbol, Option<SymbolId>)> {
|
|
// 1. local bindings
|
|
if namespace == Namespace::Value {
|
|
for scope in self.scopes.iter().rev() {
|
|
if let Some(pair) = scope.get(&name) {
|
|
return Some(pair.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
let table = if namespace == Namespace::Type {
|
|
&self.current_module.type_symbols
|
|
} else {
|
|
&self.current_module.value_symbols
|
|
};
|
|
|
|
// 2 & 3. file-private and module symbols
|
|
if let Some(sym) = table.get(name) {
|
|
// Se encontrarmos no módulo atual, tentamos achar o SymbolId correspondente na symbol_arena
|
|
let sym_id = self.symbol_arena.symbols.iter().enumerate().find(|(_, s)| {
|
|
s.name == sym.name && s.kind == match sym.kind {
|
|
SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function,
|
|
SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service,
|
|
SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct,
|
|
SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract,
|
|
SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType,
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
}
|
|
}).map(|(i, _)| SymbolId(i as u32));
|
|
|
|
return Some((sym.clone(), sym_id));
|
|
}
|
|
|
|
// 4. imported symbols
|
|
let imp_table = if namespace == Namespace::Type {
|
|
&self.imported_symbols.type_symbols
|
|
} else {
|
|
&self.imported_symbols.value_symbols
|
|
};
|
|
if let Some(sym) = imp_table.get(name) {
|
|
let sym_id = self.symbol_arena.symbols.iter().enumerate().find(|(_, s)| {
|
|
s.name == sym.name && s.kind == match sym.kind {
|
|
SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function,
|
|
SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service,
|
|
SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct,
|
|
SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract,
|
|
SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType,
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
}
|
|
}).map(|(i, _)| SymbolId(i as u32));
|
|
|
|
return Some((sym.clone(), sym_id));
|
|
}
|
|
|
|
// 5. Fallback for constructor calls: check Type namespace if looking for a Value
|
|
if namespace == Namespace::Value {
|
|
if let Some(sym) = self.current_module.type_symbols.get(name) {
|
|
if sym.kind == SymbolKind::Struct {
|
|
return Some((sym.clone(), None));
|
|
}
|
|
}
|
|
if let Some(sym) = self.imported_symbols.type_symbols.get(name) {
|
|
if sym.kind == SymbolKind::Struct {
|
|
return Some((sym.clone(), None));
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn define_local(&mut self, name: NameId, span: Span, kind: SymbolKind) -> Option<SymbolId> {
|
|
let scope = self.scopes.last_mut().expect("No scope to define local");
|
|
|
|
// Check for collision in Type namespace at top-level?
|
|
// Actually, the spec says "A name may not exist in both namespaces".
|
|
// If we want to be strict, we check current module's type symbols too.
|
|
if let Some(existing) = self.current_module.type_symbols.get(name) {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_NAMESPACE_COLLISION".to_string(),
|
|
message: format!(
|
|
"Local variable '{}' collides with a type name",
|
|
self.interner.resolve(name)
|
|
),
|
|
span,
|
|
related: vec![("type defined here".to_string(), existing.span.clone())],
|
|
});
|
|
return None;
|
|
}
|
|
|
|
if let Some((prev_sym, _)) = scope.get(&name) {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(),
|
|
message: format!("Duplicate local variable '{}'", self.interner.resolve(name)),
|
|
span,
|
|
related: vec![("previous definition here".to_string(), prev_sym.span.clone())],
|
|
});
|
|
None
|
|
} else {
|
|
let symbol = Symbol {
|
|
name,
|
|
kind,
|
|
namespace: Namespace::Value,
|
|
visibility: Visibility::FilePrivate,
|
|
ty: None, // Will be set by TypeChecker
|
|
is_host: false,
|
|
span: span.clone(),
|
|
origin: None,
|
|
};
|
|
|
|
// Criar símbolo na symbol_arena (análise global)
|
|
let sym_id = self.symbol_arena.insert(crate::analysis::symbols::Symbol {
|
|
name,
|
|
kind: match kind {
|
|
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
|
|
_ => crate::analysis::symbols::SymbolKind::Value,
|
|
},
|
|
exported: false,
|
|
module: ModuleId(0), // TODO: set actual module id when available
|
|
decl_span: span,
|
|
});
|
|
|
|
scope.insert(name, (symbol, Some(sym_id)));
|
|
Some(sym_id)
|
|
}
|
|
}
|
|
|
|
fn enter_scope(&mut self) {
|
|
self.scopes.push(HashMap::new());
|
|
}
|
|
|
|
fn exit_scope(&mut self) {
|
|
self.scopes.pop();
|
|
}
|
|
|
|
fn error_undefined(&mut self, name: NameId, span: Span) {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_UNDEFINED".to_string(),
|
|
message: format!("Undefined identifier: {}", self.interner.resolve(name)),
|
|
span,
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
|
|
fn error_duplicate_import(&mut self, name: NameId, span: Span) {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(),
|
|
message: format!("Duplicate import: {}", self.interner.resolve(name)),
|
|
span,
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
|
|
fn error_visibility(&mut self, sym: &Symbol, span: Span) {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: "E_RESOLVE_VISIBILITY".to_string(),
|
|
message: format!(
|
|
"DebugSymbol '{}' is not visible here",
|
|
self.interner.resolve(sym.name)
|
|
),
|
|
span,
|
|
related: vec![("symbol defined here".to_string(), sym.span.clone())],
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::common::files::FileManager;
|
|
use crate::common::spans::Span;
|
|
use crate::frontends::pbs::*;
|
|
use std::path::PathBuf;
|
|
|
|
fn setup_test(source: &str) -> (ParsedAst, usize, NameInterner) {
|
|
let mut fm = FileManager::new();
|
|
let file_id = fm.add(PathBuf::from("test.pbs"), source.to_string());
|
|
let mut parser = parser::Parser::new(source, crate::common::spans::FileId(file_id as u32), fm.interner_mut());
|
|
let parsed = parser.parse_file().expect("Parsing failed");
|
|
(parsed, file_id, fm.interner().clone())
|
|
}
|
|
|
|
#[test]
|
|
fn test_duplicate_symbols() {
|
|
let source = "
|
|
declare struct Foo()
|
|
declare struct Foo()
|
|
";
|
|
let (parsed, _, interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let result = collector.collect(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.unwrap_err();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_DUPLICATE_SYMBOL"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_namespace_collision() {
|
|
let source = "
|
|
declare struct Foo()
|
|
fn Foo() {}
|
|
";
|
|
let (parsed, _, interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let result = collector.collect(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.unwrap_err();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_NAMESPACE_COLLISION"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_undefined_identifier() {
|
|
let source = "
|
|
fn main() {
|
|
let x = y;
|
|
}
|
|
";
|
|
let (parsed, _, interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Collection failed");
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
let result = resolver.resolve(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.unwrap_err();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_local_variable_resolution() {
|
|
let source = "
|
|
fn main() {
|
|
let x = 10;
|
|
let y = x;
|
|
}
|
|
";
|
|
let (parsed, _, interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Collection failed");
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
let result = resolver.resolve(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_visibility_error() {
|
|
let source = "
|
|
import PrivateType from \"./other.pbs\"
|
|
fn main() {}
|
|
";
|
|
let (parsed, _, mut interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Collection failed");
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct MockProvider {
|
|
other: ModuleSymbols,
|
|
}
|
|
impl ModuleProvider for MockProvider {
|
|
fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> {
|
|
if path == "./other.pbs" { Some(&self.other) } else { None }
|
|
}
|
|
}
|
|
|
|
let mut other_ts = SymbolTable::new();
|
|
other_ts.insert(Symbol {
|
|
name: interner.intern("PrivateType"),
|
|
kind: SymbolKind::Struct,
|
|
namespace: Namespace::Type,
|
|
visibility: Visibility::FilePrivate,
|
|
ty: None,
|
|
is_host: false,
|
|
span: Span::new(crate::common::spans::FileId(1), 0, 0),
|
|
origin: None,
|
|
}).unwrap();
|
|
|
|
let mock_provider = MockProvider {
|
|
other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() },
|
|
};
|
|
|
|
let mut resolver = Resolver::new(&ms, &mock_provider, &interner);
|
|
let result = resolver.resolve(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.unwrap_err();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_VISIBILITY"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_import_resolution() {
|
|
let source = "
|
|
import PubType from \"./other.pbs\"
|
|
fn main() {
|
|
let x: PubType = 10;
|
|
}
|
|
";
|
|
let (parsed, _, mut interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Collection failed");
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct MockProvider {
|
|
other: ModuleSymbols,
|
|
}
|
|
impl ModuleProvider for MockProvider {
|
|
fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> {
|
|
if path == "./other.pbs" { Some(&self.other) } else { None }
|
|
}
|
|
}
|
|
|
|
let mut other_ts = SymbolTable::new();
|
|
other_ts.insert(Symbol {
|
|
name: interner.intern("PubType"),
|
|
kind: SymbolKind::Struct,
|
|
namespace: Namespace::Type,
|
|
visibility: Visibility::Pub,
|
|
ty: None,
|
|
is_host: false,
|
|
span: Span::new(crate::common::spans::FileId(1), 0, 0),
|
|
origin: None,
|
|
}).unwrap();
|
|
|
|
let mock_provider = MockProvider {
|
|
other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() },
|
|
};
|
|
|
|
let mut resolver = Resolver::new(&ms, &mock_provider, &interner);
|
|
let result = resolver.resolve(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_import_module_not_found() {
|
|
let source = "
|
|
import NonExistent from \"./missing.pbs\"
|
|
fn main() {}
|
|
";
|
|
let (parsed, _, interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Collection failed");
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
let result = resolver.resolve(&parsed.arena, parsed.root);
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.unwrap_err();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_INVALID_IMPORT"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_typing_literals_and_binary() {
|
|
let source = "
|
|
fn main() {
|
|
let a = 1 + 2;
|
|
let b = 1 == 2;
|
|
let c = -1;
|
|
}
|
|
";
|
|
let (parsed, _, mut interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap();
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
// Ensure primitives are interned for bootstrap
|
|
for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); }
|
|
let interner_for_bootstrap = interner.clone();
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
resolver.bootstrap_types(&interner);
|
|
resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed");
|
|
|
|
// Verify types in TypeFacts using format_type for easier assertion
|
|
use crate::analysis::types::format_type;
|
|
|
|
let mut found_a = false;
|
|
let mut found_b = false;
|
|
let mut found_c = false;
|
|
|
|
for i in 0..parsed.arena.nodes.len() {
|
|
let id = NodeId(i as u32);
|
|
let kind = parsed.arena.kind(id);
|
|
|
|
match kind {
|
|
NodeKind::Binary(n) if n.op == "+" => {
|
|
let ty = resolver.type_facts.get_node_type(id).expect("Binary + should have type");
|
|
assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int");
|
|
found_a = true;
|
|
}
|
|
NodeKind::Binary(n) if n.op == "==" => {
|
|
let ty = resolver.type_facts.get_node_type(id).expect("Binary == should have type");
|
|
assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "bool");
|
|
found_b = true;
|
|
}
|
|
NodeKind::Unary(n) if n.op == "-" => {
|
|
let ty = resolver.type_facts.get_node_type(id).expect("Unary - should have type");
|
|
assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int");
|
|
found_c = true;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
assert!(found_a, "Binary + node not found");
|
|
assert!(found_b, "Binary == node not found");
|
|
assert!(found_c, "Unary - node not found");
|
|
}
|
|
|
|
#[test]
|
|
fn test_hover_let_annotated_type() {
|
|
let source = "
|
|
fn main() {
|
|
let x: int = 10;
|
|
let y = x;
|
|
}
|
|
";
|
|
let (parsed, _, mut interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap();
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); }
|
|
let interner_for_bootstrap = interner.clone();
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
resolver.bootstrap_types(&interner);
|
|
resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed");
|
|
|
|
use crate::analysis::types::format_type;
|
|
|
|
let mut found_y_ref = false;
|
|
|
|
for i in 0..parsed.arena.nodes.len() {
|
|
let id = NodeId(i as u32);
|
|
let kind = parsed.arena.kind(id);
|
|
|
|
if let NodeKind::LetStmt(n) = kind {
|
|
if interner.resolve(n.name) == "y" {
|
|
let init_id = n.init;
|
|
if let NodeKind::Ident(ident) = parsed.arena.kind(init_id) {
|
|
if interner.resolve(ident.name) == "x" {
|
|
let ty = resolver.type_facts.get_node_type(init_id).expect("Ident x should have type");
|
|
assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int");
|
|
found_y_ref = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(found_y_ref, "Reference to x in let y = x not found or not typed");
|
|
}
|
|
|
|
#[test]
|
|
fn test_hover_param_type() {
|
|
let source = "
|
|
fn foo(a: int) {
|
|
let b = a;
|
|
}
|
|
";
|
|
let (parsed, _, mut interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap();
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); }
|
|
let interner_for_bootstrap = interner.clone();
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
resolver.bootstrap_types(&interner);
|
|
resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed");
|
|
|
|
use crate::analysis::types::format_type;
|
|
|
|
let mut found_a_ref = false;
|
|
|
|
for i in 0..parsed.arena.nodes.len() {
|
|
let id = NodeId(i as u32);
|
|
let kind = parsed.arena.kind(id);
|
|
|
|
if let NodeKind::LetStmt(n) = kind {
|
|
if interner.resolve(n.name) == "b" {
|
|
let init_id = n.init;
|
|
if let NodeKind::Ident(ident) = parsed.arena.kind(init_id) {
|
|
if interner.resolve(ident.name) == "a" {
|
|
let ty = resolver.type_facts.get_node_type(init_id).expect("Ident a should have type");
|
|
assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int");
|
|
found_a_ref = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(found_a_ref, "Reference to a in let b = a not found or not typed");
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_type_node_complex() {
|
|
let source = "
|
|
declare struct MyType()
|
|
fn main() {
|
|
let x: optional<int> = 1;
|
|
let y: result<int, string> = 2;
|
|
let z: array<float>[10] = 3;
|
|
let w: MyType = 4;
|
|
}
|
|
";
|
|
let (parsed, _, mut interner) = setup_test(source);
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap();
|
|
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
|
|
|
|
struct EmptyProvider;
|
|
impl ModuleProvider for EmptyProvider {
|
|
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); }
|
|
let interner_for_bootstrap = interner.clone();
|
|
let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner);
|
|
resolver.bootstrap_types(&interner);
|
|
resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed");
|
|
|
|
use crate::analysis::types::format_type;
|
|
|
|
let mut found_x = false;
|
|
let mut found_y = false;
|
|
let mut found_z = false;
|
|
let mut found_w = false;
|
|
|
|
for i in 0..parsed.arena.nodes.len() {
|
|
let id = NodeId(i as u32);
|
|
let kind = parsed.arena.kind(id);
|
|
|
|
if let NodeKind::LetStmt(n) = kind {
|
|
let name = interner.resolve(n.name);
|
|
let sym_id = resolver.node_to_symbol.get(id).expect(&format!("Node {:?} should be bound to a symbol", kind));
|
|
let ty_id = resolver.type_facts.get_symbol_type(sym_id).expect("Symbol should have type");
|
|
|
|
match name {
|
|
"x" => {
|
|
assert_eq!(format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None), "optional<int>");
|
|
found_x = true;
|
|
}
|
|
"y" => {
|
|
assert_eq!(format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None), "result<int, string>");
|
|
found_y = true;
|
|
}
|
|
"z" => {
|
|
assert_eq!(format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None), "array<float>[10]");
|
|
found_z = true;
|
|
}
|
|
"w" => {
|
|
// MyType is a struct. Since we don't pass symbols to format_type in this test, it should be struct#ID
|
|
let formatted = format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None);
|
|
assert!(formatted.starts_with("struct#"), "Formatted type should be struct#ID, got {}", formatted);
|
|
found_w = true;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(found_x);
|
|
assert!(found_y);
|
|
assert!(found_z);
|
|
assert!(found_w);
|
|
}
|
|
}
|