use prometeu_compiler::building::output::CompiledModule; use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata}; use prometeu_compiler::building::plan::{BuildStep, BuildTarget}; use prometeu_compiler::common::files::FileManager; use prometeu_compiler::deps::resolver::ProjectId; use prometeu_compiler::semantics::export_surface::ExportSurfaceKind; use std::collections::{BTreeMap, HashMap}; use std::path::PathBuf; use tempfile::tempdir; 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, symbols: vec![], }; 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 mut file_manager = FileManager::new(); let result = compile_project(step, &dep_modules, &mut file_manager); 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, symbols: vec![], }; // 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, symbols: vec![], }; 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 mut file_manager = FileManager::new(); let result = compile_project(step, &dep_modules, &mut file_manager); 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 mut file_manager = FileManager::new(); let compiled = compile_project(step, &HashMap::new(), &mut file_manager).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 mut file_manager = FileManager::new(); let compiled = compile_project(step, &HashMap::new(), &mut file_manager).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 mut file_manager = FileManager::new(); let result = compile_project(step, &HashMap::new(), &mut file_manager); 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 mut file_manager = FileManager::new(); let compiled = compile_project(step, &HashMap::new(), &mut file_manager).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")); }