diff --git a/crates/frontend-api/src/types.rs b/crates/frontend-api/src/types.rs index 44fea3ef..7b05a84f 100644 --- a/crates/frontend-api/src/types.rs +++ b/crates/frontend-api/src/types.rs @@ -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()); } diff --git a/crates/prometeu-compiler/src/building/linker.rs b/crates/prometeu-compiler/src/building/linker.rs index e7772d5f..29d3f74c 100644 --- a/crates/prometeu-compiler/src/building/linker.rs +++ b/crates/prometeu-compiler/src/building/linker.rs @@ -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], }]; diff --git a/crates/prometeu-compiler/src/building/output.rs b/crates/prometeu-compiler/src/building/output.rs index 832a940d..95f19474 100644 --- a/crates/prometeu-compiler/src/building/output.rs +++ b/crates/prometeu-compiler/src/building/output.rs @@ -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 = 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)) }, ); } diff --git a/crates/prometeu-compiler/tests/export_conflicts.rs b/crates/prometeu-compiler/tests/export_conflicts.rs index e5a2174f..350f4bc6 100644 --- a/crates/prometeu-compiler/tests/export_conflicts.rs +++ b/crates/prometeu-compiler/tests/export_conflicts.rs @@ -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"))); } diff --git a/files/Hard Reset FE API.md b/files/Hard Reset FE API.md index b2c87d29..bee6f8ef 100644 --- a/files/Hard Reset FE API.md +++ b/files/Hard Reset FE API.md @@ -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 diff --git a/test-cartridges/canonical/golden/program.pbc b/test-cartridges/canonical/golden/program.pbc index 3646ac85..5a70490d 100644 Binary files a/test-cartridges/canonical/golden/program.pbc and b/test-cartridges/canonical/golden/program.pbc differ