1mod cache;
7mod loading;
8mod resolution;
9#[cfg(test)]
10mod resolution_deep_tests;
11mod resolver;
12
13use crate::project::{DependencySpec, ProjectRoot, find_project_root};
14use shape_ast::ast::{FunctionDef, ImportStmt, Program, Span};
15use shape_ast::error::{Result, ShapeError};
16use shape_ast::parser::parse_program;
17use shape_value::ValueWord;
18use std::collections::HashMap;
19use std::path::{Path, PathBuf};
20use std::sync::Arc;
21
22use cache::ModuleCache;
23pub use resolver::{
24 FilesystemResolver, InMemoryResolver, ModuleCode, ModuleResolver, ResolvedModuleArtifact,
25};
26
27include!(concat!(env!("OUT_DIR"), "/embedded_stdlib_modules.rs"));
28
29#[derive(Debug, Clone)]
31pub struct Module {
32 pub name: String,
33 pub path: String,
34 pub exports: HashMap<String, Export>,
35 pub ast: Program,
36}
37
38impl Module {
39 pub fn get_export(&self, name: &str) -> Option<&Export> {
41 self.exports.get(name)
42 }
43
44 pub fn export_names(&self) -> Vec<&str> {
46 self.exports.keys().map(|s| s.as_str()).collect()
47 }
48}
49
50#[derive(Debug, Clone)]
52pub enum Export {
53 Function(Arc<FunctionDef>),
54 TypeAlias(Arc<shape_ast::ast::TypeAliasDef>),
55 Value(ValueWord),
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ModuleExportKind {
61 Function,
62 TypeAlias,
63 Interface,
64 Enum,
65 Value,
66}
67
68#[derive(Debug, Clone)]
70pub struct ModuleExportSymbol {
71 pub name: String,
73 pub alias: Option<String>,
75 pub kind: ModuleExportKind,
77 pub span: Span,
79}
80
81pub fn collect_exported_symbols(program: &Program) -> Result<Vec<ModuleExportSymbol>> {
83 loading::collect_exported_symbols(program)
84}
85
86pub fn collect_exported_function_names_from_source(
92 module_path: &str,
93 source: &str,
94) -> Result<Vec<String>> {
95 let ast = parse_program(source).map_err(|e| ShapeError::ModuleError {
96 message: format!("Failed to parse module source '{}': {}", module_path, e),
97 module_path: None,
98 })?;
99
100 let module = loading::compile_module(module_path, ast)?;
101 let mut names: Vec<String> = module
102 .exports
103 .into_iter()
104 .filter_map(|(name, export)| match export {
105 Export::Function(_) => Some(name),
106 _ => None,
107 })
108 .collect();
109 names.sort();
110 names.dedup();
111 Ok(names)
112}
113
114pub struct ModuleLoader {
116 stdlib_path: PathBuf,
118 module_paths: Vec<PathBuf>,
120 cache: ModuleCache,
122 dependency_paths: HashMap<String, PathBuf>,
125 extension_resolver: InMemoryResolver,
127 bundle_resolver: InMemoryResolver,
129 embedded_stdlib_resolver: InMemoryResolver,
131 keychain: Option<crate::crypto::Keychain>,
133 blob_store: Option<Arc<dyn crate::blob_store::BlobStore>>,
136}
137
138impl ModuleLoader {
139 pub fn new() -> Self {
141 let mut loader = Self {
142 stdlib_path: Self::default_stdlib_path(),
143 module_paths: Self::default_module_paths(),
144 cache: ModuleCache::new(),
145 dependency_paths: HashMap::new(),
146 extension_resolver: InMemoryResolver::default(),
147 bundle_resolver: InMemoryResolver::default(),
148 embedded_stdlib_resolver: InMemoryResolver::default(),
149 keychain: None,
150 blob_store: None,
151 };
152
153 if let Ok(shape_path) = std::env::var("SHAPE_PATH") {
155 for path in shape_path.split(':') {
156 loader.add_module_path(PathBuf::from(path));
157 }
158 }
159
160 for (module_path, source) in EMBEDDED_STDLIB_MODULES {
161 loader.register_embedded_stdlib_module(
162 (*module_path).to_string(),
163 ModuleCode::Source(Arc::from(*source)),
164 );
165 }
166
167 loader
168 }
169
170 pub fn clone_without_cache(&self) -> Self {
172 Self {
173 stdlib_path: self.stdlib_path.clone(),
174 module_paths: self.module_paths.clone(),
175 cache: ModuleCache::new(),
176 dependency_paths: self.dependency_paths.clone(),
177 extension_resolver: self.extension_resolver.clone(),
178 bundle_resolver: self.bundle_resolver.clone(),
179 embedded_stdlib_resolver: self.embedded_stdlib_resolver.clone(),
180 keychain: None,
181 blob_store: self.blob_store.clone(),
182 }
183 }
184
185 fn default_stdlib_path() -> PathBuf {
187 crate::stdlib_metadata::default_stdlib_path()
188 }
189
190 fn default_module_paths() -> Vec<PathBuf> {
192 let mut paths = vec![];
193
194 paths.push(PathBuf::from("."));
196
197 paths.push(PathBuf::from(".shape"));
199 paths.push(PathBuf::from("shape_modules"));
200 paths.push(PathBuf::from("modules"));
201
202 if let Some(home) = dirs::home_dir() {
204 paths.push(home.join(".shape/modules"));
205 paths.push(home.join(".local/share/shape/modules"));
206 }
207
208 paths.push(PathBuf::from("/usr/local/share/shape/modules"));
210 paths.push(PathBuf::from("/usr/share/shape/modules"));
211
212 paths
213 }
214
215 pub fn add_module_path(&mut self, path: PathBuf) {
217 if !self.module_paths.contains(&path) {
218 self.module_paths.push(path);
219 }
220 }
221
222 pub fn set_project_root(&mut self, root: &std::path::Path, extra_paths: &[PathBuf]) {
228 let root_buf = root.to_path_buf();
229 let mut to_prepend = vec![root_buf];
231 to_prepend.extend(extra_paths.iter().cloned());
232 self.module_paths.retain(|p| !to_prepend.contains(p));
234 to_prepend.extend(self.module_paths.drain(..));
235 self.module_paths = to_prepend;
236 }
237
238 pub fn configure_for_context(&mut self, current_file: &Path, workspace_root: Option<&Path>) {
240 if let Some(project) = resolve_project_root(current_file, workspace_root) {
241 let module_paths = project.resolved_module_paths();
242 self.set_project_root(&project.root_path, &module_paths);
243 self.set_dependency_paths(resolve_path_dependencies(&project));
244 }
245 }
246
247 pub fn configure_for_context_with_source(
252 &mut self,
253 current_file: &Path,
254 workspace_root: Option<&Path>,
255 current_source: Option<&str>,
256 ) {
257 self.configure_for_context(current_file, workspace_root);
258 crate::extension_context::register_declared_extensions_in_loader(
259 self,
260 Some(current_file),
261 workspace_root,
262 current_source,
263 );
264 }
265
266 pub fn set_dependency_paths(&mut self, deps: HashMap<String, PathBuf>) {
273 let mut regular_deps = HashMap::new();
274
275 for (name, path) in deps {
276 if path.extension().and_then(|e| e.to_str()) == Some("shapec") && path.is_file() {
277 match crate::package_bundle::PackageBundle::read_from_file(&path) {
279 Ok(bundle) => {
280 self.load_bundle(&bundle, Some(&name));
281 }
282 Err(e) => {
283 eprintln!(
284 "Warning: failed to load bundle dependency '{}' from '{}': {}",
285 name,
286 path.display(),
287 e
288 );
289 regular_deps.insert(name, path);
291 }
292 }
293 } else {
294 regular_deps.insert(name, path);
295 }
296 }
297
298 self.dependency_paths = regular_deps;
299 }
300
301 pub fn register_extension_module(&mut self, module_path: impl Into<String>, code: ModuleCode) {
303 self.extension_resolver.register(module_path, code);
304 }
305
306 pub fn register_embedded_stdlib_module(
308 &mut self,
309 module_path: impl Into<String>,
310 code: ModuleCode,
311 ) {
312 self.embedded_stdlib_resolver.register(module_path, code);
313 }
314
315 pub fn load_bundle(
321 &mut self,
322 bundle: &crate::package_bundle::PackageBundle,
323 prefix: Option<&str>,
324 ) {
325 for manifest in &bundle.manifests {
327 let path = if let Some(prefix) = prefix {
328 format!("{}::{}", prefix, manifest.name)
329 } else {
330 manifest.name.clone()
331 };
332
333 let mut module_blobs = HashMap::new();
336 for hash in manifest.exports.values() {
337 if let Some(data) = bundle.blob_store.get(hash) {
338 module_blobs.insert(*hash, data.clone());
339 }
340 if let Some(deps) = manifest.dependency_closure.get(hash) {
342 for dep_hash in deps {
343 if let Some(data) = bundle.blob_store.get(dep_hash) {
344 module_blobs.insert(*dep_hash, data.clone());
345 }
346 }
347 }
348 }
349 for hash in manifest.type_schemas.values() {
350 if let Some(data) = bundle.blob_store.get(hash) {
351 module_blobs.insert(*hash, data.clone());
352 }
353 }
354
355 self.register_content_addressed_module(path, manifest, module_blobs);
356 }
357
358 for module in &bundle.modules {
360 let path = if let Some(prefix) = prefix {
361 if module.module_path.is_empty() {
362 prefix.to_string()
363 } else {
364 format!("{}::{}", prefix, module.module_path)
365 }
366 } else {
367 module.module_path.clone()
368 };
369
370 self.bundle_resolver.register(
371 path,
372 ModuleCode::Compiled(Arc::from(module.bytecode_bytes.clone().into_boxed_slice())),
373 );
374 }
375 }
376
377 pub fn register_content_addressed_module(
384 &mut self,
385 module_path: impl Into<String>,
386 manifest: &crate::module_manifest::ModuleManifest,
387 blobs: HashMap<[u8; 32], Vec<u8>>,
388 ) {
389 let manifest_bytes =
390 rmp_serde::to_vec(manifest).expect("ModuleManifest serialization should not fail");
391 self.bundle_resolver.register(
392 module_path,
393 ModuleCode::ContentAddressed {
394 manifest_bytes: Arc::from(manifest_bytes.into_boxed_slice()),
395 blob_cache: Arc::new(blobs),
396 },
397 );
398 }
399
400 pub fn register_bundle_modules(&mut self, modules: Vec<(String, ModuleCode)>) {
402 for (path, code) in modules {
403 self.bundle_resolver.register(path, code);
404 }
405 }
406
407 pub fn set_blob_store(&mut self, store: Arc<dyn crate::blob_store::BlobStore>) {
410 self.blob_store = Some(store);
411 }
412
413 pub fn has_extension_module(&self, module_path: &str) -> bool {
415 self.extension_resolver.has(module_path)
416 }
417
418 pub fn extension_module_paths(&self) -> Vec<String> {
420 self.extension_resolver.module_paths()
421 }
422
423 pub fn embedded_stdlib_module_paths(&self) -> Vec<String> {
425 self.embedded_stdlib_resolver.module_paths()
426 }
427
428 pub fn get_dependency_paths(&self) -> &HashMap<String, PathBuf> {
430 &self.dependency_paths
431 }
432
433 pub fn get_module_paths(&self) -> &[PathBuf] {
435 &self.module_paths
436 }
437
438 pub fn get_stdlib_path(&self) -> &PathBuf {
440 &self.stdlib_path
441 }
442
443 pub fn set_stdlib_path(&mut self, path: PathBuf) {
445 self.stdlib_path = path;
446 }
447
448 pub fn set_keychain(&mut self, keychain: crate::crypto::Keychain) {
454 self.keychain = Some(keychain);
455 }
456
457 pub fn keychain(&self) -> Option<&crate::crypto::Keychain> {
459 self.keychain.as_ref()
460 }
461
462 pub fn clear_module_paths(&mut self) {
464 self.module_paths.clear();
465 }
466
467 pub fn reset_module_paths(&mut self) {
469 self.module_paths = Self::default_module_paths();
470 }
471
472 pub fn load_module(&mut self, module_path: &str) -> Result<Arc<Module>> {
474 self.load_module_with_context(module_path, None)
475 }
476
477 pub fn resolve_module_path(&self, module_path: &str) -> Result<PathBuf> {
479 self.resolve_module_path_with_context(module_path, None)
480 }
481
482 pub fn resolve_module_path_with_context(
484 &self,
485 module_path: &str,
486 context_path: Option<&PathBuf>,
487 ) -> Result<PathBuf> {
488 resolve_module_path_with_settings(
489 module_path,
490 context_path.map(|p| p.as_path()),
491 self.stdlib_path.as_path(),
492 &self.module_paths,
493 &self.dependency_paths,
494 )
495 }
496
497 fn load_module_from_resolved_path(
498 &mut self,
499 cache_key: String,
500 compile_module_path: &str,
501 file_path: PathBuf,
502 ) -> Result<Arc<Module>> {
503 let content = std::fs::read_to_string(&file_path).map_err(|e| ShapeError::ModuleError {
504 message: format!("Failed to read module file: {}: {}", file_path.display(), e),
505 module_path: Some(file_path.clone()),
506 })?;
507
508 let ast = parse_program(&content).map_err(|e| ShapeError::ModuleError {
510 message: format!("Failed to parse module: {}: {}", compile_module_path, e),
511 module_path: None,
512 })?;
513
514 let dependencies = resolution::extract_dependencies(&ast);
516 self.cache
517 .store_dependencies(cache_key.clone(), dependencies.clone());
518
519 let module_dir = file_path.parent().map(|p| p.to_path_buf());
521 for dep in &dependencies {
522 self.load_module_with_context(dep, module_dir.as_ref())?;
523 }
524
525 let module = loading::compile_module(compile_module_path, ast)?;
527 let module = Arc::new(module);
528
529 self.cache.insert(cache_key, module.clone());
531
532 Ok(module)
533 }
534
535 fn load_module_from_source_artifact(
536 &mut self,
537 cache_key: String,
538 compile_module_path: &str,
539 source: &str,
540 origin_path: Option<PathBuf>,
541 context_path: Option<&PathBuf>,
542 ) -> Result<Arc<Module>> {
543 let ast = parse_program(source).map_err(|e| ShapeError::ModuleError {
545 message: format!("Failed to parse module: {}: {}", compile_module_path, e),
546 module_path: origin_path.clone(),
547 })?;
548
549 let dependencies = resolution::extract_dependencies(&ast);
551 self.cache
552 .store_dependencies(cache_key.clone(), dependencies.clone());
553
554 let module_dir = origin_path
556 .as_ref()
557 .and_then(|path| path.parent().map(|p| p.to_path_buf()))
558 .or_else(|| context_path.cloned());
559 for dep in &dependencies {
560 self.load_module_with_context(dep, module_dir.as_ref())?;
561 }
562
563 let module = loading::compile_module(compile_module_path, ast)?;
565 let module = Arc::new(module);
566
567 self.cache.insert(cache_key, module.clone());
569
570 Ok(module)
571 }
572
573 fn resolve_module_artifact_with_context(
574 &self,
575 module_path: &str,
576 context_path: Option<&PathBuf>,
577 ) -> Result<ResolvedModuleArtifact> {
578 let context = context_path.map(|p| p.as_path());
579
580 if let Some(artifact) = self.extension_resolver.resolve(module_path, context)? {
581 return Ok(artifact);
582 }
583
584 if let Some(artifact) = self.bundle_resolver.resolve(module_path, context)? {
586 return Ok(artifact);
587 }
588
589 if let Some(artifact) = self
590 .embedded_stdlib_resolver
591 .resolve(module_path, context)?
592 {
593 return Ok(artifact);
594 }
595
596 let filesystem = FilesystemResolver {
597 stdlib_path: self.stdlib_path.as_path(),
598 module_paths: &self.module_paths,
599 dependency_paths: &self.dependency_paths,
600 };
601
602 filesystem
603 .resolve(module_path, context)?
604 .ok_or_else(|| ShapeError::ModuleError {
605 message: format!("Module not found: {}", module_path),
606 module_path: None,
607 })
608 }
609
610 pub fn load_module_with_context(
612 &mut self,
613 module_path: &str,
614 context_path: Option<&PathBuf>,
615 ) -> Result<Arc<Module>> {
616 if let Some(module) = self.cache.get(module_path) {
618 return Ok(module);
619 }
620
621 self.cache.check_circular_dependency(module_path)?;
623
624 let artifact = self.resolve_module_artifact_with_context(module_path, context_path)?;
626 self.cache.push_loading(module_path.to_string());
628 let result = match artifact.code {
629 ModuleCode::Source(source) => self.load_module_from_source_artifact(
630 module_path.to_string(),
631 module_path,
632 source.as_ref(),
633 artifact.origin_path,
634 context_path,
635 ),
636 ModuleCode::Both { source, .. } => self.load_module_from_source_artifact(
637 module_path.to_string(),
638 module_path,
639 source.as_ref(),
640 artifact.origin_path,
641 context_path,
642 ),
643 ModuleCode::Compiled(_compiled) => {
644 let module = Module {
647 name: module_path
648 .split("::")
649 .last()
650 .unwrap_or(module_path)
651 .to_string(),
652 path: module_path.to_string(),
653 exports: HashMap::new(), ast: shape_ast::ast::Program { items: vec![] },
655 };
656 let module = Arc::new(module);
657 self.cache.insert(module_path.to_string(), module.clone());
658 Ok(module)
659 }
660 ModuleCode::ContentAddressed {
661 manifest_bytes,
662 blob_cache,
663 } => {
664 let manifest: crate::module_manifest::ModuleManifest =
666 rmp_serde::from_slice(&manifest_bytes).map_err(|e| {
667 ShapeError::ModuleError {
668 message: format!(
669 "Failed to deserialize manifest for '{}': {}",
670 module_path, e
671 ),
672 module_path: None,
673 }
674 })?;
675
676 if !manifest.verify_integrity() {
678 return Err(ShapeError::ModuleError {
679 message: format!(
680 "Manifest integrity check failed for '{}': content hash mismatch",
681 module_path
682 ),
683 module_path: None,
684 });
685 }
686
687 if let Some(keychain) = &self.keychain {
689 let sig_data =
690 manifest
691 .signature
692 .as_ref()
693 .map(|sig| crate::crypto::ModuleSignatureData {
694 author_key: sig.author_key,
695 signature: sig.signature.clone(),
696 signed_at: sig.signed_at,
697 });
698 let result = keychain.verify_module(
699 &manifest.name,
700 &manifest.manifest_hash,
701 sig_data.as_ref(),
702 );
703 if let crate::crypto::VerifyResult::Rejected(reason) = result {
704 return Err(ShapeError::ModuleError {
705 message: format!(
706 "Signature verification failed for '{}': {}",
707 module_path, reason
708 ),
709 module_path: None,
710 });
711 }
712 }
713
714 let mut exports = HashMap::new();
719 for export_name in manifest.exports.keys() {
720 let placeholder_fn = shape_ast::ast::FunctionDef {
723 name: export_name.clone(),
724 name_span: shape_ast::ast::Span::default(),
725 params: vec![],
726 body: vec![],
727 return_type: None,
728 is_async: false,
729 is_comptime: false,
730 type_params: None,
731 where_clause: None,
732 annotations: vec![],
733 };
734 exports.insert(
735 export_name.clone(),
736 Export::Function(Arc::new(placeholder_fn)),
737 );
738 }
739
740 for (hash, data) in blob_cache.iter() {
743 let hex_key = format!("__blob__{}", hex::encode(hash));
744 self.bundle_resolver.register(
745 hex_key,
746 ModuleCode::Compiled(Arc::from(data.clone().into_boxed_slice())),
747 );
748 }
749
750 if let Some(ref store) = self.blob_store {
753 for (_name, hash) in manifest.exports.iter() {
754 let all_hashes: Vec<&[u8; 32]> = std::iter::once(hash)
755 .chain(
756 manifest
757 .dependency_closure
758 .get(hash)
759 .into_iter()
760 .flat_map(|v| v.iter()),
761 )
762 .collect();
763 for h in all_hashes {
764 let hex_key = format!("__blob__{}", hex::encode(h));
765 if !self.bundle_resolver.has(&hex_key) {
766 if let Some(data) = store.get(h) {
767 self.bundle_resolver.register(
768 hex_key,
769 ModuleCode::Compiled(Arc::from(data.into_boxed_slice())),
770 );
771 }
772 }
773 }
774 }
775 }
776
777 let module = Module {
778 name: manifest.name.clone(),
779 path: module_path.to_string(),
780 exports,
781 ast: shape_ast::ast::Program { items: vec![] },
782 };
783 let module = Arc::new(module);
784 self.cache.insert(module_path.to_string(), module.clone());
785 Ok(module)
786 }
787 };
788 self.cache.pop_loading();
789 result
790 }
791
792 pub fn load_module_from_file(&mut self, file_path: &Path) -> Result<Arc<Module>> {
797 let canonical = file_path
798 .canonicalize()
799 .unwrap_or_else(|_| file_path.to_path_buf());
800 let cache_key = canonical.to_string_lossy().to_string();
801
802 if let Some(module) = self.cache.get(&cache_key) {
804 return Ok(module);
805 }
806
807 self.cache.check_circular_dependency(&cache_key)?;
809
810 self.cache.push_loading(cache_key.clone());
811 let result = self.load_module_from_resolved_path(cache_key.clone(), &cache_key, canonical);
812 self.cache.pop_loading();
813
814 result
815 }
816
817 pub fn list_core_stdlib_module_imports(&self) -> Result<Vec<String>> {
819 let mut embedded: Vec<String> = self
820 .embedded_stdlib_resolver
821 .module_paths()
822 .into_iter()
823 .filter(|name| name.starts_with("std::core::"))
824 .collect();
825 if !embedded.is_empty() {
826 embedded.sort();
827 embedded.dedup();
828 return Ok(embedded);
829 }
830
831 if !self.stdlib_path.exists() || !self.stdlib_path.is_dir() {
832 return Err(ShapeError::ModuleError {
833 message: format!(
834 "Could not find stdlib directory at {}",
835 self.stdlib_path.display()
836 ),
837 module_path: Some(self.stdlib_path.clone()),
838 });
839 }
840
841 resolution::list_core_stdlib_module_imports(self.stdlib_path.as_path())
842 }
843
844 pub fn list_stdlib_module_imports(&self) -> Result<Vec<String>> {
846 let mut embedded: Vec<String> = self
847 .embedded_stdlib_resolver
848 .module_paths()
849 .into_iter()
850 .filter(|name| name.starts_with("std::"))
851 .collect();
852 if !embedded.is_empty() {
853 embedded.sort();
854 embedded.dedup();
855 return Ok(embedded);
856 }
857
858 if !self.stdlib_path.exists() || !self.stdlib_path.is_dir() {
859 return Err(ShapeError::ModuleError {
860 message: format!(
861 "Could not find stdlib directory at {}",
862 self.stdlib_path.display()
863 ),
864 module_path: Some(self.stdlib_path.clone()),
865 });
866 }
867
868 resolution::list_stdlib_module_imports(self.stdlib_path.as_path())
869 }
870
871 pub fn list_importable_modules_with_context(
880 &self,
881 current_file: &Path,
882 workspace_root: Option<&Path>,
883 ) -> Vec<String> {
884 let mut modules = self.list_stdlib_module_imports().unwrap_or_default();
885
886 modules.extend(self.embedded_stdlib_resolver.module_paths());
887 modules.extend(self.extension_resolver.module_paths());
888
889 if let Some(project) = resolve_project_root(current_file, workspace_root) {
890 modules.extend(
891 resolution::list_modules_from_root(&project.root_path, None).unwrap_or_default(),
892 );
893
894 for module_path in project.resolved_module_paths() {
895 modules.extend(
896 resolution::list_modules_from_root(&module_path, None).unwrap_or_default(),
897 );
898 }
899
900 for (dep_name, dep_root) in resolve_path_dependencies(&project) {
901 modules.extend(
902 resolution::list_modules_from_root(&dep_root, Some(dep_name.as_str()))
903 .unwrap_or_default(),
904 );
905 }
906 } else if let Some(context_dir) = current_file.parent() {
907 modules
908 .extend(resolution::list_modules_from_root(context_dir, None).unwrap_or_default());
909 }
910
911 modules.sort();
912 modules.dedup();
913 modules.retain(|m| !m.is_empty());
914 modules
915 }
916
917 pub fn load_core_stdlib_modules(&mut self) -> Result<Vec<Arc<Module>>> {
919 let mut modules = Vec::new();
920 for import_path in self.list_core_stdlib_module_imports()? {
921 modules.push(self.load_module(&import_path)?);
922 }
923 Ok(modules)
924 }
925
926 pub fn load_stdlib(&mut self) -> Result<()> {
928 let _ = self.load_core_stdlib_modules()?;
929 Ok(())
930 }
931
932 pub fn loaded_modules(&self) -> Vec<&str> {
934 self.cache.loaded_modules()
935 }
936
937 pub fn get_export(&self, module_path: &str, export_name: &str) -> Option<&Export> {
939 self.cache.get_export(module_path, export_name)
940 }
941
942 pub fn get_module(&self, module_path: &str) -> Option<&Arc<Module>> {
944 self.cache.get_module(module_path)
945 }
946
947 pub fn resolve_import(&mut self, import_stmt: &ImportStmt) -> Result<HashMap<String, Export>> {
949 let module = self.load_module(&import_stmt.from)?;
950 cache::resolve_import(import_stmt, &module)
951 }
952
953 pub fn clear_cache(&mut self) {
955 self.cache.clear();
956 }
957
958 pub fn get_dependencies(&self, module_path: &str) -> Option<&Vec<String>> {
960 self.cache.get_dependencies(module_path)
961 }
962
963 pub fn get_all_dependencies(&self, module_path: &str) -> Vec<String> {
965 self.cache.get_all_dependencies(module_path)
966 }
967}
968
969impl Default for ModuleLoader {
970 fn default() -> Self {
971 Self::new()
972 }
973}
974
975pub fn resolve_module_path_with_settings(
977 module_path: &str,
978 context_path: Option<&Path>,
979 stdlib_path: &Path,
980 module_paths: &[PathBuf],
981 dependency_paths: &HashMap<String, PathBuf>,
982) -> Result<PathBuf> {
983 resolution::resolve_module_path_with_context(
984 module_path,
985 context_path,
986 stdlib_path,
987 module_paths,
988 dependency_paths,
989 )
990}
991
992fn resolve_project_root(current_file: &Path, workspace_root: Option<&Path>) -> Option<ProjectRoot> {
993 workspace_root
994 .and_then(find_project_root)
995 .or_else(|| current_file.parent().and_then(find_project_root))
996}
997
998fn resolve_path_dependencies(project: &ProjectRoot) -> HashMap<String, PathBuf> {
999 let mut resolved = HashMap::new();
1000
1001 for (name, spec) in &project.config.dependencies {
1002 if let DependencySpec::Detailed(detailed) = spec {
1003 if let Some(path) = &detailed.path {
1004 let dep_path = project.root_path.join(path);
1005 let canonical = dep_path.canonicalize().unwrap_or(dep_path);
1006 resolved.insert(name.clone(), canonical);
1007 }
1008 }
1009 }
1010
1011 resolved
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016 use super::*;
1017 use std::sync::Arc;
1018
1019 #[test]
1020 fn test_compile_module_exports_function() {
1021 let source = r#"
1022pub fn greet(name) {
1023 return "Hello, " + name
1024}
1025"#;
1026 let ast = parse_program(source).unwrap();
1027 let module = loading::compile_module("test_module", ast).unwrap();
1028
1029 assert!(
1030 module.exports.contains_key("greet"),
1031 "Expected 'greet' export, got: {:?}",
1032 module.exports.keys().collect::<Vec<_>>()
1033 );
1034
1035 match module.exports.get("greet") {
1036 Some(Export::Function(func)) => {
1037 assert_eq!(func.name, "greet");
1038 }
1039 other => panic!("Expected Function export, got: {:?}", other),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_collect_exported_function_names_from_source() {
1045 let source = r#"
1046fn hidden() { 0 }
1047pub fn connect(uri) { uri }
1048pub fn ping() { 1 }
1049"#;
1050 let names = collect_exported_function_names_from_source("duckdb", source)
1051 .expect("should collect exported functions");
1052 assert_eq!(names, vec!["connect".to_string(), "ping".to_string()]);
1053 }
1054
1055 #[test]
1056 fn test_load_module_from_temp_file() {
1057 use std::io::Write;
1058
1059 let temp_dir = std::env::temp_dir();
1061 let module_path = temp_dir.join("test_load_module.shape");
1062 let mut file = std::fs::File::create(&module_path).unwrap();
1063 writeln!(
1064 file,
1065 r#"
1066pub fn add(a, b) {{
1067 return a + b
1068}}
1069"#
1070 )
1071 .unwrap();
1072
1073 let mut loader = ModuleLoader::new();
1075 loader.add_module_path(temp_dir.clone());
1076
1077 let result = loader.load_module_with_context("test_load_module", Some(&temp_dir));
1079
1080 std::fs::remove_file(&module_path).ok();
1082
1083 let module = result.expect("Module should load");
1085 assert!(
1086 module.exports.contains_key("add"),
1087 "Expected 'add' export, got: {:?}",
1088 module.exports.keys().collect::<Vec<_>>()
1089 );
1090 }
1091
1092 #[test]
1093 fn test_load_module_from_file_path() {
1094 use std::io::Write;
1095
1096 let temp_dir = tempfile::tempdir().expect("temp dir");
1097 let module_path = temp_dir.path().join("helpers.shape");
1098 let mut file = std::fs::File::create(&module_path).expect("create module");
1099 writeln!(
1100 file,
1101 r#"
1102pub fn helper(x) {{
1103 x
1104}}
1105"#
1106 )
1107 .expect("write module");
1108
1109 let mut loader = ModuleLoader::new();
1110 let module = loader
1111 .load_module_from_file(&module_path)
1112 .expect("module should load from file path");
1113 assert!(
1114 module.exports.contains_key("helper"),
1115 "Expected 'helper' export, got: {:?}",
1116 module.exports.keys().collect::<Vec<_>>()
1117 );
1118 }
1119
1120 #[test]
1121 fn test_collect_exported_symbols_detects_pub_function_and_enum() {
1122 let source = r#"
1123pub fn helper() { 1 }
1124pub enum Side { Buy, Sell }
1125"#;
1126 let ast = parse_program(source).unwrap();
1127 let exports = collect_exported_symbols(&ast).unwrap();
1128
1129 let helper = exports
1130 .iter()
1131 .find(|e| e.name == "helper")
1132 .expect("expected helper export");
1133 assert_eq!(helper.name, "helper");
1134 assert!(helper.alias.is_none());
1135 assert_eq!(helper.kind, ModuleExportKind::Function);
1136
1137 let side = exports
1138 .iter()
1139 .find(|e| e.name == "Side")
1140 .expect("expected Side export");
1141 assert_eq!(side.kind, ModuleExportKind::Enum);
1142 }
1143
1144 #[test]
1145 fn test_list_core_stdlib_module_imports_contains_core_modules() {
1146 let loader = ModuleLoader::new();
1147 let modules = loader
1148 .list_core_stdlib_module_imports()
1149 .expect("should list std.core modules");
1150
1151 assert!(
1152 !modules.is_empty(),
1153 "expected non-empty std.core module list"
1154 );
1155 assert!(
1156 modules.iter().all(|m| m.starts_with("std::core::")),
1157 "expected std::core::* import paths, got: {:?}",
1158 modules
1159 );
1160 assert!(
1161 modules.iter().any(|m| m == "std::core::math"),
1162 "expected std::core::math in core module list"
1163 );
1164 }
1165
1166 #[test]
1167 fn test_list_stdlib_module_imports_includes_non_core_namespaces() {
1168 let loader = ModuleLoader::new();
1169 let modules = loader
1170 .list_stdlib_module_imports()
1171 .expect("should list stdlib modules");
1172
1173 assert!(
1174 modules.iter().any(|m| m.starts_with("std::finance::")),
1175 "expected finance stdlib modules in list, got: {:?}",
1176 modules
1177 );
1178 }
1179
1180 #[test]
1181 fn test_embedded_stdlib_loads_without_filesystem_path() {
1182 let mut loader = ModuleLoader::new();
1183 loader.set_stdlib_path(std::env::temp_dir().join("shape_missing_stdlib_dir"));
1184
1185 let module = loader
1186 .load_module("std::core::snapshot")
1187 .expect("embedded stdlib module should load without filesystem stdlib");
1188 assert!(
1189 module.exports.contains_key("snapshot"),
1190 "expected snapshot export from std::core::snapshot"
1191 );
1192 }
1193
1194 #[test]
1195 fn test_list_importable_modules_with_context_includes_project_and_deps() {
1196 let tmp = tempfile::tempdir().unwrap();
1197 let root = tmp.path();
1198
1199 std::fs::write(
1200 root.join("shape.toml"),
1201 r#"
1202[modules]
1203paths = ["lib"]
1204
1205[dependencies]
1206mydep = { path = "deps/mydep" }
1207"#,
1208 )
1209 .unwrap();
1210
1211 std::fs::create_dir_all(root.join("src")).unwrap();
1212 std::fs::create_dir_all(root.join("lib")).unwrap();
1213 std::fs::create_dir_all(root.join("deps/mydep")).unwrap();
1214
1215 std::fs::write(root.join("src/main.shape"), "let x = 1").unwrap();
1216 std::fs::write(root.join("lib/tools.shape"), "pub fn tool() { 1 }").unwrap();
1217 std::fs::write(root.join("deps/mydep/index.shape"), "pub fn root() { 1 }").unwrap();
1218 std::fs::write(root.join("deps/mydep/util.shape"), "pub fn util() { 1 }").unwrap();
1219
1220 let loader = ModuleLoader::new();
1221 let modules =
1222 loader.list_importable_modules_with_context(&root.join("src/main.shape"), None);
1223
1224 assert!(
1225 modules.iter().any(|m| m == "tools"),
1226 "expected module path from [modules].paths, got: {:?}",
1227 modules
1228 );
1229 assert!(
1230 modules.iter().any(|m| m == "mydep"),
1231 "expected dependency index module path, got: {:?}",
1232 modules
1233 );
1234 assert!(
1235 modules.iter().any(|m| m == "mydep::util"),
1236 "expected dependency submodule path, got: {:?}",
1237 modules
1238 );
1239 }
1240
1241 #[test]
1242 fn test_load_in_memory_extension_module() {
1243 let mut loader = ModuleLoader::new();
1244 loader.register_extension_module(
1245 "duckdb",
1246 ModuleCode::Source(Arc::from(
1247 r#"
1248pub fn connect(uri) { uri }
1249"#,
1250 )),
1251 );
1252
1253 let module = loader
1254 .load_module("duckdb")
1255 .expect("in-memory extension module should load");
1256 assert!(
1257 module.exports.contains_key("connect"),
1258 "expected connect export, got {:?}",
1259 module.exports.keys().collect::<Vec<_>>()
1260 );
1261 }
1262
1263 #[test]
1264 fn test_load_in_memory_extension_module_with_dependency() {
1265 let mut loader = ModuleLoader::new();
1266 loader.register_extension_module(
1267 "b",
1268 ModuleCode::Source(Arc::from(
1269 r#"
1270pub fn answer() { 42 }
1271"#,
1272 )),
1273 );
1274 loader.register_extension_module(
1275 "a",
1276 ModuleCode::Source(Arc::from(
1277 r#"
1278from b use { answer }
1279pub fn use_answer() { answer() }
1280"#,
1281 )),
1282 );
1283
1284 let module = loader
1285 .load_module("a")
1286 .expect("in-memory module with dependency should load");
1287 assert!(
1288 module.exports.contains_key("use_answer"),
1289 "expected use_answer export"
1290 );
1291 assert!(
1292 loader.get_module("b").is_some(),
1293 "dependency module b should load"
1294 );
1295 }
1296
1297 #[test]
1298 fn test_load_bundle_modules() {
1299 use crate::package_bundle::{BundleMetadata, BundledModule, PackageBundle};
1300
1301 let bundle = PackageBundle {
1302 metadata: BundleMetadata {
1303 name: "test".to_string(),
1304 version: "0.1.0".to_string(),
1305 compiler_version: "0.5.0".to_string(),
1306 source_hash: "abc".to_string(),
1307 bundle_kind: "portable-bytecode".to_string(),
1308 build_host: "x86_64-linux".to_string(),
1309 native_portable: true,
1310 entry_module: None,
1311 built_at: 0,
1312 },
1313 modules: vec![BundledModule {
1314 module_path: "helpers".to_string(),
1315 bytecode_bytes: vec![1, 2, 3],
1316 export_names: vec!["helper".to_string()],
1317 source_hash: "def".to_string(),
1318 }],
1319 dependencies: std::collections::HashMap::new(),
1320 blob_store: std::collections::HashMap::new(),
1321 manifests: vec![],
1322 native_dependency_scopes: vec![],
1323 };
1324
1325 let mut loader = ModuleLoader::new();
1326 loader.load_bundle(&bundle, Some("mylib"));
1327
1328 let artifact = loader.resolve_module_artifact_with_context("mylib::helpers", None);
1330 assert!(artifact.is_ok(), "bundle module should be resolvable");
1331 }
1332}