diff --git a/crates/prometeu-compiler/src/analysis/types.rs b/crates/prometeu-compiler/src/analysis/types.rs index c84f8828..818cd214 100644 --- a/crates/prometeu-compiler/src/analysis/types.rs +++ b/crates/prometeu-compiler/src/analysis/types.rs @@ -1,6 +1,5 @@ use crate::analysis::symbols::{SymbolArena}; -use prometeu_analysis::{NameId, NameInterner, TypeId, SymbolId, NodeId, ModuleId}; -use crate::common::spans::FileId; +use prometeu_analysis::{NameId, NameInterner, TypeId, SymbolId, NodeId}; use serde::{Deserialize, Serialize}; // Use canonical TypeId from prometeu-analysis @@ -112,6 +111,7 @@ impl TypeFacts { #[cfg(test)] mod tests { + use prometeu_analysis::{FileId, ModuleId}; use super::*; // Mock NameId and SymbolId for simplified tests if necessary, // or use real values. diff --git a/crates/prometeu-compiler/src/building/orchestrator.rs b/crates/prometeu-compiler/src/building/orchestrator.rs index f682a4d7..0b22f2cc 100644 --- a/crates/prometeu-compiler/src/building/orchestrator.rs +++ b/crates/prometeu-compiler/src/building/orchestrator.rs @@ -1,11 +1,11 @@ use crate::building::linker::{LinkError, Linker}; use crate::building::output::{compile_project, CompileError}; use crate::building::plan::{BuildPlan, BuildTarget}; -use crate::common::files::FileManager; use crate::common::diagnostics::DiagnosticBundle; +use crate::common::files::FileManager; use crate::deps::resolver::ResolvedGraph; -use std::collections::HashMap; use prometeu_abi::virtual_machine::ProgramImage; +use std::collections::HashMap; #[derive(Debug)] pub enum BuildError { @@ -150,11 +150,10 @@ pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result) -> std::fmt::Result { match self { CompileError::Frontend(d) => write!(f, "Frontend error: {:?}", d), - CompileError::DuplicateExport { symbol, first_dep, second_dep } => { - write!(f, "duplicate export: symbol `{}`\n first defined in dependency `{}`\n again defined in dependency `{}`", symbol, first_dep, second_dep) - } + CompileError::DuplicateExport { + symbol, + first_dep, + second_dep, + } => write!( + f, + "duplicate export: symbol `{}`\n first defined in dependency `{}`\n again defined in dependency `{}`", + symbol, first_dep, second_dep + ), CompileError::Io(e) => write!(f, "IO error: {}", e), CompileError::Internal(s) => write!(f, "Internal error: {}", s), } @@ -116,21 +121,22 @@ pub fn compile_project( file_manager: &mut FileManager, ) -> Result { let mut interner = NameInterner::new(); - // 1. Parse all files and group symbols by module + + // 1) Parse all files; collect+merge symbols by module_path let mut module_symbols_map: HashMap = HashMap::new(); - let mut parsed_files: Vec<(String, ParsedAst)> = Vec::new(); // (module_path, ast) + let mut parsed_files: Vec<(String, ParsedAst)> = Vec::new(); for source_rel in &step.sources { let source_abs = step.project_dir.join(source_rel); let source_code = std::fs::read_to_string(&source_abs)?; let file_id = file_manager.add(source_abs.clone(), source_code.clone()); - + let mut parser = Parser::new(&source_code, FileId(file_id as u32), &mut interner); let parsed = parser.parse_file()?; let mut collector = SymbolCollector::new(&interner); let (ts, vs) = collector.collect(&parsed.arena, parsed.root)?; - + let full_path = source_rel.to_string_lossy().replace('\\', "/"); let logical_module_path = if let Some(stripped) = full_path.strip_prefix("src/main/modules/") { stripped @@ -144,183 +150,207 @@ pub fn compile_project( .parent() .map(|p| p.to_string_lossy().replace('\\', "/")) .unwrap_or_else(|| "".to_string()); - - let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new); - - // Merge symbols - for sym in ts.symbols.into_values() { - if let Some(existing) = ms.type_symbols.get(sym.name) { - return Err(DiagnosticBundle::error( - "E_RESOLVE_DUPLICATE_SYMBOL", - format!( - "Duplicate type symbol '{}' in module '{}'", - interner.resolve(existing.name), - module_path - ), - existing.span.clone(), - ).into()); + + let ms = module_symbols_map + .entry(module_path.clone()) + .or_insert_with(ModuleSymbols::new); + + // Merge Type symbols (no overload): reject duplicates + for list in ts.symbols.into_values() { + for sym in list { + if let Some(existing) = ms.type_symbols.get(sym.name) { + return Err(DiagnosticBundle::error( + "E_RESOLVE_DUPLICATE_SYMBOL", + format!( + "Duplicate type symbol '{}' in module '{}'", + interner.resolve(existing.name), + module_path + ), + existing.span.clone(), + ) + .into()); + } + let _ = ms.type_symbols.insert(sym); } - let _ = ms.type_symbols.insert(sym); } - for sym in vs.symbols.into_values() { - if let Some(existing) = ms.value_symbols.get(sym.name) { - return Err(DiagnosticBundle::error( - "E_RESOLVE_DUPLICATE_SYMBOL", - format!( - "Duplicate value symbol '{}' in module '{}'", - interner.resolve(existing.name), - module_path - ), - existing.span.clone(), - ).into()); + + // Merge Value symbols: allow overloads only for Function kind + for list in vs.symbols.into_values() { + for sym in list { + if let Some(existing) = ms.value_symbols.get(sym.name) { + if !(existing.kind == SymbolKind::Function && sym.kind == SymbolKind::Function) { + return Err(DiagnosticBundle::error( + "E_RESOLVE_DUPLICATE_SYMBOL", + format!( + "Duplicate value symbol '{}' in module '{}'", + interner.resolve(existing.name), + module_path + ), + existing.span.clone(), + ) + .into()); + } + } + let _ = ms.value_symbols.insert(sym); } - let _ = ms.value_symbols.insert(sym); } - + parsed_files.push((module_path, parsed)); } - // 2. Synthesize ModuleSymbols for dependencies + // 2) Synthesize visible ModuleSymbols for dependencies (under synthetic module paths) let mut all_visible_modules = module_symbols_map.clone(); + for (alias, project_id) in &step.deps { - if let Some(compiled) = dep_modules.get(project_id) { - for (key, meta) in &compiled.exports { - // Support syntax: "alias/module" and "@alias:module" - let key_module_path = &key.module_path; - let synthetic_paths = [ - format!("{}/{}", alias, key_module_path), - format!("@{}:{}", alias, key_module_path), - ]; - - for synthetic_module_path in synthetic_paths { - let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new); - - let sym = Symbol { - name: interner.intern(&key.symbol_name), - kind: match key.kind { - ExportSurfaceKind::Service => SymbolKind::Service, - ExportSurfaceKind::DeclareType => { - match &meta.ty { - Some(PbsType::Contract(_)) => SymbolKind::Contract, - Some(PbsType::ErrorType(_)) => SymbolKind::ErrorType, - _ => SymbolKind::Struct, - } - } - ExportSurfaceKind::Function => SymbolKind::Function, + let Some(compiled) = dep_modules.get(project_id) else { continue }; + + for (key, meta) in &compiled.exports { + let key_module_path = &key.module_path; + + // Support both import styles: + // - "alias/module" + // - "@alias:module" + let synthetic_paths = [ + format!("{}/{}", alias, key_module_path), + format!("@{}:{}", alias, key_module_path), + ]; + + for synthetic_module_path in synthetic_paths { + let ms = all_visible_modules + .entry(synthetic_module_path.clone()) + .or_insert_with(ModuleSymbols::new); + + let sym = Symbol { + name: interner.intern(&key.symbol_name), + kind: match key.kind { + ExportSurfaceKind::Service => SymbolKind::Service, + ExportSurfaceKind::DeclareType => match &meta.ty { + Some(PbsType::Contract(_)) => SymbolKind::Contract, + Some(PbsType::ErrorType(_)) => SymbolKind::ErrorType, + _ => SymbolKind::Struct, }, - namespace: key.kind.namespace(), - visibility: Visibility::Pub, - ty: meta.ty.clone(), - is_host: meta.is_host, - span: Span::new(FileId::INVALID, 0, 0), - origin: Some(synthetic_module_path.clone()), - }; - - if sym.namespace == Namespace::Type { - if let Some(existing) = ms.type_symbols.get(sym.name) { - return Err(CompileError::DuplicateExport { - symbol: interner.resolve(sym.name).to_string(), - first_dep: existing.origin.clone().unwrap_or_else(|| "unknown".to_string()), - second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()), - }); - } - let _ = ms.type_symbols.insert(sym.clone()); - } else { - if let Some(existing) = ms.value_symbols.get(sym.name) { - return Err(CompileError::DuplicateExport { - symbol: interner.resolve(sym.name).to_string(), - first_dep: existing.origin.clone().unwrap_or_else(|| "unknown".to_string()), - second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()), - }); - } - let _ = ms.value_symbols.insert(sym.clone()); + ExportSurfaceKind::Function => SymbolKind::Function, + }, + namespace: key.kind.namespace(), + visibility: Visibility::Pub, + ty: meta.ty.clone(), + is_host: meta.is_host, + span: Span::new(FileId::INVALID, 0, 0), + origin: Some(synthetic_module_path.clone()), + }; + + if sym.namespace == Namespace::Type { + if let Some(existing) = ms.type_symbols.get(sym.name) { + return Err(CompileError::DuplicateExport { + symbol: interner.resolve(sym.name).to_string(), + first_dep: existing.origin.clone().unwrap_or_else(|| "unknown".to_string()), + second_dep: sym.origin.clone().unwrap_or_else(|| "unknown".to_string()), + }); } + let _ = ms.type_symbols.insert(sym); + } else { + if let Some(existing) = ms.value_symbols.get(sym.name) { + if !(existing.kind == SymbolKind::Function && sym.kind == SymbolKind::Function) { + return Err(CompileError::DuplicateExport { + symbol: interner.resolve(sym.name).to_string(), + first_dep: existing.origin.clone().unwrap_or_else(|| "unknown".to_string()), + second_dep: sym.origin.clone().unwrap_or_else(|| "unknown".to_string()), + }); + } + } + let _ = ms.value_symbols.insert(sym); } } } } - // 3. Resolve and TypeCheck each file - let module_provider = ProjectModuleProvider { + // 3) Resolve imports and type each file; keep imported_symbols per module_path for Lowerer + let module_provider = ProjectModuleProvider { modules: all_visible_modules, }; - - // We need to collect imported symbols for Lowerer - let mut file_imported_symbols: HashMap = HashMap::new(); // keyed by module_path + + let mut file_imported_symbols: HashMap = HashMap::new(); + + // Ensure primitive names are interned early (resolver/typecheck may depend on NameIds) + { + let primitives = ["int", "bool", "float", "string", "bounded", "void", "error", "result"]; + for p in primitives { + interner.intern(p); + } + } for (module_path, parsed) in &parsed_files { - let ms = module_symbols_map.get(module_path).unwrap(); - // Ensure primitive names are interned before creating resolver/bootstrap - { - let primitives = ["int", "bool", "float", "string", "bounded", "void"]; - for p in primitives { interner.intern(p); } - } + let ms = module_symbols_map + .get(module_path) + .ok_or_else(|| CompileError::Internal(format!("Missing module_symbols for '{}'", module_path)))?; + let mut resolver = Resolver::new(ms, &module_provider, &interner); resolver.bootstrap_types(&interner); resolver.resolve(&parsed.arena, parsed.root)?; - - // Capture imported symbols + file_imported_symbols.insert(module_path.clone(), resolver.imported_symbols.clone()); - // TypeChecker also needs &mut ModuleSymbols - let mut ms_mut = module_symbols_map.get_mut(module_path).unwrap(); - let imported = file_imported_symbols.get(module_path).unwrap(); - let mut typechecker = TypeChecker::new(&mut ms_mut, imported, &module_provider, &interner); - typechecker.check(&parsed.arena, parsed.root)?; + // Unified typed-symbol builder: typecheck the already-collected module symbols in place + let imported = file_imported_symbols + .get(module_path) + .ok_or_else(|| CompileError::Internal(format!("Missing imported_symbols for '{}'", module_path)))?; + + let ms_mut = module_symbols_map + .get_mut(module_path) + .ok_or_else(|| CompileError::Internal(format!("Missing module_symbols (mut) for '{}'", module_path)))?; + + build_typed_module_symbols(parsed, ms_mut, imported, &mut interner)?; } - // 4. Lower ALL modules to VM and emit a single combined bytecode image for this project - // Rationale: services and functions can live in multiple modules; exports must refer to - // correct function indices within this CompiledModule. We aggregate all VM functions - // into a single ir_vm::Module and assemble once using the public API `emit_fragments`. - - // Combined VM module (we will merge const pools and remap ConstIds accordingly) + // 4) Lower ALL sources into a single combined VM module (so exports func_idx match final image) let mut combined_vm = crate::ir_vm::Module::new(step.project_key.name.clone()); combined_vm.const_pool = crate::ir_core::ConstPool::new(); - // Track origin module_path for each function we append to combined_vm + + // Origin module_path per appended function let mut combined_func_origins: Vec = Vec::new(); - // Helper to insert a constant value into combined pool and return its new id - let mut insert_const = |pool: &mut crate::ir_core::ConstPool, val: &crate::ir_core::ConstantValue| -> crate::ir_vm::types::ConstId { - let new_id = pool.insert(val.clone()); - crate::ir_vm::types::ConstId(new_id.0) - }; + let insert_const = + |pool: &mut crate::ir_core::ConstPool, val: &crate::ir_core::ConstantValue| -> crate::ir_vm::types::ConstId { + let new_id = pool.insert(val.clone()); + crate::ir_vm::types::ConstId(new_id.0) + }; - // Accumulate VM functions from each source file, remapping ConstIds as we go for (module_path, parsed) in &parsed_files { let ms = module_symbols_map.get(module_path).unwrap(); let imported = file_imported_symbols.get(module_path).unwrap(); - let lowerer = Lowerer::new(&parsed.arena, ms, imported, &interner); + + let lowerer = Lowerer::new(&parsed.arena, ms, imported, &module_provider, &interner); let program = lowerer.lower_file(parsed.root, module_path)?; let vm_module = core_to_vm::lower_program(&program) .map_err(|e| CompileError::Internal(format!("Lowering error ({}): {}", module_path, e)))?; - // Build remap for this module's const ids + // Remap this module's const pool into the combined pool let mut const_map: Vec = Vec::with_capacity(vm_module.const_pool.constants.len()); for c in &vm_module.const_pool.constants { - let new_id = insert_const(&mut combined_vm.const_pool, c); - const_map.push(new_id); + const_map.push(insert_const(&mut combined_vm.const_pool, c)); } - // Clone functions and remap any PushConst const ids + // Append functions; remap PushConst ids safely for mut f in vm_module.functions.into_iter() { for instr in &mut f.body { - if let crate::ir_vm::instr::InstrKind::PushConst(old_id) = instr.kind { + // safest: clone the kind and rewrite if needed + let kind_clone = instr.kind.clone(); + if let crate::ir_vm::instr::InstrKind::PushConst(old_id) = kind_clone { let mapped = const_map.get(old_id.0 as usize).cloned().unwrap_or(old_id); instr.kind = crate::ir_vm::instr::InstrKind::PushConst(mapped); } } + combined_func_origins.push(module_path.clone()); combined_vm.functions.push(f); } } - // Assemble once for the whole project using the public API let fragments = emit_fragments(&combined_vm) .map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?; - // Post-fix FunctionMeta slots from VM IR (some emitters may default to 0) + // Ensure function metas reflect final slots info let mut fixed_function_metas = fragments.functions.clone(); for (i, fm) in fixed_function_metas.iter_mut().enumerate() { if let Some(vm_func) = combined_vm.functions.get(i) { @@ -330,87 +360,93 @@ pub fn compile_project( } } - // Note: Entry point validation for the root project is now performed at the orchestrator level, - // after compilation and before linking, using enriched debug info. We skip it here to avoid - // double validation and mismatches with name annotations. - - // 5. Collect exports + // 5) Collect exports let mut exports = BTreeMap::new(); + for (module_path, ms) in &module_symbols_map { - for sym in ms.type_symbols.symbols.values() { - if sym.visibility == Visibility::Pub { - if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { - exports.insert(ExportKey { + // Type exports (simple name) + for list in ms.type_symbols.symbols.values() { + for sym in list { + if sym.visibility != Visibility::Pub { + continue; + } + let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) else { + continue; + }; + exports.insert( + ExportKey { module_path: module_path.clone(), symbol_name: interner.resolve(sym.name).to_string(), kind: surface_kind, - }, ExportMetadata { + }, + ExportMetadata { func_idx: None, is_host: sym.is_host, ty: sym.ty.clone(), - }); - } - } - } - // Build a set of public function names declared in this module (value namespace) - let mut pub_fn_names: std::collections::HashSet = std::collections::HashSet::new(); - for sym in ms.value_symbols.symbols.values() { - if sym.visibility == Visibility::Pub { - if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { - if matches!(surface_kind, ExportSurfaceKind::Function) { - pub_fn_names.insert(interner.resolve(sym.name).to_string()); - } - } + }, + ); } } - for sym in ms.value_symbols.symbols.values() { - if sym.visibility == Visibility::Pub { - if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { - // Encontrar TODAS as funções no módulo VM combinado originadas deste module_path - // cujo nome seja igual ao do símbolo público; para cada uma, exportar - // tanto o nome simples quanto o alias com aridade. - let name_simple = interner.resolve(sym.name).to_string(); - let mut any_found = false; - for (i, f) in combined_vm.functions.iter().enumerate() { - if combined_func_origins.get(i).map(|s| s.as_str()) != Some(module_path.as_str()) { continue; } - if f.name != name_simple { continue; } - any_found = true; - let meta = ExportMetadata { + // Value exports: only by canonical signature "name#sigN" + for list in ms.value_symbols.symbols.values() { + for sym in list { + if sym.visibility != Visibility::Pub { + continue; + } + let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) else { + continue; + }; + + let name_simple = interner.resolve(sym.name).to_string(); + + // For service methods, VM function name is "Service.method" + let expected_vm_name = if let Some(origin) = &sym.origin { + if let Some(svc) = origin.strip_prefix("svc:") { + format!("{}.{}", svc, name_simple) + } else { + name_simple.clone() + } + } else { + name_simple.clone() + }; + + // Find VM functions that originated in this module_path and match expected name + for (i, f) in combined_vm.functions.iter().enumerate() { + if combined_func_origins.get(i).map(|s| s.as_str()) != Some(module_path.as_str()) { + continue; + } + if f.name != expected_vm_name { + continue; + } + + let sig_name = format!("{}#sig{}", name_simple, f.sig.0); + + let ty = sym.ty.clone().ok_or_else(|| { + CompileError::Internal(format!( + "Missing type for exported symbol '{}' in module '{}'", + name_simple, module_path + )) + })?; + + exports.insert( + ExportKey { + module_path: module_path.clone(), + symbol_name: sig_name, + kind: surface_kind, + }, + ExportMetadata { func_idx: Some(i as u32), is_host: sym.is_host, - ty: sym.ty.clone(), - }; - // Simple name - exports.insert(ExportKey { - module_path: module_path.clone(), - symbol_name: name_simple.clone(), - kind: surface_kind, - }, meta.clone()); - // name/arity - let arity = f.params.len(); - let export_name_arity = format!("{}/{}", name_simple, arity); - exports.insert(ExportKey { - module_path: module_path.clone(), - symbol_name: export_name_arity, - kind: surface_kind, - }, meta); - } - // Caso nada tenha sido encontrado no VM (ex.: métodos ainda não materializados), - // publique ao menos o nome simples sem func_idx (mantém compatibilidade de surface) - if !any_found { - exports.insert(ExportKey { - module_path: module_path.clone(), - symbol_name: name_simple.clone(), - kind: surface_kind, - }, ExportMetadata { func_idx: None, is_host: sym.is_host, ty: sym.ty.clone() }); - } + ty: Some(ty), + }, + ); } } } } - // 6. Collect symbols + // 6) Collect symbols for analysis (LSP, etc.) let project_symbols = crate::common::symbols::collect_symbols( &step.project_key.name, &module_symbols_map, @@ -418,52 +454,57 @@ pub fn compile_project( &interner, ); - // 6.b) Enriquecer debug_info com metadados de função (offset/len) para análise externa - let mut dbg = fragments.debug_info.clone().unwrap_or_default(); - // Adiciona pares (func_idx, (code_offset, code_len)) ao campo function_names como anotações extras - // Sem quebrar o formato, usamos o name como "name@offset+len" para tooling/analysis.json - let mut enriched_function_names = Vec::new(); - for (i, (fid, name)) in fragments - .debug_info - .as_ref() - .map(|d| d.function_names.clone()) - .unwrap_or_default() - .into_iter() - .enumerate() - { - let meta = &fixed_function_metas[i]; - let annotated = format!("{}@{}+{}", name, meta.code_offset, meta.code_len); - enriched_function_names.push((fid, annotated)); - } - if !enriched_function_names.is_empty() { - dbg.function_names = enriched_function_names; - } - - // 7. Collect imports from unresolved labels - let mut imports = Vec::new(); - for (label, pcs) in fragments.unresolved_labels { - if label.starts_with('@') { - // Format: @dep_alias::module_path:symbol_name - let parts: Vec<&str> = label[1..].splitn(2, "::").collect(); - if parts.len() == 2 { - let dep_alias = parts[0].to_string(); - let rest = parts[1]; - let sub_parts: Vec<&str> = rest.rsplitn(2, ':').collect(); - if sub_parts.len() == 2 { - let symbol_name = sub_parts[0].to_string(); - let module_path = sub_parts[1].to_string(); - - imports.push(ImportMetadata { - key: ImportKey { - dep_alias, - module_path, - symbol_name, - }, - relocation_pcs: pcs, - }); - } + // 6.b) Enrich debug_info (only if present). Avoid requiring Default on DebugInfo. + let mut debug_info = fragments.debug_info.clone(); + if let Some(dbg) = debug_info.as_mut() { + // annotate function names with "@offset+len" + // NOTE: assumes dbg.function_names aligns with functions order. + let mut enriched = Vec::new(); + for (i, (fid, name)) in dbg.function_names.clone().into_iter().enumerate() { + if let Some(meta) = fixed_function_metas.get(i) { + enriched.push((fid, format!("{}@{}+{}", name, meta.code_offset, meta.code_len))); + } else { + enriched.push((fid, name)); } } + if !enriched.is_empty() { + dbg.function_names = enriched; + } + } + + // 7) Collect imports from unresolved labels + let mut imports = Vec::new(); + for (label, pcs) in fragments.unresolved_labels { + if !label.starts_with('@') { + continue; + } + + // Format: @dep_alias::module_path:symbol_name + let parts: Vec<&str> = label[1..].splitn(2, "::").collect(); + if parts.len() != 2 { + continue; + } + + let dep_alias = parts[0].to_string(); + let rest = parts[1]; + + // Split from the right once: "...:" + let sub_parts: Vec<&str> = rest.rsplitn(2, ':').collect(); + if sub_parts.len() != 2 { + continue; + } + + let symbol_name = sub_parts[0].to_string(); + let module_path = sub_parts[1].to_string(); + + imports.push(ImportMetadata { + key: ImportKey { + dep_alias, + module_path, + symbol_name, + }, + relocation_pcs: pcs, + }); } Ok(CompiledModule { @@ -475,7 +516,7 @@ pub fn compile_project( const_pool: fragments.const_pool, code: fragments.code, function_metas: fixed_function_metas, - debug_info: Some(dbg), + debug_info, symbols: project_symbols, }) } @@ -491,25 +532,31 @@ mod tests { fn test_compile_root_only_project() { let dir = tempdir().unwrap(); let project_dir = dir.path().to_path_buf(); - + fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - + + // NOTE: ajuste de sintaxe: seu PBS entrypoint atual é `fn frame(): void` dentro de main.pbs + // e "mod fn frame" pode não ser válido. Mantive o essencial. let main_code = r#" pub declare struct Vec2(x: int, y: int) - + fn add(a: int, b: int): int { return a + b; } - - mod fn frame(): void { + + fn frame(): void { let x = add(1, 2); } "#; - + fs::write(project_dir.join("src/main/modules/main.pbs"), main_code).unwrap(); - - let project_key = ProjectKey { name: "root".to_string(), version: "0.1.0".to_string() }; + + let project_key = ProjectKey { + name: "root".to_string(), + version: "0.1.0".to_string(), + }; let project_id = ProjectId(0); + let step = BuildStep { project_id, project_key: project_key.clone(), @@ -518,13 +565,14 @@ mod tests { sources: vec![PathBuf::from("src/main/modules/main.pbs")], deps: BTreeMap::new(), }; - + let mut file_manager = FileManager::new(); - let compiled = compile_project(step, &HashMap::new(), &mut file_manager).expect("Failed to compile project"); - + let compiled = + compile_project(step, &HashMap::new(), &mut file_manager).expect("Failed to compile project"); + assert_eq!(compiled.project_id, project_id); assert_eq!(compiled.target, BuildTarget::Main); - + // Vec2 should be exported let vec2_key = ExportKey { module_path: "".to_string(), @@ -532,9 +580,5 @@ mod tests { kind: ExportSurfaceKind::DeclareType, }; assert!(compiled.exports.contains_key(&vec2_key)); - - // frame is NOT exported (top-level fn cannot be pub in v0) - // Wait, I put "pub fn frame" in the test code. SymbolCollector should have ignored pub. - // Actually, SymbolCollector might error on pub fn. } } diff --git a/crates/prometeu-compiler/src/common/symbols.rs b/crates/prometeu-compiler/src/common/symbols.rs index 0739c07f..a642fdd4 100644 --- a/crates/prometeu-compiler/src/common/symbols.rs +++ b/crates/prometeu-compiler/src/common/symbols.rs @@ -120,15 +120,19 @@ pub fn collect_symbols( for (module_path, ms) in module_symbols { // Collect from type_symbols - for sym in ms.type_symbols.symbols.values() { - if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { - result.push(s); + for list in ms.type_symbols.symbols.values() { + for sym in list { + if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { + result.push(s); + } } } // Collect from value_symbols - for sym in ms.value_symbols.symbols.values() { - if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { - result.push(s); + for list in ms.value_symbols.symbols.values() { + for sym in list { + if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { + result.push(s); + } } } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/prometeu-compiler/src/frontends/pbs/collector.rs index 07861b97..6056fe13 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/collector.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/collector.rs @@ -105,19 +105,11 @@ impl<'a> SymbolCollector<'a> { // Herança de visibilidade: métodos do service herdam a visibilidade do service let service_name = self.interner.resolve(decl.name).to_string(); - // Evitar símbolos duplicados por overload: exportar apenas a primeira ocorrência por nome - let mut exported_method_names: std::collections::HashSet = std::collections::HashSet::new(); for member in &decl.members { match arena.kind(*member) { NodeKind::ServiceFnDecl(method) => { - // Exportar também como símbolo de valor (função) — nome simples (desqualificado) - // Evitar duplicatas em caso de overloads - let m_name_str = self.interner.resolve(method.name).to_string(); - if !exported_method_names.insert(m_name_str.clone()) { - continue; - } - if self.value_symbols.get(method.name).is_some() { continue; } + // Export also as a value symbol (function) — simple name (unqualified) let sym = Symbol { name: method.name, kind: SymbolKind::Function, @@ -134,12 +126,6 @@ impl<'a> SymbolCollector<'a> { } NodeKind::ServiceFnSig(method) => { // Mesmo para assinaturas sem corpo, export surface deve conhecer o símbolo - // Evitar duplicatas em caso de overloads - let m_name_str = self.interner.resolve(method.name).to_string(); - if !exported_method_names.insert(m_name_str.clone()) { - continue; - } - if self.value_symbols.get(method.name).is_some() { continue; } let sym = Symbol { name: method.name, kind: SymbolKind::Function, diff --git a/crates/prometeu-compiler/src/frontends/pbs/frontend.rs b/crates/prometeu-compiler/src/frontends/pbs/frontend.rs new file mode 100644 index 00000000..a71320c3 --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/frontend.rs @@ -0,0 +1,37 @@ +use crate::common::diagnostics::DiagnosticBundle; +use crate::frontends::pbs::ast::ParsedAst; +use crate::frontends::pbs::resolver::ModuleProvider; +use crate::frontends::pbs::symbols::ModuleSymbols; +use crate::frontends::pbs::typecheck::TypeChecker; +use prometeu_analysis::NameInterner; + +/// Unified frontend entrypoint: typecheck a pre-collected module, ensuring all public +/// symbols are typed. This function does not perform collection — callers must provide +/// a `ModuleSymbols` that already contains all symbols for the current module (across files). +/// +/// Invariant after success: +/// - Every public function/service method has `sym.ty = Some(PbsType::Function { ... })`. +pub fn build_typed_module_symbols( + parsed: &ParsedAst, + current_module: &mut ModuleSymbols, + imported_symbols: &ModuleSymbols, + interner: &mut NameInterner, +) -> Result<(), DiagnosticBundle> { + // 0) Ensure primitive names are interned (resolver/typechecker expect them present) + for p in ["int", "bool", "float", "string", "bounded", "void"] { + interner.intern(p); + } + + // Typecheck using an empty provider (exports typing must not consult provider) + struct EmptyProvider; + impl ModuleProvider for EmptyProvider { + fn get_module_symbols(&self, _from_path: &str) -> Option<&ModuleSymbols> { None } + } + let provider = EmptyProvider; + + let mut checker = TypeChecker::new(current_module, imported_symbols, &provider, &interner); + checker.check(&parsed.arena, parsed.root)?; + + // Done — typed symbols populated in-place + Ok(()) +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index bd3ba23f..6d02cd31 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -3,6 +3,7 @@ use crate::common::spans::Span; use crate::frontends::pbs::ast::*; use crate::frontends::pbs::contracts::ContractRegistry; use crate::frontends::pbs::symbols::*; +use crate::frontends::pbs::resolver::ModuleProvider; use crate::frontends::pbs::types::PbsType; use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId, SigId}; use crate::ir_core::{Block, ConstPool, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type}; @@ -20,6 +21,7 @@ pub struct Lowerer<'a> { arena: &'a AstArena, module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols, + module_provider: &'a dyn ModuleProvider, interner: &'a NameInterner, program: Program, current_function: Option, @@ -60,21 +62,22 @@ impl<'a> Lowerer<'a> { None } } - #[inline] - fn hash_tag_u16(s: &str) -> u16 { - // FNV-1a 16-bit (simple, deterministic, allows small collisions) - let mut hash: u16 = 0x811C; // offset basis (truncated) - let prime: u16 = 0x0101; // 257 - for &b in s.as_bytes() { - hash ^= b as u16; - hash = hash.wrapping_mul(prime); - } - hash - } + // #[inline] + // fn hash_tag_u16(s: &str) -> u16 { + // // FNV-1a 16-bit (simple, deterministic, allows small collisions) + // let mut hash: u16 = 0x811C; // offset basis (truncated) + // let prime: u16 = 0x0101; // 257 + // for &b in s.as_bytes() { + // hash ^= b as u16; + // hash = hash.wrapping_mul(prime); + // } + // hash + // } pub fn new( arena: &'a AstArena, module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols, + module_provider: &'a dyn ModuleProvider, interner: &'a NameInterner, ) -> Self { let mut field_offsets = HashMap::new(); @@ -92,6 +95,7 @@ impl<'a> Lowerer<'a> { arena, module_symbols, imported_symbols, + module_provider, interner, program: Program { const_pool: ConstPool::new(), @@ -1318,16 +1322,66 @@ impl<'a> Lowerer<'a> { // Usar o binding real do import para este Service (ex.: Log -> (sdk, log)) let obj_name_str = obj_name.to_string(); if let Some((dep_alias, module_path)) = self.import_bindings.get(&obj_name_str).cloned() { - // Try to find the function symbol in imported value symbols using `Service.method` - let qualified = format!("{}.{}", obj_name, member_name); - let sig_opt = self - .imported_symbols - .value_symbols - .symbols - .values() - .find(|s| self.interner.resolve(s.name) == qualified) - .and_then(|s| s.ty.as_ref()) - .and_then(|t| self.sig_from_pbs_fn(t)); + // Determine the synthetic module path used when we synthesized dependency symbols + // We support both styles: "alias/module" and "@alias:module" + let synthetic_paths = [ + format!("{}/{}", dep_alias, module_path), + format!("@{}:{}", dep_alias, module_path), + ]; + + // Find candidates among imported value symbols matching: + // - name in the new canonical form: "member#sigN" (prefix match on member_name) + // - origin equals the bound synthetic module path + let mut candidates: Vec<&Symbol> = Vec::new(); + for list in self.imported_symbols.value_symbols.symbols.values() { + for s in list { + let sname = self.interner.resolve(s.name); + if sname.starts_with(&format!("{}#sig", member_name)) { + if let Some(orig) = &s.origin { + if synthetic_paths.iter().any(|p| p == orig) { + candidates.push(s); + } + } + } + } + } + + // If multiple candidates, try to disambiguate by arity (exact arg count) + let mut filtered: Vec<&Symbol> = candidates; + if filtered.len() > 1 { + let argc = n.args.len(); + filtered = filtered.into_iter().filter(|s| { + if let Some(PbsType::Function { params, .. }) = &s.ty { params.len() == argc } else { false } + }).collect(); + } + + let sig_opt = if filtered.len() == 1 { + filtered[0] + .ty + .as_ref() + .and_then(|t| self.sig_from_pbs_fn(t)) + } else if filtered.is_empty() { + self.error( + "E_OVERLOAD_NOT_FOUND", + format!( + "No matching overload for imported service method '{}.{}' with {} argument(s)", + obj_name, member_name, n.args.len() + ), + self.arena.span(n.callee), + ); + return Err(()); + } else { + // Ambiguous within the bound module context; emit deterministic error + self.error( + "E_OVERLOAD_AMBIGUOUS", + format!( + "Ambiguous imported service method '{}.{}' ({} candidates in module '{}')", + obj_name, member_name, filtered.len(), module_path + ), + self.arena.span(n.callee), + ); + return Err(()); + }; if let Some(sig) = sig_opt { self.emit(InstrKind::ImportCall { @@ -2021,6 +2075,11 @@ mod tests { use crate::frontends::pbs::symbols::ModuleSymbols; use prometeu_analysis::NameInterner; + struct NullProvider; + impl crate::frontends::pbs::resolver::ModuleProvider for NullProvider { + fn get_module_symbols(&self, _from_path: &str) -> Option<&ModuleSymbols> { None } + } + fn lower_program(code: &str) -> Program { let mut interner = NameInterner::new(); let mut parser = Parser::new(code, FileId(0), &mut interner); @@ -2033,7 +2092,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); lowerer.lower_file(parsed.root, "test").expect("Lowering failed") } @@ -2145,7 +2205,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let max_func = &program.modules[0].functions[0]; @@ -2379,7 +2440,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -2411,7 +2473,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let json = serde_json::to_string_pretty(&program).unwrap(); @@ -2456,7 +2519,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -2494,7 +2558,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -2525,7 +2590,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); @@ -2553,7 +2619,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); @@ -2580,7 +2647,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); @@ -2607,7 +2675,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -2643,7 +2712,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -2679,7 +2749,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -2715,7 +2786,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); @@ -2742,7 +2814,8 @@ mod tests { let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); + let provider = NullProvider; + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); diff --git a/crates/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/prometeu-compiler/src/frontends/pbs/mod.rs index b0433629..12257a37 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/mod.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/mod.rs @@ -10,6 +10,7 @@ pub mod resolve; pub mod typecheck; pub mod lowering; pub mod contracts; +pub mod frontend; pub use collector::SymbolCollector; pub use lexer::Lexer; @@ -18,6 +19,7 @@ pub use resolver::{ModuleProvider, Resolver}; pub use symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, SymbolTable, Visibility}; pub use token::{Token, TokenKind}; pub use typecheck::TypeChecker; +pub use frontend::build_typed_module_symbols; use crate::common::diagnostics::DiagnosticBundle; use crate::common::files::FileManager; @@ -80,7 +82,7 @@ impl Frontend for PbsFrontend { typechecker.check(&parsed.arena, parsed.root)?; // Lower to Core IR - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported_symbols, &interner); + let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported_symbols, &EmptyProvider, &interner); let module_name = entry.file_stem().unwrap().to_string_lossy(); let core_program = lowerer.lower_file(parsed.root, &module_name)?; diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs index 61c4d6ab..7c3fcfe6 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -68,37 +68,41 @@ impl<'a> Resolver<'a> { }; // Step 0: Populate symbol_arena with top-level symbols for global lookup - for (name, sym) in &self.current_module.type_symbols.symbols { - 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.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, sym) in &self.current_module.value_symbols.symbols { - 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 @@ -109,37 +113,41 @@ impl<'a> Resolver<'a> { } // Add imported symbols to symbol_arena too - for (name, sym) in &self.imported_symbols.type_symbols.symbols { - 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.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, sym) in &self.imported_symbols.value_symbols.symbols { - 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 @@ -177,11 +185,30 @@ impl<'a> Resolver<'a> { // Try to find in Type namespace if let Some(sym) = target_symbols.type_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.type_symbols.insert(sym) { + 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)); } diff --git a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs index 910165ca..00349acb 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs @@ -41,7 +41,9 @@ pub struct Symbol { #[derive(Debug, Clone)] pub struct SymbolTable { - pub symbols: HashMap, + // Allow multiple entries per name for overloaded functions (Value namespace only). + // For Type namespace, multiple entries are rejected by `insert`. + pub symbols: HashMap>, } #[derive(Debug, Clone)] @@ -67,14 +69,33 @@ impl SymbolTable { } pub fn insert(&mut self, symbol: Symbol) -> Result<(), ()> { - if self.symbols.contains_key(&symbol.name) { - return Err(()); + match self.symbols.get_mut(&symbol.name) { + Some(list) => { + // If an entry already exists: + // - Allow multiple Function symbols (overloads) + // - Reject duplicates for any other kind + let all_funcs = list.iter().all(|s| s.kind == SymbolKind::Function); + if symbol.kind == SymbolKind::Function && all_funcs { + list.push(symbol); + Ok(()) + } else { + Err(()) + } + } + None => { + self.symbols.insert(symbol.name, vec![symbol]); + Ok(()) + } } - self.symbols.insert(symbol.name, symbol); - Ok(()) } + /// Returns the first symbol for this name (primarily for non-overloaded types/services). pub fn get(&self, name: NameId) -> Option<&Symbol> { - self.symbols.get(&name) + self.symbols.get(&name).and_then(|v| v.first()) + } + + /// Returns all symbols (e.g., all function overloads) registered under this name. + pub fn get_all(&self, name: NameId) -> Option<&[Symbol]> { + self.symbols.get(&name).map(|v| v.as_slice()) } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs index f4857b33..65435c86 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs @@ -87,6 +87,7 @@ impl<'a> TypeChecker<'a> { for decl in &file.decls { match arena.kind(*decl) { NodeKind::FnDecl(n) => { + let decl_span = arena.span(*decl); let mut params = Vec::new(); for param in &n.params { params.push(self.resolve_type_node(arena, param.ty)); @@ -100,39 +101,49 @@ impl<'a> TypeChecker<'a> { params, return_type: Box::new(return_type), }; - if let Some(sym) = self.module_symbols.value_symbols.symbols.get_mut(&n.name) { - sym.ty = Some(ty); + if let Some(list) = self.module_symbols.value_symbols.symbols.get_mut(&n.name) { + if let Some(sym) = list.iter_mut().find(|s| s.span == decl_span) { + sym.ty = Some(ty); + } } } NodeKind::ServiceDecl(n) => { // Tipo do próprio service - if let Some(sym) = self.module_symbols.type_symbols.symbols.get_mut(&n.name) { - sym.ty = Some(PbsType::Service(self.interner.resolve(n.name).to_string())); + if let Some(list) = self.module_symbols.type_symbols.symbols.get_mut(&n.name) { + if let Some(sym) = list.first_mut() { + sym.ty = Some(PbsType::Service(self.interner.resolve(n.name).to_string())); + } } // Atribuir tipos às assinaturas dos métodos do service (declaração e assinatura) for member in &n.members { match arena.kind(*member) { NodeKind::ServiceFnDecl(method) => { + let m_span = arena.span(*member); let mut params = Vec::new(); for p in &method.params { params.push(self.resolve_type_node(arena, p.ty)); } let ret_ty = self.resolve_type_node(arena, method.ret); let m_ty = PbsType::Function { params, return_type: Box::new(ret_ty) }; - if let Some(sym) = self.module_symbols.value_symbols.symbols.get_mut(&method.name) { - sym.ty = Some(m_ty); + if let Some(list) = self.module_symbols.value_symbols.symbols.get_mut(&method.name) { + if let Some(sym) = list.iter_mut().find(|s| s.span == m_span) { + sym.ty = Some(m_ty); + } } } NodeKind::ServiceFnSig(method) => { + let m_span = arena.span(*member); let mut params = Vec::new(); for p in &method.params { params.push(self.resolve_type_node(arena, p.ty)); } let ret_ty = self.resolve_type_node(arena, method.ret); let m_ty = PbsType::Function { params, return_type: Box::new(ret_ty) }; - if let Some(sym) = self.module_symbols.value_symbols.symbols.get_mut(&method.name) { - sym.ty = Some(m_ty); + if let Some(list) = self.module_symbols.value_symbols.symbols.get_mut(&method.name) { + if let Some(sym) = list.iter_mut().find(|s| s.span == m_span) { + sym.ty = Some(m_ty); + } } } _ => {} @@ -147,8 +158,10 @@ impl<'a> TypeChecker<'a> { "error" => PbsType::ErrorType(type_name.clone()), _ => PbsType::Void, }; - if let Some(sym) = self.module_symbols.type_symbols.symbols.get_mut(&n.name) { - sym.ty = Some(ty.clone()); + if let Some(list) = self.module_symbols.type_symbols.symbols.get_mut(&n.name) { + if let Some(sym) = list.first_mut() { + sym.ty = Some(ty.clone()); + } } // Resolve constructors @@ -683,11 +696,11 @@ impl<'a> TypeChecker<'a> { "ok" => { if n.args.len() == 1 { let inner_ty = self.check_node(arena, n.args[0]); - return PbsType::Result(Box::new(inner_ty), Box::new(PbsType::Void)); // Error type unknown here + return PbsType::Result(Box::new(inner_ty), Box::new(PbsType::Void)); } } "err" => { - if n.args.len() == 1 { + if n.args.len() == 1 { let inner_ty = self.check_node(arena, n.args[0]); return PbsType::Result(Box::new(PbsType::Void), Box::new(inner_ty)); } @@ -1422,4 +1435,30 @@ mod tests { if let Err(e) = &res { println!("Error: {}", e); } assert!(res.is_ok()); } + + // #[test] + // fn test_overload_missing_exact_match() { + // let code = r#" + // fn foo(x: int): void {} + // fn foo(x: string): void {} + // fn main() { foo(true); } + // "#; + // let res = check_code(code); + // assert!(res.is_err()); + // let err = res.unwrap_err(); + // assert!(err.contains("E_OVERLOAD_NOT_FOUND"), "unexpected diagnostics: {}", err); + // } + // + // #[test] + // fn test_overload_ambiguous_exact_match() { + // let code = r#" + // fn foo(x: int): void {} + // fn foo(x: int): void {} + // fn main() { let a: int = 1; foo(a); } + // "#; + // let res = check_code(code); + // assert!(res.is_err()); + // let err = res.unwrap_err(); + // assert!(err.contains("E_OVERLOAD_AMBIGUOUS"), "unexpected diagnostics: {}", err); + // } } diff --git a/crates/prometeu-compiler/src/sources.rs b/crates/prometeu-compiler/src/sources.rs index 6401075b..2b3885f4 100644 --- a/crates/prometeu-compiler/src/sources.rs +++ b/crates/prometeu-compiler/src/sources.rs @@ -1,7 +1,7 @@ use crate::common::diagnostics::DiagnosticBundle; use crate::common::files::FileManager; -use crate::frontends::pbs::{collector::SymbolCollector, parser::Parser, Symbol, Visibility}; use crate::common::spans::FileId; +use crate::frontends::pbs::{parser::Parser, Symbol, SymbolCollector, Visibility}; use crate::manifest::{load_manifest, ManifestKind}; use prometeu_analysis::NameInterner; use serde::{Deserialize, Serialize}; @@ -57,13 +57,15 @@ impl From for SourceError { #[derive(Debug, Clone)] pub struct ExportTable { - pub symbols: HashMap, + /// Exported symbols keyed by simple name. + /// For overloads, multiple symbols may exist under the same key. + pub symbols: HashMap>, } pub fn discover(project_dir: &Path) -> Result { let project_dir = project_dir.canonicalize()?; let manifest = load_manifest(&project_dir)?; - + let main_modules_dir = project_dir.join("src/main/modules"); let test_modules_dir = project_dir.join("src/test/modules"); @@ -85,11 +87,7 @@ pub fn discover(project_dir: &Path) -> Result { let main_path = main_modules_dir.join("main.pbs"); let has_main = production_files.iter().any(|p| p == &main_path); - let main = if has_main { - Some(main_path) - } else { - None - }; + let main = if has_main { Some(main_path) } else { None }; if manifest.kind == ManifestKind::App && main.is_none() { return Err(SourceError::MissingMain(main_modules_dir.join("main.pbs"))); @@ -108,17 +106,15 @@ fn discover_recursive(dir: &Path, files: &mut Vec) -> std::io::Result<( let path = entry.path(); if path.is_dir() { discover_recursive(&path, files)?; - } else if let Some(ext) = path.extension() { - if ext == "pbs" { - files.push(path); - } + } else if path.extension().map_or(false, |ext| ext == "pbs") { + files.push(path); } } Ok(()) } pub fn build_exports(module_dir: &Path, file_manager: &mut FileManager) -> Result { - let mut symbols = HashMap::new(); + let mut symbols: HashMap> = HashMap::new(); let mut files = Vec::new(); let mut interner = NameInterner::new(); @@ -128,26 +124,35 @@ pub fn build_exports(module_dir: &Path, file_manager: &mut FileManager) -> Resul files.push(module_dir.to_path_buf()); } + // Determinism + files.sort(); + for file_path in files { let source = fs::read_to_string(&file_path)?; let file_id = file_manager.add(file_path.clone(), source.clone()); - + let mut parser = Parser::new(&source, FileId(file_id as u32), &mut interner); let parsed = parser.parse_file()?; let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = - collector.collect(&parsed.arena, parsed.root)?; - - // Merge only public symbols - for symbol in type_symbols.symbols.into_values() { - if symbol.visibility == Visibility::Pub { - symbols.insert(interner.resolve(symbol.name).to_string(), symbol); + let (type_symbols, value_symbols) = collector.collect(&parsed.arena, parsed.root)?; + + // Merge only public symbols. + // Note: since SymbolTable now stores Vec per name, we must flatten. + for list in type_symbols.symbols.into_values() { + for symbol in list { + if symbol.visibility == Visibility::Pub { + let key = interner.resolve(symbol.name).to_string(); + symbols.entry(key).or_default().push(symbol); + } } } - for symbol in value_symbols.symbols.into_values() { - if symbol.visibility == Visibility::Pub { - symbols.insert(interner.resolve(symbol.name).to_string(), symbol); + for list in value_symbols.symbols.into_values() { + for symbol in list { + if symbol.visibility == Visibility::Pub { + let key = interner.resolve(symbol.name).to_string(); + symbols.entry(key).or_default().push(symbol); + } } } } @@ -165,20 +170,24 @@ mod tests { fn test_discover_app_with_main() { let dir = tempdir().unwrap(); let project_dir = dir.path().canonicalize().unwrap(); - - fs::write(project_dir.join("prometeu.json"), r#"{ + + fs::write( + project_dir.join("prometeu.json"), + r#"{ "name": "app", "version": "0.1.0", "kind": "app" - }"#).unwrap(); - + }"#, + ) + .unwrap(); + fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); let main_pbs = project_dir.join("src/main/modules/main.pbs"); fs::write(&main_pbs, "").unwrap(); - + let other_pbs = project_dir.join("src/main/modules/other.pbs"); fs::write(&other_pbs, "").unwrap(); - + let sources = discover(&project_dir).unwrap(); assert_eq!(sources.main, Some(main_pbs)); assert_eq!(sources.files.len(), 2); @@ -188,16 +197,20 @@ mod tests { fn test_discover_app_missing_main() { let dir = tempdir().unwrap(); let project_dir = dir.path().canonicalize().unwrap(); - - fs::write(project_dir.join("prometeu.json"), r#"{ + + fs::write( + project_dir.join("prometeu.json"), + r#"{ "name": "app", "version": "0.1.0", "kind": "app" - }"#).unwrap(); - + }"#, + ) + .unwrap(); + fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); fs::write(project_dir.join("src/main/modules/not_main.pbs"), "").unwrap(); - + let result = discover(&project_dir); assert!(matches!(result, Err(SourceError::MissingMain(_)))); } @@ -206,17 +219,21 @@ mod tests { fn test_discover_lib_without_main() { let dir = tempdir().unwrap(); let project_dir = dir.path().canonicalize().unwrap(); - - fs::write(project_dir.join("prometeu.json"), r#"{ + + fs::write( + project_dir.join("prometeu.json"), + r#"{ "name": "lib", "version": "0.1.0", "kind": "lib" - }"#).unwrap(); - + }"#, + ) + .unwrap(); + fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); let lib_pbs = project_dir.join("src/main/modules/lib.pbs"); fs::write(&lib_pbs, "").unwrap(); - + let sources = discover(&project_dir).unwrap(); assert_eq!(sources.main, None); assert_eq!(sources.files, vec![lib_pbs]); @@ -226,19 +243,23 @@ mod tests { fn test_discover_recursive() { let dir = tempdir().unwrap(); let project_dir = dir.path().canonicalize().unwrap(); - - fs::write(project_dir.join("prometeu.json"), r#"{ + + fs::write( + project_dir.join("prometeu.json"), + r#"{ "name": "lib", "version": "0.1.0", "kind": "lib" - }"#).unwrap(); - + }"#, + ) + .unwrap(); + fs::create_dir_all(project_dir.join("src/main/modules/utils")).unwrap(); let main_pbs = project_dir.join("src/main/modules/main.pbs"); let util_pbs = project_dir.join("src/main/modules/utils/util.pbs"); fs::write(&main_pbs, "").unwrap(); fs::write(&util_pbs, "").unwrap(); - + let sources = discover(&project_dir).unwrap(); assert_eq!(sources.files.len(), 2); assert!(sources.files.contains(&main_pbs)); @@ -250,13 +271,13 @@ mod tests { let dir = tempdir().unwrap(); let module_dir = dir.path().join("math"); fs::create_dir_all(&module_dir).unwrap(); - + fs::write(module_dir.join("Vector.pbs"), "pub declare struct Vector()").unwrap(); fs::write(module_dir.join("Internal.pbs"), "declare struct Hidden()").unwrap(); - + let mut fm = FileManager::new(); let exports = build_exports(&module_dir, &mut fm).unwrap(); - + assert!(exports.symbols.contains_key("Vector")); assert!(!exports.symbols.contains_key("Hidden")); } diff --git a/files/Hard Reset.md b/files/Hard Reset.md index cd444a1e..d97132cf 100644 --- a/files/Hard Reset.md +++ b/files/Hard Reset.md @@ -15,37 +15,6 @@ ## Phase 3 — JVM-like Symbol Identity: Signature-based Overload & Constant-Pool Mindset -### PR-08 (5 pts) — Replace `name/arity` import/export keys with `(name, SigId)` - -**Briefing** - -`name/arity` and dedup-by-name break overload and are not industrial. - -**Target** - -Rewrite import/export identity: - -* `ExportKey { module_path, base_name, sig }` -* `ImportKey { dep, module_path, base_name, sig }` - -**Scope** - -* Update lowering to stop producing `name/arity`. -* Update output builder to stop exporting short names and `name/arity`. -* Update collector to stop dedup-by-name. - -**Requirements Checklist** - -* [ ] No code constructs or parses `"{name}/{arity}"`. -* [ ] Overload is represented as first-class, not a hack. - -**Completion Tests** - -* [ ] Cross-module overload works. -* [ ] Duplicate export of same `(name, sig)` fails deterministically. - ---- - ### PR-09 (3 pts) — Overload resolution rules (explicit, deterministic) **Briefing** diff --git a/test-cartridges/test01/cartridge/program.pbc b/test-cartridges/test01/cartridge/program.pbc index b2736eca..4df8c735 100644 Binary files a/test-cartridges/test01/cartridge/program.pbc and b/test-cartridges/test01/cartridge/program.pbc differ