use std::cell::RefCell; use std::collections::HashSet; use std::fs; use std::rc::Rc; use myc::ast::compiler::module_loader::ModuleLoader; fn make_loader() -> ModuleLoader { ModuleLoader::new( Rc::new(RefCell::new(vec![])), Rc::new(RefCell::new(HashSet::new())), ) } fn make_loader_with_search_path(path: std::path::PathBuf) -> ModuleLoader { ModuleLoader::new( Rc::new(RefCell::new(vec![path])), Rc::new(RefCell::new(HashSet::new())), ) } // --- Basic resolution --- #[test] fn simple_use_directive_resolves_file() { let dir = tempfile::TempDir::new().unwrap(); fs::write(dir.path().join("utils.myc"), "(def x 42)").unwrap(); let loader = make_loader(); let files = loader .collect_dependency_files("#use utils\n(+ x 1)", dir.path()) .unwrap(); assert_eq!(files.len(), 1); assert!(files[0].0.ends_with("utils.myc")); } #[test] fn missing_module_returns_descriptive_error() { let dir = tempfile::TempDir::new().unwrap(); let loader = make_loader(); let result = loader.collect_dependency_files("#use nonexistent\n42", dir.path()); assert!(result.is_err()); let msg = result.unwrap_err(); assert!( msg.contains("nonexistent"), "error message should name the missing module, got: {msg}" ); } #[test] fn no_use_directives_returns_empty() { let dir = tempfile::TempDir::new().unwrap(); let loader = make_loader(); let files = loader .collect_dependency_files("(+ 1 2)", dir.path()) .unwrap(); assert!(files.is_empty()); } // --- Topological ordering --- #[test] fn transitive_deps_returned_in_topological_order() { // Dependency chain: source → b → c // Expected order in result: [c, b] (dependencies before dependents) let dir = tempfile::TempDir::new().unwrap(); fs::write(dir.path().join("c.myc"), "(def c_val 1)").unwrap(); fs::write(dir.path().join("b.myc"), "#use c\n(def b_val 2)").unwrap(); let loader = make_loader(); let files = loader .collect_dependency_files("#use b\n(+ b_val c_val)", dir.path()) .unwrap(); assert_eq!(files.len(), 2); assert!( files[0].0.ends_with("c.myc"), "c must come first (topological order)" ); assert!( files[1].0.ends_with("b.myc"), "b must come second (depends on c)" ); } // --- Deduplication --- #[test] fn shared_dependency_loaded_only_once() { // Both b and the source use c — c must appear exactly once. let dir = tempfile::TempDir::new().unwrap(); fs::write(dir.path().join("c.myc"), "(def c_val 1)").unwrap(); fs::write(dir.path().join("b.myc"), "#use c\n(def b_val 2)").unwrap(); let loader = make_loader(); let source = "#use b\n#use c\n(+ b_val c_val)"; let files = loader .collect_dependency_files(source, dir.path()) .unwrap(); let c_count = files.iter().filter(|(p, _)| p.ends_with("c.myc")).count(); assert_eq!(c_count, 1, "c.myc must appear exactly once"); assert_eq!(files.len(), 2); // c, b } // --- Search path resolution --- #[test] fn module_found_via_search_path() { // base_path has no .myc files; module lives in lib_dir (a search path) let base_dir = tempfile::TempDir::new().unwrap(); let lib_dir = tempfile::TempDir::new().unwrap(); fs::write(lib_dir.path().join("utils.myc"), "(def util_val 99)").unwrap(); let loader = make_loader_with_search_path(lib_dir.path().to_path_buf()); let files = loader .collect_dependency_files("#use utils\n42", base_dir.path()) .unwrap(); assert_eq!(files.len(), 1); assert!(files[0].0.ends_with("utils.myc")); } // --- Directory modules --- #[test] fn directory_module_loads_all_myc_files_alphabetically() { // #use lib should load lib/a.myc, lib/b.myc, lib/c.myc in that order let dir = tempfile::TempDir::new().unwrap(); let lib_dir = dir.path().join("lib"); fs::create_dir(&lib_dir).unwrap(); fs::write(lib_dir.join("c.myc"), "(def c 3)").unwrap(); fs::write(lib_dir.join("a.myc"), "(def a 1)").unwrap(); fs::write(lib_dir.join("b.myc"), "(def b 2)").unwrap(); let loader = make_loader(); let files = loader .collect_dependency_files("#use lib\n42", dir.path()) .unwrap(); assert_eq!(files.len(), 3); assert!(files[0].0.ends_with("a.myc"), "first: a.myc"); assert!(files[1].0.ends_with("b.myc"), "second: b.myc"); assert!(files[2].0.ends_with("c.myc"), "third: c.myc"); }