From 45696b99e7a40c8b1e9468ce2fa1845505ca2ec4 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Mon, 2 Feb 2026 23:06:09 +0000 Subject: [PATCH] pr 65 --- .../prometeu-compiler/src/building/output.rs | 57 +++- .../tests/export_conflicts.rs | 259 ++++++++++++++++++ docs/specs/hardware/topics/chapter-2.md | 13 +- test-cartridges/canonical/golden/program.pbc | Bin 1087 -> 1087 bytes test-cartridges/test01/cartridge/program.pbc | Bin 1087 -> 1087 bytes 5 files changed, 308 insertions(+), 21 deletions(-) create mode 100644 crates/prometeu-compiler/tests/export_conflicts.rs diff --git a/crates/prometeu-compiler/src/building/output.rs b/crates/prometeu-compiler/src/building/output.rs index d4febf7f..b45163a8 100644 --- a/crates/prometeu-compiler/src/building/output.rs +++ b/crates/prometeu-compiler/src/building/output.rs @@ -17,7 +17,6 @@ use crate::lowering::core_to_vm; use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; -use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ExportKey { @@ -61,6 +60,11 @@ pub struct CompiledModule { #[derive(Debug)] pub enum CompileError { Frontend(crate::common::diagnostics::DiagnosticBundle), + DuplicateExport { + symbol: String, + first_dep: String, + second_dep: String, + }, Io(std::io::Error), Internal(String), } @@ -69,6 +73,9 @@ 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::Io(e) => write!(f, "IO error: {}", e), CompileError::Internal(s) => write!(f, "Internal error: {}", s), } @@ -107,7 +114,7 @@ pub fn compile_project( // 1. Parse all files and group symbols by module let mut module_symbols_map: HashMap = HashMap::new(); - let mut parsed_files: Vec<(String, FileNode, String)> = Vec::new(); // (module_path, ast, file_stem) + let mut parsed_files: Vec<(String, FileNode)> = Vec::new(); // (module_path, ast) for source_rel in &step.sources { let source_abs = step.project_dir.join(source_rel); @@ -120,10 +127,19 @@ pub fn compile_project( let mut collector = SymbolCollector::new(); let (ts, vs) = collector.collect(&ast)?; - let module_path = source_rel.parent() - .unwrap_or(Path::new("")) - .to_string_lossy() - .replace('\\', "/"); + 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 + } else if let Some(stripped) = full_path.strip_prefix("src/test/modules/") { + stripped + } else { + &full_path + }; + + let module_path = std::path::Path::new(logical_module_path) + .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); @@ -145,8 +161,7 @@ pub fn compile_project( } } - let file_stem = source_abs.file_stem().unwrap().to_string_lossy().to_string(); - parsed_files.push((module_path, ast, file_stem)); + parsed_files.push((module_path, ast)); } // 2. Synthesize ModuleSymbols for dependencies @@ -155,7 +170,7 @@ pub fn compile_project( 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.replace("src/main/modules/", ""); + let key_module_path = &key.module_path; let synthetic_paths = [ format!("{}/{}", alias, key_module_path), format!("@{}:{}", alias, key_module_path), @@ -185,9 +200,21 @@ pub fn compile_project( }; if sym.namespace == Namespace::Type { - ms.type_symbols.insert(sym).ok(); + if let Err(existing) = ms.type_symbols.insert(sym.clone()) { + return Err(CompileError::DuplicateExport { + symbol: sym.name, + first_dep: existing.origin.unwrap_or_else(|| "unknown".to_string()), + second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()), + }); + } } else { - ms.value_symbols.insert(sym).ok(); + if let Err(existing) = ms.value_symbols.insert(sym.clone()) { + return Err(CompileError::DuplicateExport { + symbol: sym.name, + first_dep: existing.origin.unwrap_or_else(|| "unknown".to_string()), + second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()), + }); + } } } } @@ -202,7 +229,7 @@ pub fn compile_project( // We need to collect imported symbols for Lowerer let mut file_imported_symbols: HashMap = HashMap::new(); // keyed by module_path - for (module_path, ast, _) in &parsed_files { + for (module_path, ast) in &parsed_files { let ms = module_symbols_map.get(module_path).unwrap(); let mut resolver = Resolver::new(ms, &module_provider); resolver.resolve(ast)?; @@ -225,11 +252,11 @@ pub fn compile_project( field_types: HashMap::new(), }; - for (module_path, ast, file_stem) in &parsed_files { + for (module_path, ast) 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(ms, imported); - let program = lowerer.lower_file(ast, &file_stem)?; + let program = lowerer.lower_file(ast, module_path)?; // Combine program into combined_program if combined_program.modules.is_empty() { @@ -367,7 +394,7 @@ mod tests { // Vec2 should be exported let vec2_key = ExportKey { - module_path: "src/main/modules".to_string(), + module_path: "".to_string(), symbol_name: "Vec2".to_string(), kind: ExportSurfaceKind::DeclareType, }; diff --git a/crates/prometeu-compiler/tests/export_conflicts.rs b/crates/prometeu-compiler/tests/export_conflicts.rs new file mode 100644 index 00000000..61843e84 --- /dev/null +++ b/crates/prometeu-compiler/tests/export_conflicts.rs @@ -0,0 +1,259 @@ +use std::collections::{BTreeMap, HashMap}; +use std::path::PathBuf; +use tempfile::tempdir; +use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata}; +use prometeu_compiler::building::plan::{BuildStep, BuildTarget}; +use prometeu_compiler::deps::resolver::ProjectId; +use prometeu_compiler::semantics::export_surface::ExportSurfaceKind; +use prometeu_compiler::building::output::CompiledModule; + +use std::fs; + +#[test] +fn test_local_vs_dependency_conflict() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().to_path_buf(); + + // Dependency: sdk + let dep_id = ProjectId { name: "sdk-impl".to_string(), version: "1.0.0".to_string() }; + let mut dep_exports = BTreeMap::new(); + dep_exports.insert(ExportKey { + module_path: "math".to_string(), // normalized path + symbol_name: "Vector".to_string(), + kind: ExportSurfaceKind::DeclareType, + }, ExportMetadata { + func_idx: None, + is_host: false, + ty: None, + }); + + let dep_module = CompiledModule { + project_id: dep_id.clone(), + target: BuildTarget::Main, + exports: dep_exports, + imports: vec![], + const_pool: vec![], + code: vec![], + function_metas: vec![], + debug_info: None, + }; + + let mut dep_modules = HashMap::new(); + dep_modules.insert(dep_id.clone(), dep_module); + + // Main project has a LOCAL module named "sdk/math" + // By creating a file in src/main/modules/sdk/math/, the module path becomes "sdk/math" + fs::create_dir_all(project_dir.join("src/main/modules/sdk/math")).unwrap(); + fs::write(project_dir.join("src/main/modules/sdk/math/local.pbs"), "pub declare struct Vector(x: int)").unwrap(); + + let main_id = ProjectId { name: "main".to_string(), version: "0.1.0".to_string() }; + let mut deps = BTreeMap::new(); + deps.insert("sdk".to_string(), dep_id.clone()); + + let step = BuildStep { + project_id: main_id, + project_dir, + target: BuildTarget::Main, + sources: vec![PathBuf::from("src/main/modules/sdk/math/local.pbs")], + deps, + }; + + let result = compile_project(step, &dep_modules); + + match result { + Err(CompileError::DuplicateExport { symbol, .. }) => { + assert_eq!(symbol, "Vector"); + }, + _ => panic!("Expected DuplicateExport error, got {:?}", result), + } +} + +#[test] +fn test_aliased_dependency_conflict() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().to_path_buf(); + + // Dependency 1: exports "b/c:Vector" + let dep1_id = ProjectId { name: "p1".to_string(), version: "1.0.0".to_string() }; + let mut dep1_exports = BTreeMap::new(); + dep1_exports.insert(ExportKey { + module_path: "b/c".to_string(), + symbol_name: "Vector".to_string(), + kind: ExportSurfaceKind::DeclareType, + }, ExportMetadata { + func_idx: None, + is_host: false, + ty: None, + }); + let dep1_module = CompiledModule { + project_id: dep1_id.clone(), + target: BuildTarget::Main, + exports: dep1_exports, + imports: vec![], + const_pool: vec![], + code: vec![], + function_metas: vec![], + debug_info: None, + }; + + // Dependency 2: exports "c:Vector" + let dep2_id = ProjectId { name: "p2".to_string(), version: "1.0.0".to_string() }; + let mut dep2_exports = BTreeMap::new(); + dep2_exports.insert(ExportKey { + module_path: "c".to_string(), + symbol_name: "Vector".to_string(), + kind: ExportSurfaceKind::DeclareType, + }, ExportMetadata { + func_idx: None, + is_host: false, + ty: None, + }); + let dep2_module = CompiledModule { + project_id: dep2_id.clone(), + target: BuildTarget::Main, + exports: dep2_exports, + imports: vec![], + const_pool: vec![], + code: vec![], + function_metas: vec![], + debug_info: None, + }; + + let mut dep_modules = HashMap::new(); + dep_modules.insert(dep1_id.clone(), dep1_module); + dep_modules.insert(dep2_id.clone(), dep2_module); + + let main_id = ProjectId { name: "main".to_string(), version: "0.1.0".to_string() }; + let mut deps = BTreeMap::new(); + deps.insert("a".to_string(), dep1_id.clone()); + deps.insert("a/b".to_string(), dep2_id.clone()); + + let step = BuildStep { + project_id: main_id, + project_dir, + target: BuildTarget::Main, + sources: vec![], + deps, + }; + + let result = compile_project(step, &dep_modules); + + match result { + Err(CompileError::DuplicateExport { symbol, .. }) => { + assert_eq!(symbol, "Vector"); + }, + _ => panic!("Expected DuplicateExport error, got {:?}", result), + } +} + +#[test] +fn test_mixed_main_test_modules() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().to_path_buf(); + + fs::create_dir_all(project_dir.join("src/main/modules/math")).unwrap(); + fs::write(project_dir.join("src/main/modules/math/Vector.pbs"), "pub declare struct Vector(x: int)").unwrap(); + + fs::create_dir_all(project_dir.join("src/test/modules/foo")).unwrap(); + fs::write(project_dir.join("src/test/modules/foo/Test.pbs"), "pub declare struct Test(x: int)").unwrap(); + + let project_id = ProjectId { name: "mixed".to_string(), version: "0.1.0".to_string() }; + let step = BuildStep { + project_id, + project_dir, + target: BuildTarget::Main, + sources: vec![ + PathBuf::from("src/main/modules/math/Vector.pbs"), + PathBuf::from("src/test/modules/foo/Test.pbs"), + ], + deps: BTreeMap::new(), + }; + + let compiled = compile_project(step, &HashMap::new()).unwrap(); + + // Both should be in exports with normalized paths + assert!(compiled.exports.keys().any(|k| k.module_path == "math")); + assert!(compiled.exports.keys().any(|k| k.module_path == "foo")); +} + +#[test] +fn test_module_merging_same_directory() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().to_path_buf(); + + fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap(); + fs::write(project_dir.join("src/main/modules/gfx/api.pbs"), "pub declare struct Gfx(id: int)").unwrap(); + fs::write(project_dir.join("src/main/modules/gfx/colors.pbs"), "pub declare struct Color(r: int)").unwrap(); + + let project_id = ProjectId { name: "merge".to_string(), version: "0.1.0".to_string() }; + let step = BuildStep { + project_id, + project_dir, + target: BuildTarget::Main, + sources: vec![ + PathBuf::from("src/main/modules/gfx/api.pbs"), + PathBuf::from("src/main/modules/gfx/colors.pbs"), + ], + deps: BTreeMap::new(), + }; + + let compiled = compile_project(step, &HashMap::new()).unwrap(); + + // 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" && k.symbol_name == "Color")); +} + +#[test] +fn test_duplicate_symbol_in_same_module_different_files() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().to_path_buf(); + + fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap(); + fs::write(project_dir.join("src/main/modules/gfx/a.pbs"), "pub declare struct Gfx(id: int)").unwrap(); + fs::write(project_dir.join("src/main/modules/gfx/b.pbs"), "pub declare struct Gfx(id: int)").unwrap(); + + let project_id = ProjectId { name: "dup".to_string(), version: "0.1.0".to_string() }; + let step = BuildStep { + project_id, + project_dir, + target: BuildTarget::Main, + sources: vec![ + PathBuf::from("src/main/modules/gfx/a.pbs"), + PathBuf::from("src/main/modules/gfx/b.pbs"), + ], + deps: BTreeMap::new(), + }; + + let result = compile_project(step, &HashMap::new()); + assert!(result.is_err()); + // Should be a frontend error (duplicate symbol) +} + +#[test] +fn test_root_module_merging() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().to_path_buf(); + + fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); + fs::write(project_dir.join("src/main/modules/main.pbs"), "pub declare struct Main(id: int)").unwrap(); + fs::write(project_dir.join("src/main/modules/utils.pbs"), "pub declare struct Utils(id: int)").unwrap(); + + let project_id = ProjectId { name: "root-merge".to_string(), version: "0.1.0".to_string() }; + let step = BuildStep { + project_id, + project_dir, + target: BuildTarget::Main, + sources: vec![ + PathBuf::from("src/main/modules/main.pbs"), + PathBuf::from("src/main/modules/utils.pbs"), + ], + deps: BTreeMap::new(), + }; + + let compiled = compile_project(step, &HashMap::new()).unwrap(); + + // 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 == "" && k.symbol_name == "Utils")); +} diff --git a/docs/specs/hardware/topics/chapter-2.md b/docs/specs/hardware/topics/chapter-2.md index daa4d3f6..48ef1ecd 100644 --- a/docs/specs/hardware/topics/chapter-2.md +++ b/docs/specs/hardware/topics/chapter-2.md @@ -215,14 +215,15 @@ State: ### 6.6 Functions -| Instruction | Cycles | Description | -|----------------| ------ |--------------------------------------------| -| `CALL addr` | 5 | Saves PC and creates a new call frame | -| `RET` | 4 | Returns from function, restoring PC | -| `PUSH_SCOPE` | 3 | Creates a scope within the current function | -| `POP_SCOPE` | 3 | Removes current scope and its local variables | +| Instruction | Cycles | Description | +|----------------------| ------ |-----------------------------------------------| +| `CALL ` | 5 | Saves PC and creates a new call frame | +| `RET` | 4 | Returns from function, restoring PC | +| `PUSH_SCOPE` | 3 | Creates a scope within the current function | +| `POP_SCOPE` | 3 | Removes current scope and its local variables | **ABI Rules for Functions:** +* **`func_id`:** A 32-bit index into the **final FunctionTable**, assigned by the compiler linker at build time. * **Mandatory Return Value:** Every function MUST leave exactly one value on the stack before `RET`. If the function logic doesn't return a value, it must push `null`. * **Stack Cleanup:** `RET` automatically clears all local variables (based on `stack_base`) and re-pushes the return value. diff --git a/test-cartridges/canonical/golden/program.pbc b/test-cartridges/canonical/golden/program.pbc index 3a84bc0cfffebf8d23cbab9645ffb94ecc299c98..033b90ca4c445d3c04c8b7ad090cd51a231b9952 100644 GIT binary patch delta 47 wcmdnbv7cju1dBQ=0|P@^QDSZ?Bak5m#KlF)`nidjdHT8eDWy57#a3Wh02kQ}G5`Po delta 25 fcmdnbv7cju1j}Sq7Aamv1_lOJAWkbv%uNLVOP&Q! diff --git a/test-cartridges/test01/cartridge/program.pbc b/test-cartridges/test01/cartridge/program.pbc index 3a84bc0cfffebf8d23cbab9645ffb94ecc299c98..033b90ca4c445d3c04c8b7ad090cd51a231b9952 100644 GIT binary patch delta 47 wcmdnbv7cju1dBQ=0|P@^QDSZ?Bak5m#KlF)`nidjdHT8eDWy57#a3Wh02kQ}G5`Po delta 25 fcmdnbv7cju1j}Sq7Aamv1_lOJAWkbv%uNLVOP&Q!