intermediary state, safe point
This commit is contained in:
parent
c80260e780
commit
f5d259ba2a
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
37
crates/prometeu-compiler/src/frontends/pbs/frontend.rs
Normal file
37
crates/prometeu-compiler/src/frontends/pbs/frontend.rs
Normal 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(())
|
||||
}
|
||||
@ -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());
|
||||
|
||||
@ -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)?;
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
// }
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
@ -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**
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user