pr 03.04
This commit is contained in:
parent
a4cc1487c4
commit
f994a566d8
@ -164,16 +164,18 @@ pub enum ExportKind {
|
|||||||
Const,
|
Const,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An export item description (opaque for BE logic, except name/kind).
|
/// Canonical export item (FE-owned, BE-agnostic, no string protocols).
|
||||||
|
///
|
||||||
|
/// This surface is stable even if display formatting changes.
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||||
pub struct ExportItem {
|
pub enum ExportItem {
|
||||||
pub name: ItemName,
|
/// A declared canonical type (struct/enum/etc.)
|
||||||
pub kind: ExportKind,
|
Type { name: ItemName },
|
||||||
}
|
/// A declared service owner (method container)
|
||||||
|
Service { name: ItemName },
|
||||||
impl ExportItem {
|
/// A function export (free fn or method) identified by canonical key
|
||||||
pub fn new(name: ItemName, kind: ExportKind) -> Self { Self { name, kind } }
|
Function { fn_key: CanonicalFnKey },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A fully-qualified export reference: project + module + item + kind.
|
/// A fully-qualified export reference: project + module + item + kind.
|
||||||
|
|||||||
@ -81,8 +81,11 @@ impl Linker {
|
|||||||
let mut combined_pc_to_span = Vec::new();
|
let mut combined_pc_to_span = Vec::new();
|
||||||
let mut combined_function_names = Vec::new();
|
let mut combined_function_names = Vec::new();
|
||||||
|
|
||||||
// 1. DebugSymbol resolution map: (ProjectKey, module_path, symbol_name) -> func_idx in combined_functions
|
// 1. DebugSymbol resolution maps:
|
||||||
|
// - canonical: (ProjectKey, module_path, ExportItem) -> func_idx
|
||||||
|
// - compatibility (Phase 03 transitional): (ProjectKey, module_path, short_name) -> func_idx
|
||||||
let mut global_symbols = HashMap::new();
|
let mut global_symbols = HashMap::new();
|
||||||
|
let mut global_symbols_str = HashMap::new();
|
||||||
|
|
||||||
let mut module_code_offsets = Vec::with_capacity(modules.len());
|
let mut module_code_offsets = Vec::with_capacity(modules.len());
|
||||||
let mut module_function_offsets = Vec::with_capacity(modules.len());
|
let mut module_function_offsets = Vec::with_capacity(modules.len());
|
||||||
@ -102,12 +105,25 @@ impl Linker {
|
|||||||
if let Some(local_func_idx) = meta.func_idx {
|
if let Some(local_func_idx) = meta.func_idx {
|
||||||
let global_func_idx = function_offset + local_func_idx;
|
let global_func_idx = function_offset + local_func_idx;
|
||||||
// Note: Use a tuple as key for clarity
|
// Note: Use a tuple as key for clarity
|
||||||
let symbol_id = (module.project_id.clone(), key.module_path.clone(), key.symbol_name.clone());
|
let symbol_id = (module.project_id.clone(), key.module_path.clone(), key.item.clone());
|
||||||
|
|
||||||
if global_symbols.contains_key(&symbol_id) {
|
if global_symbols.contains_key(&symbol_id) {
|
||||||
return Err(LinkError::DuplicateExport(format!("Project {:?} export {}:{} already defined", symbol_id.0, symbol_id.1, symbol_id.2)));
|
return Err(LinkError::DuplicateExport(format!(
|
||||||
|
"Project {:?} export {}:{:?} already defined",
|
||||||
|
symbol_id.0, symbol_id.1, symbol_id.2
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
// Canonical mapping
|
||||||
global_symbols.insert(symbol_id, global_func_idx);
|
global_symbols.insert(symbol_id, global_func_idx);
|
||||||
|
|
||||||
|
// Compatibility string mapping (short name only)
|
||||||
|
let short_name = match &key.item {
|
||||||
|
frontend_api::types::ExportItem::Function { fn_key } => fn_key.name.as_str().to_string(),
|
||||||
|
frontend_api::types::ExportItem::Service { name } => name.as_str().to_string(),
|
||||||
|
frontend_api::types::ExportItem::Type { name } => name.as_str().to_string(),
|
||||||
|
};
|
||||||
|
let symbol_id_str = (module.project_id.clone(), key.module_path.clone(), short_name);
|
||||||
|
global_symbols_str.insert(symbol_id_str, global_func_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +187,7 @@ impl Linker {
|
|||||||
for pid in candidate_projects {
|
for pid in candidate_projects {
|
||||||
let pid_val: ProjectId = (*pid).clone();
|
let pid_val: ProjectId = (*pid).clone();
|
||||||
let key = (pid_val, import.key.module_path.clone(), import.key.symbol_name.clone());
|
let key = (pid_val, import.key.module_path.clone(), import.key.symbol_name.clone());
|
||||||
if let Some(&idx) = global_symbols.get(&key) {
|
if let Some(&idx) = global_symbols_str.get(&key) {
|
||||||
resolved_idx = Some(idx);
|
resolved_idx = Some(idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -272,10 +288,12 @@ impl Linker {
|
|||||||
for (key, meta) in &root_module.exports {
|
for (key, meta) in &root_module.exports {
|
||||||
if let Some(local_func_idx) = meta.func_idx {
|
if let Some(local_func_idx) = meta.func_idx {
|
||||||
let global_func_idx = module_function_offsets.last().unwrap() + local_func_idx;
|
let global_func_idx = module_function_offsets.last().unwrap() + local_func_idx;
|
||||||
final_exports.insert(format!("{}:{}", key.module_path, key.symbol_name), global_func_idx);
|
final_exports.insert(format!("{}:{:?}", key.module_path, key.item), global_func_idx);
|
||||||
// Also provide short name for root module exports to facilitate entrypoint resolution
|
// Also provide short name for root module exports to facilitate entrypoint resolution.
|
||||||
if !final_exports.contains_key(&key.symbol_name) {
|
// For canonical items, we fall back to the `Debug` representation without the module path.
|
||||||
final_exports.insert(key.symbol_name.clone(), global_func_idx);
|
let short = format!("{:?}", key.item);
|
||||||
|
if !final_exports.contains_key(&short) {
|
||||||
|
final_exports.insert(short, global_func_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -421,11 +439,9 @@ mod tests {
|
|||||||
lib_code.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
lib_code.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||||
|
|
||||||
let mut lib_exports = BTreeMap::new();
|
let mut lib_exports = BTreeMap::new();
|
||||||
lib_exports.insert(ExportKey {
|
use frontend_api::types::{ExportItem, ItemName, CanonicalFnKey, SignatureRef};
|
||||||
module_path: "math".into(),
|
let add_key = ExportItem::Function { fn_key: CanonicalFnKey::new(None, ItemName::new("add").unwrap(), SignatureRef(0)) };
|
||||||
symbol_name: "add".into(),
|
lib_exports.insert(ExportKey { module_path: "math".into(), item: add_key }, ExportMetadata { func_idx: Some(0), is_host: false, ty: None });
|
||||||
kind: ExportSurfaceKind::Service,
|
|
||||||
}, ExportMetadata { func_idx: Some(0), is_host: false, ty: None });
|
|
||||||
|
|
||||||
let lib_module = CompiledModule {
|
let lib_module = CompiledModule {
|
||||||
project_id: lib_id,
|
project_id: lib_id,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use crate::semantics::export_surface::ExportSurfaceKind;
|
|||||||
use prometeu_analysis::ids::ProjectId;
|
use prometeu_analysis::ids::ProjectId;
|
||||||
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
|
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use frontend_api::types::TypeRef;
|
use frontend_api::types::{TypeRef, ExportItem};
|
||||||
use frontend_api::traits::Frontend as CanonFrontend;
|
use frontend_api::traits::Frontend as CanonFrontend;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
@ -25,8 +25,7 @@ fn symbol_name_hash(name: &str) -> u32 {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct ExportKey {
|
pub struct ExportKey {
|
||||||
pub module_path: String,
|
pub module_path: String,
|
||||||
pub symbol_name: String,
|
pub item: ExportItem,
|
||||||
pub kind: ExportSurfaceKind,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -135,23 +134,21 @@ pub fn compile_project(
|
|||||||
// Build dependency synthetic export keys and detect cross-dependency duplicates upfront
|
// Build dependency synthetic export keys and detect cross-dependency duplicates upfront
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
#[derive(Hash, Eq, PartialEq)]
|
#[derive(Hash, Eq, PartialEq)]
|
||||||
struct DepKey(String, String, u8); // (module_path, symbol_name, kind_id)
|
struct DepKey(String, ExportItem); // (module_path, item)
|
||||||
fn kind_id(k: ExportSurfaceKind) -> u8 { match k { ExportSurfaceKind::Function => 0, ExportSurfaceKind::Service => 1, ExportSurfaceKind::DeclareType => 2 } }
|
|
||||||
let mut dep_seen: HashSet<DepKey> = HashSet::new();
|
let mut dep_seen: HashSet<DepKey> = HashSet::new();
|
||||||
for (alias, project_id) in &step.deps {
|
for (alias, project_id) in &step.deps {
|
||||||
if let Some(compiled) = dep_modules.get(project_id) {
|
if let Some(compiled) = dep_modules.get(project_id) {
|
||||||
for (key, _meta) in &compiled.exports {
|
for (key, _meta) in &compiled.exports {
|
||||||
// Track both legacy and canonical forms ONLY for collision detection between deps and local modules.
|
// Track using canonical item keyed by module path; alias variations only for display/conflict reporting.
|
||||||
// Import resolution uses only '@alias:module' elsewhere.
|
|
||||||
let synthetic_paths = [
|
let synthetic_paths = [
|
||||||
format!("{}/{}", alias, key.module_path),
|
format!("{}/{}", alias, key.module_path),
|
||||||
format!("@{}:{}", alias, key.module_path),
|
format!("@{}:{}", alias, key.module_path),
|
||||||
];
|
];
|
||||||
for sp in synthetic_paths {
|
for sp in synthetic_paths {
|
||||||
let k = DepKey(sp.clone(), key.symbol_name.clone(), kind_id(key.kind));
|
let k = DepKey(sp.clone(), key.item.clone());
|
||||||
if !dep_seen.insert(k) {
|
if !dep_seen.insert(k) {
|
||||||
return Err(CompileError::DuplicateExport {
|
return Err(CompileError::DuplicateExport {
|
||||||
symbol: key.symbol_name.clone(),
|
symbol: format!("{:?}", key.item),
|
||||||
first_dep: alias.clone(),
|
first_dep: alias.clone(),
|
||||||
second_dep: alias.clone(),
|
second_dep: alias.clone(),
|
||||||
});
|
});
|
||||||
@ -190,36 +187,23 @@ pub fn compile_project(
|
|||||||
// Aggregate FE exports per module, detecting duplicates and dep conflicts
|
// Aggregate FE exports per module, detecting duplicates and dep conflicts
|
||||||
let entry = fe_exports_per_module.entry(module_path.clone()).or_insert_with(Vec::new);
|
let entry = fe_exports_per_module.entry(module_path.clone()).or_insert_with(Vec::new);
|
||||||
for it in unit.exports {
|
for it in unit.exports {
|
||||||
let kind = match it.kind {
|
|
||||||
frontend_api::types::ExportKind::Function => ExportSurfaceKind::Function,
|
|
||||||
frontend_api::types::ExportKind::Service => ExportSurfaceKind::Service,
|
|
||||||
frontend_api::types::ExportKind::Type => ExportSurfaceKind::DeclareType,
|
|
||||||
frontend_api::types::ExportKind::Const => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Conflict with dependency synthetic exports?
|
// Conflict with dependency synthetic exports?
|
||||||
let dep_key = DepKey(module_path.clone(), it.name.as_str().to_string(), kind_id(kind));
|
let dep_key = DepKey(module_path.clone(), it.clone());
|
||||||
if dep_seen.contains(&dep_key) {
|
if dep_seen.contains(&dep_key) {
|
||||||
return Err(CompileError::DuplicateExport {
|
return Err(CompileError::DuplicateExport {
|
||||||
symbol: it.name.as_str().to_string(),
|
symbol: format!("{:?}", it),
|
||||||
first_dep: "dependency".to_string(),
|
first_dep: "dependency".to_string(),
|
||||||
second_dep: "local".to_string(),
|
second_dep: "local".to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local duplicate within same module?
|
// Local duplicate within same module?
|
||||||
let already = entry.iter().any(|e| e.name.as_str() == it.name.as_str() && {
|
let already = entry.iter().any(|e| e == &it);
|
||||||
matches!((e.kind, it.kind),
|
|
||||||
(frontend_api::types::ExportKind::Type, frontend_api::types::ExportKind::Type)
|
|
||||||
| (frontend_api::types::ExportKind::Service, frontend_api::types::ExportKind::Service)
|
|
||||||
| (frontend_api::types::ExportKind::Function, frontend_api::types::ExportKind::Function)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
if already {
|
if already {
|
||||||
return Err(CompileError::Frontend(
|
return Err(CompileError::Frontend(
|
||||||
DiagnosticBundle::error(
|
DiagnosticBundle::error(
|
||||||
"E_RESOLVE_DUPLICATE_SYMBOL",
|
"E_RESOLVE_DUPLICATE_SYMBOL",
|
||||||
format!("Duplicate symbol '{}' in module '{}'", it.name.as_str(), module_path),
|
format!("Duplicate symbol '{:?}' in module '{}'", it, module_path),
|
||||||
crate::common::spans::Span::new(crate::common::spans::FileId::INVALID, 0, 0),
|
crate::common::spans::Span::new(crate::common::spans::FileId::INVALID, 0, 0),
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
@ -266,59 +250,32 @@ pub fn compile_project(
|
|||||||
|
|
||||||
for (module_path, items) in &fe_exports_per_module {
|
for (module_path, items) in &fe_exports_per_module {
|
||||||
for item in items {
|
for item in items {
|
||||||
let kind = match item.kind {
|
match item {
|
||||||
frontend_api::types::ExportKind::Function => ExportSurfaceKind::Function,
|
ExportItem::Type { name } => {
|
||||||
frontend_api::types::ExportKind::Service => ExportSurfaceKind::Service,
|
exports.insert(
|
||||||
frontend_api::types::ExportKind::Type => ExportSurfaceKind::DeclareType,
|
ExportKey { module_path: module_path.clone(), item: item.clone() },
|
||||||
frontend_api::types::ExportKind::Const => continue,
|
ExportMetadata { func_idx: None, is_host: false, ty: Some(TypeRef(symbol_name_hash(name.as_str()))) },
|
||||||
};
|
);
|
||||||
|
}
|
||||||
if kind == ExportSurfaceKind::DeclareType || kind == ExportSurfaceKind::Service {
|
ExportItem::Service { name: _ } => {
|
||||||
exports.insert(
|
// Service owner export (no functions synthesized here)
|
||||||
ExportKey {
|
exports.insert(
|
||||||
module_path: module_path.clone(),
|
ExportKey { module_path: module_path.clone(), item: item.clone() },
|
||||||
symbol_name: item.name.as_str().to_string(),
|
ExportMetadata { func_idx: None, is_host: false, ty: None },
|
||||||
kind,
|
);
|
||||||
},
|
}
|
||||||
ExportMetadata { func_idx: None, is_host: false, ty: Some(TypeRef(symbol_name_hash(item.name.as_str()))) },
|
ExportItem::Function { fn_key } => {
|
||||||
);
|
// Map function to VM function index by name + signature id
|
||||||
|
|
||||||
// Heuristic: also export service methods as qualified functions "Service.method#sigN"
|
|
||||||
if kind == ExportSurfaceKind::Service {
|
|
||||||
let svc_name = item.name.as_str().to_string();
|
|
||||||
for (i, f) in combined_vm.functions.iter().enumerate() {
|
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()) {
|
if combined_func_origins.get(i).map(|s| s.as_str()) != Some(module_path.as_str()) { continue; }
|
||||||
continue;
|
if f.name != fn_key.name.as_str() { continue; }
|
||||||
}
|
if f.sig.0 != fn_key.sig.0 { continue; }
|
||||||
// Skip known runtime/entry function names
|
|
||||||
if f.name == "frame" { continue; }
|
|
||||||
|
|
||||||
let sig_name = format!("{}.#sig{}", f.name, f.sig.0); // keep compatibility with below concatenation
|
|
||||||
let qualified = format!("{}.{}#sig{}", svc_name, f.name, f.sig.0);
|
|
||||||
exports.insert(
|
exports.insert(
|
||||||
ExportKey { module_path: module_path.clone(), symbol_name: qualified, kind: ExportSurfaceKind::Function },
|
ExportKey { module_path: module_path.clone(), item: item.clone() },
|
||||||
ExportMetadata { func_idx: Some(i as u32), is_host: false, ty: Some(TypeRef(f.sig.0 as u32)) },
|
ExportMetadata { func_idx: Some(i as u32), is_host: false, ty: Some(TypeRef(f.sig.0 as u32)) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Map function export to VM function index by simple name (strip qualification)
|
|
||||||
let name_full = item.name.as_str();
|
|
||||||
let expected_vm_name = name_full.split('.').last().unwrap_or(name_full);
|
|
||||||
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_full, f.sig.0);
|
|
||||||
exports.insert(
|
|
||||||
ExportKey { module_path: module_path.clone(), symbol_name: sig_name, kind },
|
|
||||||
ExportMetadata { func_idx: Some(i as u32), is_host: false, ty: Some(TypeRef(f.sig.0 as u32)) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,6 +376,7 @@ mod tests {
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use crate::frontends::pbs::adapter::PbsFrontendAdapter;
|
use crate::frontends::pbs::adapter::PbsFrontendAdapter;
|
||||||
|
use frontend_api::types::{ItemName, ExportItem, CanonicalFnKey};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compile_root_only_project() {
|
fn test_compile_root_only_project() {
|
||||||
@ -466,11 +424,10 @@ mod tests {
|
|||||||
assert_eq!(compiled.project_id, project_id);
|
assert_eq!(compiled.project_id, project_id);
|
||||||
assert_eq!(compiled.target, BuildTarget::Main);
|
assert_eq!(compiled.target, BuildTarget::Main);
|
||||||
|
|
||||||
// Vec2 should be exported
|
// Vec2 should be exported (canonical)
|
||||||
let vec2_key = ExportKey {
|
let vec2_key = ExportKey {
|
||||||
module_path: "".to_string(),
|
module_path: "".to_string(),
|
||||||
symbol_name: "Vec2".to_string(),
|
item: ExportItem::Type { name: ItemName::new("Vec2").unwrap() },
|
||||||
kind: ExportSurfaceKind::DeclareType,
|
|
||||||
};
|
};
|
||||||
assert!(compiled.exports.contains_key(&vec2_key));
|
assert!(compiled.exports.contains_key(&vec2_key));
|
||||||
}
|
}
|
||||||
@ -511,15 +468,17 @@ mod tests {
|
|||||||
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager)
|
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager)
|
||||||
.expect("Failed to compile project");
|
.expect("Failed to compile project");
|
||||||
|
|
||||||
// Find a function export with qualified method name prefix "Log.debug#sig"
|
// Find a function export with canonical fn key: owner=Log, name=debug
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for (key, _meta) in &compiled.exports {
|
for (key, _meta) in &compiled.exports {
|
||||||
if key.kind == ExportSurfaceKind::Function && key.symbol_name.starts_with("Log.debug#sig") {
|
if let ExportItem::Function { fn_key } = &key.item {
|
||||||
found = true;
|
if fn_key.owner.as_ref().map(|n| n.as_str()) == Some("Log") && fn_key.name.as_str() == "debug" {
|
||||||
break;
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(found, "Expected an export with qualified name 'Log.debug#sigX' but not found. Exports: {:?}", compiled.exports.keys().collect::<Vec<_>>());
|
assert!(found, "Expected an export with canonical fn key owner=Log, name=debug but not found. Exports: {:?}", compiled.exports.keys().collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,17 @@ use crate::frontends::pbs::{parser::Parser, SymbolCollector, ModuleSymbols, Reso
|
|||||||
use crate::lowering::core_to_vm;
|
use crate::lowering::core_to_vm;
|
||||||
use crate::common::spans::FileId;
|
use crate::common::spans::FileId;
|
||||||
use frontend_api::traits::{Frontend as CanonFrontend, FrontendUnit};
|
use frontend_api::traits::{Frontend as CanonFrontend, FrontendUnit};
|
||||||
use frontend_api::types::{Diagnostic as CanonDiagnostic, Severity as CanonSeverity, ExportItem, ExportKind, ItemName, LoweredIr, ImportRef, parse_pbs_from_string};
|
use frontend_api::types::{
|
||||||
|
Diagnostic as CanonDiagnostic,
|
||||||
|
Severity as CanonSeverity,
|
||||||
|
ExportItem,
|
||||||
|
ItemName,
|
||||||
|
LoweredIr,
|
||||||
|
ImportRef,
|
||||||
|
CanonicalFnKey,
|
||||||
|
SignatureRef,
|
||||||
|
parse_pbs_from_string,
|
||||||
|
};
|
||||||
use prometeu_analysis::NameInterner;
|
use prometeu_analysis::NameInterner;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@ -90,38 +100,33 @@ impl CanonFrontend for PbsFrontendAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare canonical exports from symbol tables (names only, kind best-effort)
|
// Prepare canonical exports from symbol tables (canonical, no string protocols)
|
||||||
let mut exports: Vec<ExportItem> = Vec::new();
|
let mut exports: Vec<ExportItem> = Vec::new();
|
||||||
for list in module_symbols.type_symbols.symbols.values() {
|
for list in module_symbols.type_symbols.symbols.values() {
|
||||||
for sym in list {
|
for sym in list {
|
||||||
if crate::frontends::pbs::symbols::Visibility::Pub != sym.visibility { continue; }
|
if crate::frontends::pbs::symbols::Visibility::Pub != sym.visibility { continue; }
|
||||||
if let Ok(name) = ItemName::new(interner.resolve(sym.name)) {
|
if let Ok(name) = ItemName::new(interner.resolve(sym.name)) {
|
||||||
let ek = match sym.kind {
|
match sym.kind {
|
||||||
crate::frontends::pbs::symbols::SymbolKind::Service => ExportKind::Service,
|
crate::frontends::pbs::symbols::SymbolKind::Service => exports.push(ExportItem::Service { name }),
|
||||||
_ => ExportKind::Type,
|
_ => exports.push(ExportItem::Type { name }),
|
||||||
};
|
}
|
||||||
exports.push(ExportItem::new(name, ek));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for list in module_symbols.value_symbols.symbols.values() {
|
for list in module_symbols.value_symbols.symbols.values() {
|
||||||
for sym in list {
|
for sym in list {
|
||||||
if crate::frontends::pbs::symbols::Visibility::Pub != sym.visibility { continue; }
|
if crate::frontends::pbs::symbols::Visibility::Pub != sym.visibility { continue; }
|
||||||
// Qualify service methods as "Service.method" for canonical naming
|
// Build canonical function key for free fns and methods
|
||||||
let raw_name = interner.resolve(sym.name);
|
let raw_name = interner.resolve(sym.name);
|
||||||
let qualified = if let Some(origin) = &sym.origin {
|
if let crate::frontends::pbs::symbols::SymbolKind::Function = sym.kind {
|
||||||
if let Some(svc) = origin.strip_prefix("svc:") {
|
// Attempt to derive owner from origin `svc:Owner` if present
|
||||||
format!("{}.{}", svc, raw_name)
|
let owner_name = sym.origin.as_deref().and_then(|o| o.strip_prefix("svc:")).and_then(|svc| ItemName::new(svc).ok());
|
||||||
} else {
|
// We don't have a stable signature id from PBS yet; use 0 as placeholder until resolver exposes it.
|
||||||
raw_name.to_string()
|
let sig = SignatureRef(0);
|
||||||
|
if let Ok(name_item) = ItemName::new(raw_name) {
|
||||||
|
let fn_key = CanonicalFnKey::new(owner_name, name_item, sig);
|
||||||
|
exports.push(ExportItem::Function { fn_key });
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
raw_name.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(name) = ItemName::new(&qualified) {
|
|
||||||
let kind = match sym.kind { crate::frontends::pbs::symbols::SymbolKind::Function => ExportKind::Function, crate::frontends::pbs::symbols::SymbolKind::Service => ExportKind::Service, _ => ExportKind::Const };
|
|
||||||
exports.push(ExportItem::new(name, kind));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use prometeu_compiler::building::output::{compile_project, CompileError, ExportK
|
|||||||
use prometeu_compiler::building::plan::{BuildStep, BuildTarget};
|
use prometeu_compiler::building::plan::{BuildStep, BuildTarget};
|
||||||
use prometeu_compiler::common::files::FileManager;
|
use prometeu_compiler::common::files::FileManager;
|
||||||
use prometeu_compiler::deps::resolver::ProjectKey;
|
use prometeu_compiler::deps::resolver::ProjectKey;
|
||||||
use prometeu_compiler::semantics::export_surface::ExportSurfaceKind;
|
use frontend_api::types::{ExportItem, ItemName};
|
||||||
use prometeu_compiler::frontends::pbs::adapter::PbsFrontendAdapter;
|
use prometeu_compiler::frontends::pbs::adapter::PbsFrontendAdapter;
|
||||||
use prometeu_analysis::ids::ProjectId;
|
use prometeu_analysis::ids::ProjectId;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
@ -23,8 +23,7 @@ fn test_local_vs_dependency_conflict() {
|
|||||||
let mut dep_exports = BTreeMap::new();
|
let mut dep_exports = BTreeMap::new();
|
||||||
dep_exports.insert(ExportKey {
|
dep_exports.insert(ExportKey {
|
||||||
module_path: "math".to_string(), // normalized path
|
module_path: "math".to_string(), // normalized path
|
||||||
symbol_name: "Vector".to_string(),
|
item: ExportItem::Type { name: ItemName::new("Vector").unwrap() },
|
||||||
kind: ExportSurfaceKind::DeclareType,
|
|
||||||
}, ExportMetadata {
|
}, ExportMetadata {
|
||||||
func_idx: None,
|
func_idx: None,
|
||||||
is_host: false,
|
is_host: false,
|
||||||
@ -89,8 +88,7 @@ fn test_aliased_dependency_conflict() {
|
|||||||
let mut dep1_exports = BTreeMap::new();
|
let mut dep1_exports = BTreeMap::new();
|
||||||
dep1_exports.insert(ExportKey {
|
dep1_exports.insert(ExportKey {
|
||||||
module_path: "b/c".to_string(),
|
module_path: "b/c".to_string(),
|
||||||
symbol_name: "Vector".to_string(),
|
item: ExportItem::Type { name: ItemName::new("Vector").unwrap() },
|
||||||
kind: ExportSurfaceKind::DeclareType,
|
|
||||||
}, ExportMetadata {
|
}, ExportMetadata {
|
||||||
func_idx: None,
|
func_idx: None,
|
||||||
is_host: false,
|
is_host: false,
|
||||||
@ -115,8 +113,7 @@ fn test_aliased_dependency_conflict() {
|
|||||||
let mut dep2_exports = BTreeMap::new();
|
let mut dep2_exports = BTreeMap::new();
|
||||||
dep2_exports.insert(ExportKey {
|
dep2_exports.insert(ExportKey {
|
||||||
module_path: "c".to_string(),
|
module_path: "c".to_string(),
|
||||||
symbol_name: "Vector".to_string(),
|
item: ExportItem::Type { name: ItemName::new("Vector").unwrap() },
|
||||||
kind: ExportSurfaceKind::DeclareType,
|
|
||||||
}, ExportMetadata {
|
}, ExportMetadata {
|
||||||
func_idx: None,
|
func_idx: None,
|
||||||
is_host: false,
|
is_host: false,
|
||||||
@ -228,8 +225,8 @@ fn test_module_merging_same_directory() {
|
|||||||
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
|
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
|
||||||
|
|
||||||
// Both should be in the same module "gfx"
|
// Both should be in the same module "gfx"
|
||||||
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && k.symbol_name == "Gfx"));
|
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Service { name } if name.as_str() == "Gfx")));
|
||||||
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && k.symbol_name == "Color"));
|
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Color")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -290,6 +287,6 @@ fn test_root_module_merging() {
|
|||||||
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
|
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
|
||||||
|
|
||||||
// Both should be in the root module ""
|
// Both should be in the root module ""
|
||||||
assert!(compiled.exports.keys().any(|k| k.module_path == "" && k.symbol_name == "Main"));
|
assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Service { name } if name.as_str() == "Main")));
|
||||||
assert!(compiled.exports.keys().any(|k| k.module_path == "" && k.symbol_name == "Utils"));
|
assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Service { name } if name.as_str() == "Utils")));
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user