intermediary state, safe point

This commit is contained in:
bQUARKz 2026-02-10 15:15:42 +00:00
parent c80260e780
commit f5d259ba2a
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
14 changed files with 702 additions and 480 deletions

View File

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

View File

@ -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<Bu
mod tests {
use super::*;
use crate::deps::resolver::{ProjectKey, ResolvedGraph, ResolvedNode};
use crate::sources::discover;
use crate::manifest::{Manifest, ManifestKind};
use std::collections::BTreeMap;
use crate::sources::discover;
use prometeu_analysis::ids::ProjectId;
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use tempfile::tempdir;

View File

@ -4,21 +4,20 @@ use crate::common::diagnostics::DiagnosticBundle;
use crate::common::files::FileManager;
use crate::common::spans::{FileId, Span};
use crate::deps::resolver::ProjectKey;
use prometeu_analysis::ids::ProjectId;
use crate::frontends::pbs::ast::ParsedAst;
use crate::frontends::pbs::collector::SymbolCollector;
use crate::frontends::pbs::lowering::Lowerer;
use crate::frontends::pbs::parser::Parser;
use crate::frontends::pbs::resolver::{ModuleProvider, Resolver};
use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility};
use crate::frontends::pbs::typecheck::TypeChecker;
use crate::frontends::pbs::types::PbsType;
use crate::frontends::pbs::{build_typed_module_symbols, SymbolCollector};
use crate::lowering::core_to_vm;
use crate::semantics::export_surface::ExportSurfaceKind;
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
use prometeu_analysis::ids::ProjectId;
use prometeu_analysis::NameInterner;
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use crate::frontends::pbs::parser::Parser;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExportKey {
@ -63,7 +62,7 @@ pub struct CompiledModule {
#[derive(Debug)]
pub enum CompileError {
Frontend(crate::common::diagnostics::DiagnosticBundle),
Frontend(DiagnosticBundle),
DuplicateExport {
symbol: String,
first_dep: String,
@ -77,9 +76,15 @@ impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> 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<CompiledModule, CompileError> {
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<String, ModuleSymbols> = 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<String, ModuleSymbols> = HashMap::new(); // keyed by module_path
let mut file_imported_symbols: HashMap<String, ModuleSymbols> = 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<String> = 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<crate::ir_vm::types::ConstId> = 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<String> = 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: "...:<symbol_name>"
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.
}
}

View File

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

View File

@ -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<String> = 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,

View File

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

View File

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

View File

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

View File

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

View File

@ -41,7 +41,9 @@ pub struct Symbol {
#[derive(Debug, Clone)]
pub struct SymbolTable {
pub symbols: HashMap<NameId, Symbol>,
// Allow multiple entries per name for overloaded functions (Value namespace only).
// For Type namespace, multiple entries are rejected by `insert`.
pub symbols: HashMap<NameId, Vec<Symbol>>,
}
#[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())
}
}

View File

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

View File

@ -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<DiagnosticBundle> for SourceError {
#[derive(Debug, Clone)]
pub struct ExportTable {
pub symbols: HashMap<String, Symbol>,
/// Exported symbols keyed by simple name.
/// For overloads, multiple symbols may exist under the same key.
pub symbols: HashMap<String, Vec<Symbol>>,
}
pub fn discover(project_dir: &Path) -> Result<ProjectSources, SourceError> {
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<ProjectSources, SourceError> {
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<PathBuf>) -> 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<ExportTable, SourceError> {
let mut symbols = HashMap::new();
let mut symbols: HashMap<String, Vec<Symbol>> = 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<Symbol> 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"));
}

View File

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