This commit is contained in:
bQUARKz 2026-02-04 19:36:51 +00:00
parent 6cbcddfd99
commit 89163807b4
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 120 additions and 36 deletions

View File

@ -103,6 +103,16 @@ impl DefIndex {
pub fn get(&self, key: DefKey) -> Option<SymbolId> {
self.symbols.get(&key).copied()
}
/// Lookup by name/namespace ignoring module. Returns the first match with its module id.
pub fn get_by_name_any_module(&self, name: NameId, namespace: Namespace) -> Option<(u32, SymbolId)> {
for (k, v) in &self.symbols {
if k.name == name && k.namespace == namespace {
return Some((k.module, *v));
}
}
None
}
}
impl RefIndex {

View File

@ -7,6 +7,7 @@ pub fn build_def_index(
arena: &AstArena,
module: u32,
_interner: &NameInterner,
imports: Option<(&SymbolArena, &DefIndex)>,
) -> (SymbolArena, DefIndex, RefIndex, NodeToSymbol, Vec<Diagnostic>) {
let mut symbols = SymbolArena::new();
let mut index = DefIndex::new();
@ -69,7 +70,7 @@ pub fn build_def_index(
// Passo 2: Resolver referências (Identifiers)
for &root_id in &arena.roots {
walk_node(root_id, arena, module, &index, &mut ref_index, &mut node_to_symbol, &mut diagnostics);
walk_node(root_id, arena, module, &index, imports, &mut ref_index, &mut node_to_symbol, &mut diagnostics);
}
(symbols, index, ref_index, node_to_symbol, diagnostics)
@ -80,6 +81,7 @@ fn walk_node(
arena: &AstArena,
module: u32,
index: &DefIndex,
imports: Option<(&SymbolArena, &DefIndex)>,
ref_index: &mut RefIndex,
node_to_symbol: &mut NodeToSymbol,
diagnostics: &mut Vec<Diagnostic>,
@ -90,29 +92,29 @@ fn walk_node(
match kind {
NodeKind::File(file) => {
for &decl in &file.decls {
walk_node(decl, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(decl, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::FnDecl(fn_decl) => {
walk_node(fn_decl.body, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(fn_decl.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Block(block) => {
for &stmt in &block.stmts {
walk_node(stmt, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(stmt, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
if let Some(tail) = block.tail {
walk_node(tail, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(tail, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::LetStmt(let_stmt) => {
walk_node(let_stmt.init, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(let_stmt.init, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::ExprStmt(expr_stmt) => {
walk_node(expr_stmt.expr, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(expr_stmt.expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::ReturnStmt(ret) => {
if let Some(expr) = ret.expr {
walk_node(expr, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::Ident(ident) => {
@ -125,6 +127,27 @@ fn walk_node(
if let Some(symbol_id) = index.get(key) {
ref_index.record_ref(symbol_id, span);
node_to_symbol.bind_node(node_id, symbol_id);
} else if let Some((import_arena, import_index)) = imports {
if let Some((_, symbol_id)) = import_index.get_by_name_any_module(ident.name, Namespace::Value) {
let symbol = import_arena.get(symbol_id);
if !symbol.exported && symbol.module != module {
diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
code: Some("E_RESOLVE_VISIBILITY".to_string()),
message: format!("Symbol is not exported from module {}", symbol.module),
span: Some(span),
});
}
ref_index.record_ref(symbol_id, span);
node_to_symbol.bind_node(node_id, symbol_id);
} else {
diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
code: Some("E_RESOLVE_UNDEFINED_IDENTIFIER".to_string()),
message: format!("Undefined identifier"),
span: Some(span),
});
}
} else {
diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
@ -135,68 +158,68 @@ fn walk_node(
}
}
NodeKind::Call(call) => {
walk_node(call.callee, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(call.callee, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
for &arg in &call.args {
walk_node(arg, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(arg, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::Unary(unary) => {
walk_node(unary.expr, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(unary.expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Binary(binary) => {
walk_node(binary.left, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(binary.right, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(binary.left, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
walk_node(binary.right, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Cast(cast) => {
walk_node(cast.expr, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(cast.expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::IfExpr(if_expr) => {
walk_node(if_expr.cond, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(if_expr.then_block, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(if_expr.cond, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
walk_node(if_expr.then_block, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
if let Some(else_block) = if_expr.else_block {
walk_node(else_block, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(else_block, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::WhenExpr(when_expr) => {
for &arm in &when_expr.arms {
walk_node(arm, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(arm, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::WhenArm(when_arm) => {
walk_node(when_arm.cond, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(when_arm.body, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(when_arm.cond, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
walk_node(when_arm.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Alloc(alloc) => {
walk_node(alloc.ty, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(alloc.ty, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Mutate(mutate) => {
walk_node(mutate.target, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(mutate.body, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(mutate.target, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
walk_node(mutate.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Borrow(borrow) => {
walk_node(borrow.target, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(borrow.body, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(borrow.target, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
walk_node(borrow.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::Peek(peek) => {
walk_node(peek.target, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(peek.body, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(peek.target, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
walk_node(peek.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::MemberAccess(member) => {
walk_node(member.object, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(member.object, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
NodeKind::TypeDecl(type_decl) => {
if let Some(body) = type_decl.body {
walk_node(body, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::TypeBody(body) => {
for &method in &body.methods {
walk_node(method, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(method, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
NodeKind::ServiceDecl(service) => {
for &member in &service.members {
walk_node(member, arena, module, index, ref_index, node_to_symbol, diagnostics);
walk_node(member, arena, module, index, imports, ref_index, node_to_symbol, diagnostics);
}
}
// Nós literais ou que não contém outros nós para percorrer (neste nível)
@ -252,7 +275,7 @@ mod tests {
arena.roots.push(file_id);
let (symbols, index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner);
let (symbols, index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
assert!(diagnostics.is_empty());
assert_eq!(symbols.symbols.len(), 2);
@ -304,7 +327,7 @@ mod tests {
arena.roots.push(file_id);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()));
@ -334,7 +357,7 @@ mod tests {
arena.roots.push(file_id);
let (symbols, _index, _ref_index, node_to_symbol, _diagnostics) = build_def_index(&arena, 1, &interner);
let (symbols, _index, _ref_index, node_to_symbol, _diagnostics) = build_def_index(&arena, 1, &interner, None);
let symbol_id = node_to_symbol.get(decl_id).expect("Node should be bound to a symbol");
let symbol = symbols.get(symbol_id);
@ -367,7 +390,7 @@ mod tests {
arena.roots.push(file_id);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, Some("E_RESOLVE_UNDEFINED_IDENTIFIER".to_string()));
@ -413,7 +436,7 @@ mod tests {
arena.roots.push(file_id);
let (symbols, index, ref_index, node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner);
let (symbols, index, ref_index, node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
assert!(diagnostics.is_empty(), "Diagnostics should be empty: {:?}", diagnostics);
@ -425,4 +448,55 @@ mod tests {
let bound_id = node_to_symbol.get(ident_id).expect("ident should be bound to symbol");
assert_eq!(bound_id, target_sym_id);
}
#[test]
fn test_resolve_visibility_violation() {
let mut interner = NameInterner::new();
// Módulo 1: define função privada
let mut arena1 = AstArena::default();
let target_name = interner.intern("private_func");
let body1 = arena1.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(0, 0, 0));
let decl1 = arena1.push(NodeKind::FnDecl(FnDeclNodeArena {
vis: None, // Privado
name: target_name,
params: vec![],
ret: None,
else_fallback: None,
body: body1,
}), Span::new(0, 0, 10));
let file1 = arena1.push(NodeKind::File(FileNodeArena {
imports: vec![],
decls: vec![decl1],
}), Span::new(0, 0, 100));
arena1.roots.push(file1);
let (symbols1, index1, _, _, _) = build_def_index(&arena1, 1, &interner, None);
// Módulo 2: tenta usar função privada do Módulo 1
let mut arena2 = AstArena::default();
let ident_id = arena2.push(NodeKind::Ident(IdentNodeArena { name: target_name }), Span::new(0, 50, 62));
let expr_stmt = arena2.push(NodeKind::ExprStmt(ExprStmtNodeArena { expr: ident_id }), Span::new(0, 50, 62));
let body2 = arena2.push(NodeKind::Block(BlockNodeArena { stmts: vec![expr_stmt], tail: None }), Span::new(0, 40, 70));
let caller = arena2.push(NodeKind::FnDecl(FnDeclNodeArena {
vis: Some("pub".to_string()),
name: interner.intern("caller"),
params: vec![],
ret: None,
else_fallback: None,
body: body2,
}), Span::new(0, 30, 80));
let file2 = arena2.push(NodeKind::File(FileNodeArena {
imports: vec![],
decls: vec![caller],
}), Span::new(0, 0, 100));
arena2.roots.push(file2);
let (_symbols2, _index2, _ref_index2, _node_to_symbol2, diagnostics) =
build_def_index(&arena2, 2, &interner, Some((&symbols1, &index1)));
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, Some("E_RESOLVE_VISIBILITY".to_string()));
assert_eq!(diagnostics[0].span, Some(Span::new(0, 50, 62)));
}
}