This commit is contained in:
bQUARKz 2026-02-10 20:49:00 +00:00
parent f994a566d8
commit ab2ebba286
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
6 changed files with 29 additions and 56 deletions

View File

@ -85,7 +85,7 @@ impl fmt::Display for ModulePath {
/// Canonical exported/declared item name.
/// Invariants:
/// - must start with [A-Z]
/// - must start with [A-Za-z] (types/services typically use UpperCamel; functions may be lowerCamel)
/// - remaining chars: [A-Za-z0-9_]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
@ -97,7 +97,7 @@ impl ItemName {
if s.is_empty() { return Err(CanonError::Empty("ItemName")); }
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_uppercase() => {}
Some(c) if c.is_ascii_alphabetic() => {}
_ => return Err(CanonError::InvalidStart("ItemName")),
}
if !s[1..].chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
@ -310,7 +310,7 @@ mod tests {
fn item_name_rules() {
assert!(ItemName::new("Test").is_ok());
assert!(ItemName::new("Log2").is_ok());
assert!(ItemName::new("test").is_err());
assert!(ItemName::new("test").is_ok());
assert!(ItemName::new("_Test").is_err());
assert!(ItemName::new("Te-st").is_err());
}

View File

@ -440,7 +440,8 @@ mod tests {
let mut lib_exports = BTreeMap::new();
use frontend_api::types::{ExportItem, ItemName, CanonicalFnKey, SignatureRef};
let add_key = ExportItem::Function { fn_key: CanonicalFnKey::new(None, ItemName::new("add").unwrap(), SignatureRef(0)) };
// NOTE: ItemName validation may enforce capitalized identifiers; for test purposes use a canonical valid name.
let add_key = ExportItem::Function { fn_key: CanonicalFnKey::new(None, ItemName::new("Add").unwrap(), SignatureRef(0)) };
lib_exports.insert(ExportKey { module_path: "math".into(), item: add_key }, ExportMetadata { func_idx: Some(0), is_host: false, ty: None });
let lib_module = CompiledModule {
@ -476,7 +477,7 @@ mod tests {
key: ImportKey {
dep_alias: "mylib".into(),
module_path: "math".into(),
symbol_name: "add".into(),
symbol_name: "Add".into(),
},
relocation_pcs: vec![call_pc],
}];

View File

@ -135,6 +135,16 @@ pub fn compile_project(
use std::collections::HashSet;
#[derive(Hash, Eq, PartialEq)]
struct DepKey(String, ExportItem); // (module_path, item)
fn display_export_item(item: &ExportItem) -> String {
match item {
ExportItem::Type { name } | ExportItem::Service { name } => name.as_str().to_string(),
ExportItem::Function { fn_key } => {
let base = fn_key.debug_name();
format!("{}#sig{}", base, fn_key.sig.0)
}
}
}
let mut dep_seen: HashSet<DepKey> = HashSet::new();
for (alias, project_id) in &step.deps {
if let Some(compiled) = dep_modules.get(project_id) {
@ -148,7 +158,7 @@ pub fn compile_project(
let k = DepKey(sp.clone(), key.item.clone());
if !dep_seen.insert(k) {
return Err(CompileError::DuplicateExport {
symbol: format!("{:?}", key.item),
symbol: display_export_item(&key.item),
first_dep: alias.clone(),
second_dep: alias.clone(),
});
@ -191,7 +201,7 @@ pub fn compile_project(
let dep_key = DepKey(module_path.clone(), it.clone());
if dep_seen.contains(&dep_key) {
return Err(CompileError::DuplicateExport {
symbol: format!("{:?}", it),
symbol: display_export_item(&it),
first_dep: "dependency".to_string(),
second_dep: "local".to_string(),
});
@ -265,13 +275,18 @@ pub fn compile_project(
);
}
ExportItem::Function { fn_key } => {
// Map function to VM function index by name + signature id
// Map function to VM function index by name and overwrite FE-provided sig with actual VM sig id
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 != fn_key.name.as_str() { continue; }
if f.sig.0 != fn_key.sig.0 { continue; }
// Rebuild canonical key with authoritative BE signature id
let fixed_key = ExportItem::Function { fn_key: frontend_api::types::CanonicalFnKey::new(
fn_key.owner.clone(),
fn_key.name.clone(),
frontend_api::types::SignatureRef(f.sig.0 as u32),
)};
exports.insert(
ExportKey { module_path: module_path.clone(), item: item.clone() },
ExportKey { module_path: module_path.clone(), item: fixed_key },
ExportMetadata { func_idx: Some(i as u32), is_host: false, ty: Some(TypeRef(f.sig.0 as u32)) },
);
}

View File

@ -225,7 +225,7 @@ fn test_module_merging_same_directory() {
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
// Both should be in the same module "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" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Gfx")));
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Color")));
}
@ -287,6 +287,6 @@ fn test_root_module_merging() {
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
// Both should be in the root module ""
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 == "" && matches!(&k.item, ExportItem::Service { name } if name.as_str() == "Utils")));
assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Main")));
assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Utils")));
}

View File

@ -14,49 +14,6 @@
---
## PR-03.05 — Canonical export surface: `ExportItem` (no `svc:` / no `name#sig` strings)
### Title
Replace stringy export naming with canonical `ExportItem` model
### Briefing / Context
Exports are currently keyed by `(module_path, symbol_name string, kind)` where symbol_name embeds `#sig` and/or owner names. This is fragile and couples FE naming to BE behavior.
### Target
* BE export map keys are canonical:
* `ExportItem::Type { name }`
* `ExportItem::Service { name }`
* `ExportItem::Function { fn_key: CanonicalFnKey }`
* Export surface remains stable even if we later change display formatting.
### Scope
* Update compiled module export structures.
* Update dependency symbol synthesis to use canonical export items.
* Update linker relocation labels to reference canonical export items.
### Checklist
* [ ] Introduce `ExportItem` and migrate ExportKey.
* [ ] Update dependency export synthesis.
* [ ] Update linker/import label format (if used) to canonical encoding.
* [ ] Ensure backward compatibility is explicitly NOT required for Phase 03.
### Tests
* Unit: exporting a service method yields `ExportItem::Function { owner=Log, name=debug, sig=... }`.
* Integration: build root + dep, link, run golden.
### Risk
High. Touches serialization and linking labels.
---
## PR-03.06 — Deterministic overload resolution across deps (arity is not enough)
### Title