1mod cache;
7mod loading;
8mod resolution;
9#[cfg(all(test, feature = "deep-tests"))]
10mod resolution_deep_tests;
11mod resolver;
12
13use crate::project::{DependencySpec, ProjectRoot, find_project_root, normalize_package_identity};
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 current_project_root: Option<PathBuf>,
122 cache: ModuleCache,
124 dependency_paths: HashMap<String, PathBuf>,
127 extension_resolver: InMemoryResolver,
129 bundle_resolver: InMemoryResolver,
131 embedded_stdlib_resolver: InMemoryResolver,
133 keychain: Option<crate::crypto::Keychain>,
135 blob_store: Option<Arc<dyn crate::blob_store::BlobStore>>,
138}
139
140impl ModuleLoader {
141 pub fn new() -> Self {
143 let mut loader = Self {
144 stdlib_path: Self::default_stdlib_path(),
145 module_paths: Self::default_module_paths(),
146 current_project_root: None,
147 cache: ModuleCache::new(),
148 dependency_paths: HashMap::new(),
149 extension_resolver: InMemoryResolver::default(),
150 bundle_resolver: InMemoryResolver::default(),
151 embedded_stdlib_resolver: InMemoryResolver::default(),
152 keychain: None,
153 blob_store: None,
154 };
155
156 if let Ok(shape_path) = std::env::var("SHAPE_PATH") {
158 for path in shape_path.split(':') {
159 loader.add_module_path(PathBuf::from(path));
160 }
161 }
162
163 for (module_path, source) in EMBEDDED_STDLIB_MODULES {
164 loader.register_embedded_stdlib_module(
165 (*module_path).to_string(),
166 ModuleCode::Source(Arc::from(*source)),
167 );
168 }
169
170 loader
171 }
172
173 pub fn clone_without_cache(&self) -> Self {
175 Self {
176 stdlib_path: self.stdlib_path.clone(),
177 module_paths: self.module_paths.clone(),
178 current_project_root: self.current_project_root.clone(),
179 cache: ModuleCache::new(),
180 dependency_paths: self.dependency_paths.clone(),
181 extension_resolver: self.extension_resolver.clone(),
182 bundle_resolver: self.bundle_resolver.clone(),
183 embedded_stdlib_resolver: self.embedded_stdlib_resolver.clone(),
184 keychain: None,
185 blob_store: self.blob_store.clone(),
186 }
187 }
188
189 fn default_stdlib_path() -> PathBuf {
191 crate::stdlib_metadata::default_stdlib_path()
192 }
193
194 fn default_module_paths() -> Vec<PathBuf> {
196 let mut paths = vec![];
197
198 paths.push(PathBuf::from("."));
200
201 paths.push(PathBuf::from(".shape"));
203 paths.push(PathBuf::from("shape_modules"));
204 paths.push(PathBuf::from("modules"));
205
206 if let Some(home) = dirs::home_dir() {
208 paths.push(home.join(".shape/modules"));
209 paths.push(home.join(".local/share/shape/modules"));
210 }
211
212 paths.push(PathBuf::from("/usr/local/share/shape/modules"));
214 paths.push(PathBuf::from("/usr/share/shape/modules"));
215
216 paths
217 }
218
219 pub fn add_module_path(&mut self, path: PathBuf) {
221 if !self.module_paths.contains(&path) {
222 self.module_paths.push(path);
223 }
224 }
225
226 pub fn set_project_root(&mut self, root: &std::path::Path, extra_paths: &[PathBuf]) {
232 let root_buf = root.to_path_buf();
233 self.current_project_root = Some(root_buf.clone());
234 let mut to_prepend = vec![root_buf];
236 to_prepend.extend(extra_paths.iter().cloned());
237 self.module_paths.retain(|p| !to_prepend.contains(p));
239 to_prepend.extend(self.module_paths.drain(..));
240 self.module_paths = to_prepend;
241 }
242
243 pub fn configure_for_context(&mut self, current_file: &Path, workspace_root: Option<&Path>) {
245 if let Some(project) = resolve_project_root(current_file, workspace_root) {
246 let module_paths = project.resolved_module_paths();
247 self.set_project_root(&project.root_path, &module_paths);
248 self.set_dependency_paths(resolve_path_dependencies(&project));
249 }
250 }
251
252 pub fn configure_for_context_with_source(
257 &mut self,
258 current_file: &Path,
259 workspace_root: Option<&Path>,
260 current_source: Option<&str>,
261 ) {
262 self.configure_for_context(current_file, workspace_root);
263 crate::extension_context::register_declared_extensions_in_loader(
264 self,
265 Some(current_file),
266 workspace_root,
267 current_source,
268 );
269 }
270
271 pub fn set_dependency_paths(&mut self, deps: HashMap<String, PathBuf>) {
278 let mut regular_deps = HashMap::new();
279
280 for (name, path) in deps {
281 if path.extension().and_then(|e| e.to_str()) == Some("shapec") && path.is_file() {
282 match crate::package_bundle::PackageBundle::read_from_file(&path) {
284 Ok(bundle) => {
285 self.load_bundle(&bundle, Some(&name));
286 }
287 Err(e) => {
288 eprintln!(
289 "Warning: failed to load bundle dependency '{}' from '{}': {}",
290 name,
291 path.display(),
292 e
293 );
294 regular_deps.insert(name, path);
296 }
297 }
298 } else {
299 regular_deps.insert(name, path);
300 }
301 }
302
303 self.dependency_paths = regular_deps;
304 }
305
306 pub fn register_extension_module(&mut self, module_path: impl Into<String>, code: ModuleCode) {
308 self.extension_resolver.register(module_path, code);
309 }
310
311 pub fn register_embedded_stdlib_module(
313 &mut self,
314 module_path: impl Into<String>,
315 code: ModuleCode,
316 ) {
317 self.embedded_stdlib_resolver.register(module_path, code);
318 }
319
320 pub fn load_bundle(
326 &mut self,
327 bundle: &crate::package_bundle::PackageBundle,
328 prefix: Option<&str>,
329 ) {
330 for manifest in &bundle.manifests {
332 let path = if let Some(prefix) = prefix {
333 format!("{}::{}", prefix, manifest.name)
334 } else {
335 manifest.name.clone()
336 };
337
338 let mut module_blobs = HashMap::new();
341 for hash in manifest.exports.values() {
342 if let Some(data) = bundle.blob_store.get(hash) {
343 module_blobs.insert(*hash, data.clone());
344 }
345 if let Some(deps) = manifest.dependency_closure.get(hash) {
347 for dep_hash in deps {
348 if let Some(data) = bundle.blob_store.get(dep_hash) {
349 module_blobs.insert(*dep_hash, data.clone());
350 }
351 }
352 }
353 }
354 for hash in manifest.type_schemas.values() {
355 if let Some(data) = bundle.blob_store.get(hash) {
356 module_blobs.insert(*hash, data.clone());
357 }
358 }
359
360 self.register_content_addressed_module(path, manifest, module_blobs);
361 }
362
363 for module in &bundle.modules {
365 let path = if let Some(prefix) = prefix {
366 if module.module_path.is_empty() {
367 prefix.to_string()
368 } else {
369 format!("{}::{}", prefix, module.module_path)
370 }
371 } else {
372 module.module_path.clone()
373 };
374
375 self.bundle_resolver.register(
376 path,
377 ModuleCode::Compiled(Arc::from(module.bytecode_bytes.clone().into_boxed_slice())),
378 );
379 }
380 }
381
382 pub fn register_content_addressed_module(
389 &mut self,
390 module_path: impl Into<String>,
391 manifest: &crate::module_manifest::ModuleManifest,
392 blobs: HashMap<[u8; 32], Vec<u8>>,
393 ) {
394 let manifest_bytes =
395 rmp_serde::to_vec(manifest).expect("ModuleManifest serialization should not fail");
396 self.bundle_resolver.register(
397 module_path,
398 ModuleCode::ContentAddressed {
399 manifest_bytes: Arc::from(manifest_bytes.into_boxed_slice()),
400 blob_cache: Arc::new(blobs),
401 },
402 );
403 }
404
405 pub fn register_bundle_modules(&mut self, modules: Vec<(String, ModuleCode)>) {
407 for (path, code) in modules {
408 self.bundle_resolver.register(path, code);
409 }
410 }
411
412 pub fn set_blob_store(&mut self, store: Arc<dyn crate::blob_store::BlobStore>) {
415 self.blob_store = Some(store);
416 }
417
418 pub fn has_extension_module(&self, module_path: &str) -> bool {
420 self.extension_resolver.has(module_path)
421 }
422
423 pub fn extension_module_paths(&self) -> Vec<String> {
425 self.extension_resolver.module_paths()
426 }
427
428 pub fn embedded_stdlib_module_paths(&self) -> Vec<String> {
430 self.embedded_stdlib_resolver.module_paths()
431 }
432
433 pub fn get_dependency_paths(&self) -> &HashMap<String, PathBuf> {
435 &self.dependency_paths
436 }
437
438 pub fn get_module_paths(&self) -> &[PathBuf] {
440 &self.module_paths
441 }
442
443 pub fn get_stdlib_path(&self) -> &PathBuf {
445 &self.stdlib_path
446 }
447
448 pub fn set_stdlib_path(&mut self, path: PathBuf) {
450 self.stdlib_path = path;
451 }
452
453 pub fn set_keychain(&mut self, keychain: crate::crypto::Keychain) {
459 self.keychain = Some(keychain);
460 }
461
462 pub fn keychain(&self) -> Option<&crate::crypto::Keychain> {
464 self.keychain.as_ref()
465 }
466
467 pub fn clear_module_paths(&mut self) {
469 self.module_paths.clear();
470 }
471
472 pub fn reset_module_paths(&mut self) {
474 self.module_paths = Self::default_module_paths();
475 }
476
477 pub fn load_module(&mut self, module_path: &str) -> Result<Arc<Module>> {
479 self.load_module_with_context(module_path, None)
480 }
481
482 pub fn resolve_module_path(&self, module_path: &str) -> Result<PathBuf> {
484 self.resolve_module_path_with_context(module_path, None)
485 }
486
487 pub fn resolve_module_path_with_context(
489 &self,
490 module_path: &str,
491 context_path: Option<&PathBuf>,
492 ) -> Result<PathBuf> {
493 resolve_module_path_with_settings(
494 module_path,
495 context_path.map(|p| p.as_path()),
496 self.stdlib_path.as_path(),
497 &self.module_paths,
498 &self.dependency_paths,
499 )
500 }
501
502 fn load_module_from_resolved_path(
503 &mut self,
504 cache_key: String,
505 compile_module_path: &str,
506 file_path: PathBuf,
507 ) -> Result<Arc<Module>> {
508 let content = std::fs::read_to_string(&file_path).map_err(|e| ShapeError::ModuleError {
509 message: format!("Failed to read module file: {}: {}", file_path.display(), e),
510 module_path: Some(file_path.clone()),
511 })?;
512
513 let ast = parse_program(&content).map_err(|e| ShapeError::ModuleError {
515 message: format!("Failed to parse module: {}: {}", compile_module_path, e),
516 module_path: None,
517 })?;
518 let mut ast = ast;
519 annotate_program_declaring_module_path(&mut ast, compile_module_path);
520 annotate_program_native_abi_package_key(
521 &mut ast,
522 self.package_key_for_origin_path(Some(&file_path))
523 .as_deref(),
524 );
525
526 let dependencies = resolution::extract_dependencies(&ast);
528 self.cache
529 .store_dependencies(cache_key.clone(), dependencies.clone());
530
531 let module_dir = file_path.parent().map(|p| p.to_path_buf());
533 for dep in &dependencies {
534 self.load_module_with_context(dep, module_dir.as_ref())?;
535 }
536
537 let module = loading::compile_module(compile_module_path, ast)?;
539 let module = Arc::new(module);
540
541 self.cache.insert(cache_key, module.clone());
543
544 Ok(module)
545 }
546
547 fn load_module_from_source_artifact(
548 &mut self,
549 cache_key: String,
550 compile_module_path: &str,
551 source: &str,
552 origin_path: Option<PathBuf>,
553 context_path: Option<&PathBuf>,
554 ) -> Result<Arc<Module>> {
555 let ast = parse_program(source).map_err(|e| ShapeError::ModuleError {
557 message: format!("Failed to parse module: {}: {}", compile_module_path, e),
558 module_path: origin_path.clone(),
559 })?;
560 let mut ast = ast;
561 annotate_program_declaring_module_path(&mut ast, compile_module_path);
562 annotate_program_native_abi_package_key(
563 &mut ast,
564 self.package_key_for_origin_path(origin_path.as_deref())
565 .as_deref(),
566 );
567
568 let dependencies = resolution::extract_dependencies(&ast);
570 self.cache
571 .store_dependencies(cache_key.clone(), dependencies.clone());
572
573 let module_dir = origin_path
575 .as_ref()
576 .and_then(|path| path.parent().map(|p| p.to_path_buf()))
577 .or_else(|| context_path.cloned());
578 for dep in &dependencies {
579 self.load_module_with_context(dep, module_dir.as_ref())?;
580 }
581
582 let module = loading::compile_module(compile_module_path, ast)?;
584 let module = Arc::new(module);
585
586 self.cache.insert(cache_key, module.clone());
588
589 Ok(module)
590 }
591
592 fn resolve_module_artifact_with_context(
593 &self,
594 module_path: &str,
595 context_path: Option<&PathBuf>,
596 ) -> Result<ResolvedModuleArtifact> {
597 let context = context_path.map(|p| p.as_path());
598
599 if let Some(artifact) = self.extension_resolver.resolve(module_path, context)? {
600 return Ok(artifact);
601 }
602
603 if let Some(artifact) = self.bundle_resolver.resolve(module_path, context)? {
605 return Ok(artifact);
606 }
607
608 if let Some(artifact) = self
609 .embedded_stdlib_resolver
610 .resolve(module_path, context)?
611 {
612 return Ok(artifact);
613 }
614
615 let filesystem = FilesystemResolver {
616 stdlib_path: self.stdlib_path.as_path(),
617 module_paths: &self.module_paths,
618 dependency_paths: &self.dependency_paths,
619 };
620
621 filesystem
622 .resolve(module_path, context)?
623 .ok_or_else(|| ShapeError::ModuleError {
624 message: format!("Module not found: {}", module_path),
625 module_path: None,
626 })
627 }
628
629 pub fn load_module_with_context(
631 &mut self,
632 module_path: &str,
633 context_path: Option<&PathBuf>,
634 ) -> Result<Arc<Module>> {
635 if let Some(module) = self.cache.get(module_path) {
637 return Ok(module);
638 }
639
640 self.cache.check_circular_dependency(module_path)?;
642
643 let artifact = self.resolve_module_artifact_with_context(module_path, context_path)?;
645 self.cache.push_loading(module_path.to_string());
647 let result = match artifact.code {
648 ModuleCode::Source(source) => self.load_module_from_source_artifact(
649 module_path.to_string(),
650 module_path,
651 source.as_ref(),
652 artifact.origin_path,
653 context_path,
654 ),
655 ModuleCode::Both { source, .. } => self.load_module_from_source_artifact(
656 module_path.to_string(),
657 module_path,
658 source.as_ref(),
659 artifact.origin_path,
660 context_path,
661 ),
662 ModuleCode::Compiled(_compiled) => {
663 let module = Module {
666 name: module_path
667 .split("::")
668 .last()
669 .unwrap_or(module_path)
670 .to_string(),
671 path: module_path.to_string(),
672 exports: HashMap::new(), ast: shape_ast::ast::Program {
674 items: vec![],
675 docs: shape_ast::ast::ProgramDocs::default(),
676 },
677 };
678 let module = Arc::new(module);
679 self.cache.insert(module_path.to_string(), module.clone());
680 Ok(module)
681 }
682 ModuleCode::ContentAddressed {
683 manifest_bytes,
684 blob_cache,
685 } => {
686 let manifest: crate::module_manifest::ModuleManifest =
688 rmp_serde::from_slice(&manifest_bytes).map_err(|e| {
689 ShapeError::ModuleError {
690 message: format!(
691 "Failed to deserialize manifest for '{}': {}",
692 module_path, e
693 ),
694 module_path: None,
695 }
696 })?;
697
698 if !manifest.verify_integrity() {
700 return Err(ShapeError::ModuleError {
701 message: format!(
702 "Manifest integrity check failed for '{}': content hash mismatch",
703 module_path
704 ),
705 module_path: None,
706 });
707 }
708
709 if let Some(keychain) = &self.keychain {
711 let sig_data =
712 manifest
713 .signature
714 .as_ref()
715 .map(|sig| crate::crypto::ModuleSignatureData {
716 author_key: sig.author_key,
717 signature: sig.signature.clone(),
718 signed_at: sig.signed_at,
719 });
720 let result = keychain.verify_module(
721 &manifest.name,
722 &manifest.manifest_hash,
723 sig_data.as_ref(),
724 );
725 if let crate::crypto::VerifyResult::Rejected(reason) = result {
726 return Err(ShapeError::ModuleError {
727 message: format!(
728 "Signature verification failed for '{}': {}",
729 module_path, reason
730 ),
731 module_path: None,
732 });
733 }
734 }
735
736 let mut exports = HashMap::new();
741 for export_name in manifest.exports.keys() {
742 let placeholder_fn = shape_ast::ast::FunctionDef {
745 name: export_name.clone(),
746 name_span: shape_ast::ast::Span::default(),
747 declaring_module_path: None,
748 doc_comment: None,
749 params: vec![],
750 body: vec![],
751 return_type: None,
752 is_async: false,
753 is_comptime: false,
754 type_params: None,
755 where_clause: None,
756 annotations: vec![],
757 };
758 exports.insert(
759 export_name.clone(),
760 Export::Function(Arc::new(placeholder_fn)),
761 );
762 }
763
764 for (hash, data) in blob_cache.iter() {
767 let hex_key = format!("__blob__{}", hex::encode(hash));
768 self.bundle_resolver.register(
769 hex_key,
770 ModuleCode::Compiled(Arc::from(data.clone().into_boxed_slice())),
771 );
772 }
773
774 if let Some(ref store) = self.blob_store {
777 for (_name, hash) in manifest.exports.iter() {
778 let all_hashes: Vec<&[u8; 32]> = std::iter::once(hash)
779 .chain(
780 manifest
781 .dependency_closure
782 .get(hash)
783 .into_iter()
784 .flat_map(|v| v.iter()),
785 )
786 .collect();
787 for h in all_hashes {
788 let hex_key = format!("__blob__{}", hex::encode(h));
789 if !self.bundle_resolver.has(&hex_key) {
790 if let Some(data) = store.get(h) {
791 self.bundle_resolver.register(
792 hex_key,
793 ModuleCode::Compiled(Arc::from(data.into_boxed_slice())),
794 );
795 }
796 }
797 }
798 }
799 }
800
801 let module = Module {
802 name: manifest.name.clone(),
803 path: module_path.to_string(),
804 exports,
805 ast: shape_ast::ast::Program {
806 items: vec![],
807 docs: shape_ast::ast::ProgramDocs::default(),
808 },
809 };
810 let module = Arc::new(module);
811 self.cache.insert(module_path.to_string(), module.clone());
812 Ok(module)
813 }
814 };
815 self.cache.pop_loading();
816 result
817 }
818
819 pub fn load_module_from_file(&mut self, file_path: &Path) -> Result<Arc<Module>> {
824 let canonical = file_path
825 .canonicalize()
826 .unwrap_or_else(|_| file_path.to_path_buf());
827 let cache_key = canonical.to_string_lossy().to_string();
828
829 if let Some(module) = self.cache.get(&cache_key) {
831 return Ok(module);
832 }
833
834 self.cache.check_circular_dependency(&cache_key)?;
836
837 self.cache.push_loading(cache_key.clone());
838 let result = self.load_module_from_resolved_path(cache_key.clone(), &cache_key, canonical);
839 self.cache.pop_loading();
840
841 result
842 }
843
844 pub fn list_core_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::core::"))
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_core_stdlib_module_imports(self.stdlib_path.as_path())
869 }
870
871 pub fn list_stdlib_module_imports(&self) -> Result<Vec<String>> {
873 let mut embedded: Vec<String> = self
874 .embedded_stdlib_resolver
875 .module_paths()
876 .into_iter()
877 .filter(|name| name.starts_with("std::"))
878 .collect();
879 if !embedded.is_empty() {
880 embedded.sort();
881 embedded.dedup();
882 return Ok(embedded);
883 }
884
885 if !self.stdlib_path.exists() || !self.stdlib_path.is_dir() {
886 return Err(ShapeError::ModuleError {
887 message: format!(
888 "Could not find stdlib directory at {}",
889 self.stdlib_path.display()
890 ),
891 module_path: Some(self.stdlib_path.clone()),
892 });
893 }
894
895 resolution::list_stdlib_module_imports(self.stdlib_path.as_path())
896 }
897
898 pub fn list_importable_modules_with_context(
907 &self,
908 current_file: &Path,
909 workspace_root: Option<&Path>,
910 ) -> Vec<String> {
911 let mut modules = self.list_stdlib_module_imports().unwrap_or_default();
912
913 modules.extend(self.embedded_stdlib_resolver.module_paths());
914 modules.extend(self.extension_resolver.module_paths());
915
916 if let Some(project) = resolve_project_root(current_file, workspace_root) {
917 modules.extend(
918 resolution::list_modules_from_root(&project.root_path, None).unwrap_or_default(),
919 );
920
921 for module_path in project.resolved_module_paths() {
922 modules.extend(
923 resolution::list_modules_from_root(&module_path, None).unwrap_or_default(),
924 );
925 }
926
927 for (dep_name, dep_root) in resolve_path_dependencies(&project) {
928 modules.extend(
929 resolution::list_modules_from_root(&dep_root, Some(dep_name.as_str()))
930 .unwrap_or_default(),
931 );
932 }
933 } else if let Some(context_dir) = current_file.parent() {
934 modules
935 .extend(resolution::list_modules_from_root(context_dir, None).unwrap_or_default());
936 }
937
938 modules.sort();
939 modules.dedup();
940 modules.retain(|m| !m.is_empty());
941 modules
942 }
943
944 pub fn load_core_stdlib_modules(&mut self) -> Result<Vec<Arc<Module>>> {
946 let mut modules = Vec::new();
947 for import_path in self.list_core_stdlib_module_imports()? {
948 modules.push(self.load_module(&import_path)?);
949 }
950 Ok(modules)
951 }
952
953 pub fn load_stdlib(&mut self) -> Result<()> {
955 let _ = self.load_core_stdlib_modules()?;
956 Ok(())
957 }
958
959 pub fn loaded_modules(&self) -> Vec<&str> {
961 self.cache.loaded_modules()
962 }
963
964 pub fn get_export(&self, module_path: &str, export_name: &str) -> Option<&Export> {
966 self.cache.get_export(module_path, export_name)
967 }
968
969 pub fn get_module(&self, module_path: &str) -> Option<&Arc<Module>> {
971 self.cache.get_module(module_path)
972 }
973
974 pub fn resolve_import(&mut self, import_stmt: &ImportStmt) -> Result<HashMap<String, Export>> {
976 let module = self.load_module(&import_stmt.from)?;
977 cache::resolve_import(import_stmt, &module)
978 }
979
980 pub fn clear_cache(&mut self) {
982 self.cache.clear();
983 }
984
985 pub fn get_dependencies(&self, module_path: &str) -> Option<&Vec<String>> {
987 self.cache.get_dependencies(module_path)
988 }
989
990 pub fn get_all_dependencies(&self, module_path: &str) -> Vec<String> {
992 self.cache.get_all_dependencies(module_path)
993 }
994
995 fn package_key_for_origin_path(&self, origin_path: Option<&Path>) -> Option<String> {
996 let origin_path = origin_path?;
997 let origin = origin_path
998 .canonicalize()
999 .unwrap_or_else(|_| origin_path.to_path_buf());
1000
1001 for dep_root in self.dependency_paths.values() {
1002 let dep_root = dep_root.canonicalize().unwrap_or_else(|_| dep_root.clone());
1003 if origin.starts_with(&dep_root)
1004 && let Some(project) = find_project_root(&dep_root)
1005 {
1006 return Some(normalize_package_identity(&project.root_path, &project.config).2);
1007 }
1008 }
1009
1010 if let Some(project_root) = &self.current_project_root {
1011 let project_root = project_root
1012 .canonicalize()
1013 .unwrap_or_else(|_| project_root.clone());
1014 if origin.starts_with(&project_root)
1015 && let Some(project) = find_project_root(&project_root)
1016 {
1017 return Some(normalize_package_identity(&project.root_path, &project.config).2);
1018 }
1019 }
1020
1021 None
1022 }
1023}
1024
1025fn annotate_program_native_abi_package_key(program: &mut Program, package_key: Option<&str>) {
1026 let Some(package_key) = package_key else {
1027 return;
1028 };
1029 for item in &mut program.items {
1030 annotate_item_native_abi_package_key(item, package_key);
1031 }
1032}
1033
1034fn annotate_program_declaring_module_path(program: &mut Program, module_path: &str) {
1035 for item in &mut program.items {
1036 annotate_item_declaring_module_path(item, module_path);
1037 }
1038}
1039
1040fn annotate_item_native_abi_package_key(item: &mut shape_ast::ast::Item, package_key: &str) {
1041 use shape_ast::ast::{ExportItem, Item};
1042
1043 match item {
1044 Item::ForeignFunction(def, _) => {
1045 if let Some(native) = def.native_abi.as_mut()
1046 && native.package_key.is_none()
1047 {
1048 native.package_key = Some(package_key.to_string());
1049 }
1050 }
1051 Item::Export(export, _) => {
1052 if let ExportItem::ForeignFunction(def) = &mut export.item
1053 && let Some(native) = def.native_abi.as_mut()
1054 && native.package_key.is_none()
1055 {
1056 native.package_key = Some(package_key.to_string());
1057 }
1058 }
1059 Item::Module(module, _) => {
1060 for nested in &mut module.items {
1061 annotate_item_native_abi_package_key(nested, package_key);
1062 }
1063 }
1064 _ => {}
1065 }
1066}
1067
1068fn annotate_item_declaring_module_path(item: &mut shape_ast::ast::Item, module_path: &str) {
1069 use shape_ast::ast::{ExportItem, Item};
1070
1071 match item {
1072 Item::Function(def, _) => {
1073 if def.declaring_module_path.is_none() {
1074 def.declaring_module_path = Some(module_path.to_string());
1075 }
1076 }
1077 Item::Export(export, _) => match &mut export.item {
1078 ExportItem::Function(def) => {
1079 if def.declaring_module_path.is_none() {
1080 def.declaring_module_path = Some(module_path.to_string());
1081 }
1082 }
1083 ExportItem::ForeignFunction(_) => {}
1084 _ => {}
1085 },
1086 Item::Extend(extend, _) => {
1087 for method in &mut extend.methods {
1088 if method.declaring_module_path.is_none() {
1089 method.declaring_module_path = Some(module_path.to_string());
1090 }
1091 }
1092 }
1093 Item::Impl(impl_block, _) => {
1094 for method in &mut impl_block.methods {
1095 if method.declaring_module_path.is_none() {
1096 method.declaring_module_path = Some(module_path.to_string());
1097 }
1098 }
1099 }
1100 Item::Module(module, _) => {
1101 let nested_path = format!("{}::{}", module_path, module.name);
1102 for nested in &mut module.items {
1103 annotate_item_declaring_module_path(nested, &nested_path);
1104 }
1105 }
1106 _ => {}
1107 }
1108}
1109
1110impl Default for ModuleLoader {
1111 fn default() -> Self {
1112 Self::new()
1113 }
1114}
1115
1116pub fn resolve_module_path_with_settings(
1118 module_path: &str,
1119 context_path: Option<&Path>,
1120 stdlib_path: &Path,
1121 module_paths: &[PathBuf],
1122 dependency_paths: &HashMap<String, PathBuf>,
1123) -> Result<PathBuf> {
1124 resolution::resolve_module_path_with_context(
1125 module_path,
1126 context_path,
1127 stdlib_path,
1128 module_paths,
1129 dependency_paths,
1130 )
1131}
1132
1133fn resolve_project_root(current_file: &Path, workspace_root: Option<&Path>) -> Option<ProjectRoot> {
1134 workspace_root
1135 .and_then(find_project_root)
1136 .or_else(|| current_file.parent().and_then(find_project_root))
1137}
1138
1139fn resolve_path_dependencies(project: &ProjectRoot) -> HashMap<String, PathBuf> {
1140 let mut resolved = HashMap::new();
1141
1142 for (name, spec) in &project.config.dependencies {
1143 if let DependencySpec::Detailed(detailed) = spec {
1144 if let Some(path) = &detailed.path {
1145 let dep_path = project.root_path.join(path);
1146 let canonical = dep_path.canonicalize().unwrap_or(dep_path);
1147 resolved.insert(name.clone(), canonical);
1148 }
1149 }
1150 }
1151
1152 resolved
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use super::*;
1158 use std::sync::Arc;
1159
1160 #[test]
1161 fn test_compile_module_exports_function() {
1162 let source = r#"
1163pub fn greet(name) {
1164 return "Hello, " + name
1165}
1166"#;
1167 let ast = parse_program(source).unwrap();
1168 let module = loading::compile_module("test_module", ast).unwrap();
1169
1170 assert!(
1171 module.exports.contains_key("greet"),
1172 "Expected 'greet' export, got: {:?}",
1173 module.exports.keys().collect::<Vec<_>>()
1174 );
1175
1176 match module.exports.get("greet") {
1177 Some(Export::Function(func)) => {
1178 assert_eq!(func.name, "greet");
1179 }
1180 other => panic!("Expected Function export, got: {:?}", other),
1181 }
1182 }
1183
1184 #[test]
1185 fn test_collect_exported_function_names_from_source() {
1186 let source = r#"
1187fn hidden() { 0 }
1188pub fn connect(uri) { uri }
1189pub fn ping() { 1 }
1190"#;
1191 let names = collect_exported_function_names_from_source("duckdb", source)
1192 .expect("should collect exported functions");
1193 assert_eq!(names, vec!["connect".to_string(), "ping".to_string()]);
1194 }
1195
1196 #[test]
1197 fn test_stdlib_methods_are_annotated_with_declaring_module_path() {
1198 let mut loader = ModuleLoader::new();
1199 let module = loader
1200 .load_module("std::core::json_value")
1201 .expect("load stdlib module");
1202
1203 let extend = module
1204 .ast
1205 .items
1206 .iter()
1207 .find_map(|item| match item {
1208 shape_ast::ast::Item::Extend(extend, _) => Some(extend),
1209 _ => None,
1210 })
1211 .expect("json_value module should contain an extend block");
1212 let method = extend
1213 .methods
1214 .iter()
1215 .find(|method| method.name == "get")
1216 .expect("json_value extend block should contain get()");
1217
1218 assert_eq!(
1219 method.declaring_module_path.as_deref(),
1220 Some("std::core::json_value")
1221 );
1222 }
1223
1224 #[test]
1225 fn test_load_module_from_temp_file() {
1226 use std::io::Write;
1227
1228 let temp_dir = std::env::temp_dir();
1230 let module_path = temp_dir.join("test_load_module.shape");
1231 let mut file = std::fs::File::create(&module_path).unwrap();
1232 writeln!(
1233 file,
1234 r#"
1235pub fn add(a, b) {{
1236 return a + b
1237}}
1238"#
1239 )
1240 .unwrap();
1241
1242 let mut loader = ModuleLoader::new();
1244 loader.add_module_path(temp_dir.clone());
1245
1246 let result = loader.load_module_with_context("test_load_module", Some(&temp_dir));
1248
1249 std::fs::remove_file(&module_path).ok();
1251
1252 let module = result.expect("Module should load");
1254 assert!(
1255 module.exports.contains_key("add"),
1256 "Expected 'add' export, got: {:?}",
1257 module.exports.keys().collect::<Vec<_>>()
1258 );
1259 }
1260
1261 #[test]
1262 fn test_load_module_from_file_path() {
1263 use std::io::Write;
1264
1265 let temp_dir = tempfile::tempdir().expect("temp dir");
1266 let module_path = temp_dir.path().join("helpers.shape");
1267 let mut file = std::fs::File::create(&module_path).expect("create module");
1268 writeln!(
1269 file,
1270 r#"
1271pub fn helper(x) {{
1272 x
1273}}
1274"#
1275 )
1276 .expect("write module");
1277
1278 let mut loader = ModuleLoader::new();
1279 let module = loader
1280 .load_module_from_file(&module_path)
1281 .expect("module should load from file path");
1282 assert!(
1283 module.exports.contains_key("helper"),
1284 "Expected 'helper' export, got: {:?}",
1285 module.exports.keys().collect::<Vec<_>>()
1286 );
1287 }
1288
1289 #[test]
1290 fn test_loaded_dependency_module_annotates_native_abi_with_package_key() {
1291 let root = tempfile::tempdir().expect("tempdir");
1292 let dep_root = root.path().join("dep_pkg");
1293
1294 std::fs::create_dir_all(&dep_root).expect("create dep root");
1295 std::fs::write(
1296 root.path().join("shape.toml"),
1297 r#"
1298[project]
1299name = "app"
1300version = "0.1.0"
1301
1302[dependencies]
1303dep_pkg = { path = "./dep_pkg" }
1304"#,
1305 )
1306 .expect("write root shape.toml");
1307 std::fs::write(
1308 dep_root.join("shape.toml"),
1309 r#"
1310[project]
1311name = "dep_pkg"
1312version = "1.2.3"
1313"#,
1314 )
1315 .expect("write dep shape.toml");
1316 std::fs::write(
1317 dep_root.join("index.shape"),
1318 r#"
1319extern C fn dep_call() -> i32 from "shared";
1320"#,
1321 )
1322 .expect("write dep source");
1323
1324 let mut loader = ModuleLoader::new();
1325 loader.set_project_root(root.path(), &[]);
1326 loader.set_dependency_paths(HashMap::from([("dep_pkg".to_string(), dep_root.clone())]));
1327
1328 let module = loader.load_module("dep_pkg").expect("load dep module");
1329 let foreign = module
1330 .ast
1331 .items
1332 .iter()
1333 .find_map(|item| match item {
1334 shape_ast::ast::Item::ForeignFunction(def, _) => Some(def),
1335 _ => None,
1336 })
1337 .expect("foreign function should exist");
1338 let native = foreign
1339 .native_abi
1340 .as_ref()
1341 .expect("native abi should exist");
1342 assert_eq!(native.package_key.as_deref(), Some("dep_pkg@1.2.3"));
1343 }
1344
1345 #[test]
1346 fn test_collect_exported_symbols_detects_pub_function_and_enum() {
1347 let source = r#"
1348pub fn helper() { 1 }
1349pub enum Side { Buy, Sell }
1350"#;
1351 let ast = parse_program(source).unwrap();
1352 let exports = collect_exported_symbols(&ast).unwrap();
1353
1354 let helper = exports
1355 .iter()
1356 .find(|e| e.name == "helper")
1357 .expect("expected helper export");
1358 assert_eq!(helper.name, "helper");
1359 assert!(helper.alias.is_none());
1360 assert_eq!(helper.kind, ModuleExportKind::Function);
1361
1362 let side = exports
1363 .iter()
1364 .find(|e| e.name == "Side")
1365 .expect("expected Side export");
1366 assert_eq!(side.kind, ModuleExportKind::Enum);
1367 }
1368
1369 #[test]
1370 fn test_list_core_stdlib_module_imports_contains_core_modules() {
1371 let loader = ModuleLoader::new();
1372 let modules = loader
1373 .list_core_stdlib_module_imports()
1374 .expect("should list std.core modules");
1375
1376 assert!(
1377 !modules.is_empty(),
1378 "expected non-empty std.core module list"
1379 );
1380 assert!(
1381 modules.iter().all(|m| m.starts_with("std::core::")),
1382 "expected std::core::* import paths, got: {:?}",
1383 modules
1384 );
1385 assert!(
1386 modules.iter().any(|m| m == "std::core::math"),
1387 "expected std::core::math in core module list"
1388 );
1389 }
1390
1391 #[test]
1392 fn test_list_stdlib_module_imports_includes_non_core_namespaces() {
1393 let loader = ModuleLoader::new();
1394 let modules = loader
1395 .list_stdlib_module_imports()
1396 .expect("should list stdlib modules");
1397
1398 assert!(
1399 modules.iter().any(|m| m.starts_with("std::finance::")),
1400 "expected finance stdlib modules in list, got: {:?}",
1401 modules
1402 );
1403 }
1404
1405 #[test]
1406 fn test_embedded_stdlib_loads_without_filesystem_path() {
1407 let mut loader = ModuleLoader::new();
1408 loader.set_stdlib_path(std::env::temp_dir().join("shape_missing_stdlib_dir"));
1409
1410 let module = loader
1411 .load_module("std::core::snapshot")
1412 .expect("embedded stdlib module should load without filesystem stdlib");
1413 assert!(
1414 module.exports.contains_key("snapshot"),
1415 "expected snapshot export from std::core::snapshot"
1416 );
1417 }
1418
1419 #[test]
1420 fn test_list_importable_modules_with_context_includes_project_and_deps() {
1421 let tmp = tempfile::tempdir().unwrap();
1422 let root = tmp.path();
1423
1424 std::fs::write(
1425 root.join("shape.toml"),
1426 r#"
1427[modules]
1428paths = ["lib"]
1429
1430[dependencies]
1431mydep = { path = "deps/mydep" }
1432"#,
1433 )
1434 .unwrap();
1435
1436 std::fs::create_dir_all(root.join("src")).unwrap();
1437 std::fs::create_dir_all(root.join("lib")).unwrap();
1438 std::fs::create_dir_all(root.join("deps/mydep")).unwrap();
1439
1440 std::fs::write(root.join("src/main.shape"), "let x = 1").unwrap();
1441 std::fs::write(root.join("lib/tools.shape"), "pub fn tool() { 1 }").unwrap();
1442 std::fs::write(root.join("deps/mydep/index.shape"), "pub fn root() { 1 }").unwrap();
1443 std::fs::write(root.join("deps/mydep/util.shape"), "pub fn util() { 1 }").unwrap();
1444
1445 let loader = ModuleLoader::new();
1446 let modules =
1447 loader.list_importable_modules_with_context(&root.join("src/main.shape"), None);
1448
1449 assert!(
1450 modules.iter().any(|m| m == "tools"),
1451 "expected module path from [modules].paths, got: {:?}",
1452 modules
1453 );
1454 assert!(
1455 modules.iter().any(|m| m == "mydep"),
1456 "expected dependency index module path, got: {:?}",
1457 modules
1458 );
1459 assert!(
1460 modules.iter().any(|m| m == "mydep::util"),
1461 "expected dependency submodule path, got: {:?}",
1462 modules
1463 );
1464 }
1465
1466 #[test]
1467 fn test_load_in_memory_extension_module() {
1468 let mut loader = ModuleLoader::new();
1469 loader.register_extension_module(
1470 "duckdb",
1471 ModuleCode::Source(Arc::from(
1472 r#"
1473pub fn connect(uri) { uri }
1474"#,
1475 )),
1476 );
1477
1478 let module = loader
1479 .load_module("duckdb")
1480 .expect("in-memory extension module should load");
1481 assert!(
1482 module.exports.contains_key("connect"),
1483 "expected connect export, got {:?}",
1484 module.exports.keys().collect::<Vec<_>>()
1485 );
1486 }
1487
1488 #[test]
1489 fn test_load_in_memory_extension_module_with_dependency() {
1490 let mut loader = ModuleLoader::new();
1491 loader.register_extension_module(
1492 "b",
1493 ModuleCode::Source(Arc::from(
1494 r#"
1495pub fn answer() { 42 }
1496"#,
1497 )),
1498 );
1499 loader.register_extension_module(
1500 "a",
1501 ModuleCode::Source(Arc::from(
1502 r#"
1503from b use { answer }
1504pub fn use_answer() { answer() }
1505"#,
1506 )),
1507 );
1508
1509 let module = loader
1510 .load_module("a")
1511 .expect("in-memory module with dependency should load");
1512 assert!(
1513 module.exports.contains_key("use_answer"),
1514 "expected use_answer export"
1515 );
1516 assert!(
1517 loader.get_module("b").is_some(),
1518 "dependency module b should load"
1519 );
1520 }
1521
1522 #[test]
1523 fn test_load_bundle_modules() {
1524 use crate::package_bundle::{BundleMetadata, BundledModule, PackageBundle};
1525
1526 let bundle = PackageBundle {
1527 metadata: BundleMetadata {
1528 name: "test".to_string(),
1529 version: "0.1.0".to_string(),
1530 compiler_version: "0.5.0".to_string(),
1531 source_hash: "abc".to_string(),
1532 bundle_kind: "portable-bytecode".to_string(),
1533 build_host: "x86_64-linux".to_string(),
1534 native_portable: true,
1535 entry_module: None,
1536 built_at: 0,
1537 readme: None,
1538 },
1539 modules: vec![BundledModule {
1540 module_path: "helpers".to_string(),
1541 bytecode_bytes: vec![1, 2, 3],
1542 export_names: vec!["helper".to_string()],
1543 source_hash: "def".to_string(),
1544 }],
1545 dependencies: std::collections::HashMap::new(),
1546 blob_store: std::collections::HashMap::new(),
1547 manifests: vec![],
1548 native_dependency_scopes: vec![],
1549 docs: std::collections::HashMap::new(),
1550 };
1551
1552 let mut loader = ModuleLoader::new();
1553 loader.load_bundle(&bundle, Some("mylib"));
1554
1555 let artifact = loader.resolve_module_artifact_with_context("mylib::helpers", None);
1557 assert!(artifact.is_ok(), "bundle module should be resolvable");
1558 }
1559}