1use std::collections::BTreeMap;
2use std::future::Future;
3use std::hash::{Hash, Hasher};
4use std::path::{Path, PathBuf};
5use std::pin::Pin;
6use std::sync::{Arc, Mutex, OnceLock};
7
8use harn_modules::DefKind;
9
10use crate::bytecode_cache;
11use crate::module_artifact::compile_module_artifact_from_source;
12use crate::module_source::{self, ModuleSource};
13use crate::prepared_module::PreparedModuleArtifact;
14use crate::value::{ModuleFunctionRegistry, VmClosure, VmEnv, VmError, VmValue};
15
16use super::{ScopeSpan, Vm};
17
18static STDLIB_MODULE_ARTIFACT_CACHE: OnceLock<
19 Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>>,
20> = OnceLock::new();
21
22fn stdlib_module_artifact_cache() -> &'static Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>> {
23 STDLIB_MODULE_ARTIFACT_CACHE.get_or_init(|| Mutex::new(BTreeMap::new()))
24}
25
26fn verified_package_source(bytes: Vec<u8>, path: &Path) -> Result<String, VmError> {
27 String::from_utf8(bytes).map_err(|error| {
28 VmError::Runtime(format!(
29 "installed package source {} is not valid UTF-8: {error}",
30 path.display()
31 ))
32 })
33}
34
35fn exported_function_closures(
36 loaded: &LoadedModule,
37 display_path: &Path,
38) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
39 let mut exports = BTreeMap::new();
40 for name in loaded
41 .public_exports
42 .keys()
43 .filter(|name| loaded.functions.contains_key(*name))
44 {
45 let Some(closure) = loaded.functions.get(name) else {
46 return Err(VmError::Runtime(format!(
47 "Import error: exported function '{name}' is missing from {}",
48 display_path.display()
49 )));
50 };
51 exports.insert(name.clone(), Arc::clone(closure));
52 }
53 Ok(exports)
54}
55
56#[cfg(test)]
57fn reset_stdlib_module_artifact_cache() {
58 stdlib_module_artifact_cache().lock().unwrap().clear();
59}
60
61#[cfg(test)]
62fn stdlib_module_artifact_cache_ptr(module: &str, source: &str) -> Option<usize> {
63 let key = stdlib_artifact_cache_key(module, source);
64 stdlib_module_artifact_cache()
65 .lock()
66 .unwrap()
67 .get(&key)
68 .map(|artifact| Arc::as_ptr(artifact) as usize)
69}
70
71pub(crate) struct LoadedModule {
72 pub(crate) functions: BTreeMap<String, Arc<VmClosure>>,
73 pub(crate) public_exports: BTreeMap<String, DefKind>,
76 pub(crate) public_values: BTreeMap<String, VmValue>,
79 pub(crate) public_type_schemas: BTreeMap<String, VmValue>,
83 package_execution_guard: Option<Arc<harn_modules::package_execution::PackageExecutionGuard>>,
88 pub(crate) _module_functions: crate::value::ModuleFunctionRegistry,
89 pub(crate) _module_state: crate::value::ModuleState,
90}
91
92pub(crate) type ModuleCache = Arc<BTreeMap<PathBuf, Arc<LoadedModule>>>;
100
101#[derive(Clone, Debug)]
107pub(crate) struct DeferredCyclicImport {
108 pub(crate) importer: PathBuf,
110 pub(crate) target: PathBuf,
112 pub(crate) selected_names: Option<Vec<String>>,
114 pub(crate) namespace_alias: Option<String>,
116}
117
118#[derive(Clone, Copy)]
119enum ImportProjection<'a> {
120 BindCaller(Option<&'a [String]>),
121 BindNamespace(&'a str),
123 MaterializeOnly,
124}
125
126impl ImportProjection<'_> {
127 fn package_rejection_kind(self) -> &'static str {
128 match self {
129 Self::BindCaller(_) | Self::BindNamespace(_) => "import",
130 Self::MaterializeOnly => "execution",
131 }
132 }
133}
134
135fn module_import_names(
139 module_name: &str,
140 loaded: &LoadedModule,
141 selected_names: Option<&[String]>,
142) -> Result<Vec<String>, VmError> {
143 if let Some(names) = selected_names {
144 for name in names {
145 if !loaded.public_exports.contains_key(name) {
146 let hint = if loaded.functions.contains_key(name) {
147 " — it is defined there but not `pub`; mark it `pub` to export it"
148 } else {
149 ""
150 };
151 return Err(VmError::Runtime(format!(
152 "Import error: '{name}' is not exported by {module_name}{hint}"
153 )));
154 }
155 }
156 return Ok(names.to_vec());
157 }
158
159 Ok(loaded.public_exports.keys().cloned().collect())
160}
161
162fn build_namespace_dict(module_path: &str, loaded: &LoadedModule) -> VmValue {
168 let mut map = BTreeMap::new();
169 map.insert(
170 "_namespace".to_string(),
171 VmValue::String(arcstr::ArcStr::from(module_path)),
172 );
173 for (name, kind) in &loaded.public_exports {
174 if !kind.has_runtime_value() {
175 if let Some(schema) = loaded.public_type_schemas.get(name) {
177 map.insert(name.clone(), schema.clone());
178 }
179 continue;
180 }
181 if let Some(value) = loaded.public_values.get(name) {
182 map.insert(name.clone(), value.clone());
183 continue;
184 }
185 if let Some(schema) = loaded.public_type_schemas.get(name) {
186 map.insert(name.clone(), schema.clone());
187 continue;
188 }
189 if let Some(closure) = loaded.functions.get(name) {
190 map.insert(name.clone(), VmValue::Closure(Arc::clone(closure)));
191 }
192 }
193 VmValue::dict(map)
194}
195
196pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
197 let synthetic_current_file = base.join("__harn_import_base__.harn");
198 if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
199 return resolved;
200 }
201
202 let mut file_path = base.join(path);
203
204 if !file_path.exists() && file_path.extension().is_none() {
205 file_path.set_extension("harn");
206 }
207
208 file_path
209}
210
211fn stdlib_artifact_cache_key(module: &str, source: &str) -> String {
212 let mut hasher = std::collections::hash_map::DefaultHasher::new();
213 module.hash(&mut hasher);
214 source.hash(&mut hasher);
215 format!("{module}:{:016x}", hasher.finish())
216}
217
218fn stdlib_module_artifact(
219 module: &str,
220 synthetic: &Path,
221 source: &'static str,
222 recorder: Option<&super::ModulePhaseRecorder>,
223) -> Result<Arc<PreparedModuleArtifact>, VmError> {
224 let key = stdlib_artifact_cache_key(module, source);
225 {
226 let cache = stdlib_module_artifact_cache().lock().unwrap();
227 if let Some(cached) = cache.get(&key) {
228 return Ok(Arc::clone(cached));
229 }
230 }
231
232 let embedded = ModuleSource::from_text(source);
237 let lookup = {
238 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
239 bytecode_cache::load_module(synthetic, &embedded)
240 };
241 let artifact = if let Some(artifact) = lookup.artifact {
242 artifact
243 } else {
244 let mut compile_span = recorder.map(super::ModulePhaseRecorder::compile_span);
245 let compiled = compile_module_artifact_from_source(synthetic, source)?;
246 if let Some(span) = &mut compile_span {
247 span.mark_compile_succeeded();
248 }
249 drop(compile_span);
250 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
251 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
252 eprintln!("[harn] stdlib module cache write skipped for {module}: {err}");
253 }
254 }
255 compiled
256 };
257
258 let compiled = {
259 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
260 Arc::new(PreparedModuleArtifact::from_cached(artifact))
261 };
262 let mut cache = stdlib_module_artifact_cache().lock().unwrap();
263 if let Some(cached) = cache.get(&key) {
264 return Ok(Arc::clone(cached));
265 }
266 cache.insert(key, Arc::clone(&compiled));
267 Ok(compiled)
268}
269
270impl Vm {
271 fn resolve_module_import_path(&self, base: &Path, path: &str) -> Result<PathBuf, VmError> {
272 if let Some(guard) = &self.package_execution_guard {
273 let synthetic_current_file = base.join("__harn_import_base__.harn");
274 if let Some(resolved) =
275 harn_modules::resolve_import_path_with_guard(&synthetic_current_file, path, guard)
276 .map_err(|error| {
277 VmError::Runtime(format!("installed package import rejected: {error}"))
278 })?
279 {
280 return Ok(resolved);
281 }
282 let mut file_path = base.join(path);
283 if !file_path.exists() && file_path.extension().is_none() {
284 file_path.set_extension("harn");
285 }
286 return Ok(file_path);
287 }
288 Ok(resolve_module_import_path(base, path))
289 }
290
291 pub async fn resolve_callable(
295 &mut self,
296 callable: &crate::value::VmCallable,
297 ) -> Result<Arc<crate::value::VmClosure>, VmError> {
298 self.ensure_execution_available()?;
299 match callable {
300 crate::value::VmCallable::Eager(closure) => Ok(Arc::clone(closure)),
301 crate::value::VmCallable::Lazy(lazy) => {
302 let (cache_key, module_path) = self.lazy_callable_module_path(lazy);
303 let next_guard = lazy
304 .package_execution_guard_handle()
305 .or_else(|| self.package_execution_guard.clone());
306 if let Some(guard) = &next_guard {
307 guard.verify_entry_source(&module_path).map_err(|error| {
308 VmError::Runtime(format!("installed package execution rejected: {error}"))
309 })?;
310 }
311 let resolution = {
312 let mut modules = self.lazy_callable_modules.lock();
313 let slots = modules.entry(cache_key).or_default();
314 if let Some(slot) = slots.iter().find(|slot| slot.execution_guard == next_guard)
315 {
316 Arc::clone(&slot.resolution)
317 } else {
318 let resolution = Arc::new(tokio::sync::OnceCell::new());
319 slots.push(crate::vm::state::LazyCallableCacheSlot {
320 execution_guard: next_guard.clone(),
321 resolution: Arc::clone(&resolution),
322 });
323 resolution
324 }
325 };
326 let previous_package_execution_guard =
327 std::mem::replace(&mut self.package_execution_guard, next_guard);
328 let resolved = resolution
329 .get_or_try_init(|| async {
330 let exports = self.load_module_exports(&module_path).await?;
331 let exports = exports
332 .into_iter()
333 .map(|(name, closure)| (name, closure.retained_for_host_registry()))
334 .collect();
335 Ok::<_, VmError>(Arc::new(crate::vm::state::ResolvedLazyCallable {
340 exports,
341 retained_module_graph: Arc::clone(&self.module_cache),
342 }))
343 })
344 .await;
345 self.package_execution_guard = previous_package_execution_guard;
346 let resolved = resolved?;
347 resolved
348 .exports
349 .get(&lazy.function_name)
350 .cloned()
351 .ok_or_else(|| {
352 VmError::Runtime(format!(
353 "function '{}' is not exported by module '{}'",
354 lazy.function_name,
355 lazy.module_path.display()
356 ))
357 })
358 }
359 crate::value::VmCallable::Pipeline(_) => Err(VmError::TypeError(
360 "pipeline callable requires execute_callable".to_string(),
361 )),
362 }
363 }
364
365 pub async fn execute_callable(
366 &mut self,
367 callable: &crate::value::VmCallable,
368 args: &[crate::value::VmValue],
369 ) -> Result<crate::value::VmValue, VmError> {
370 let crate::value::VmCallable::Pipeline(pipeline) = callable else {
371 let closure = self.resolve_callable(callable).await?;
372 return self.call_closure_pub(&closure, args).await;
373 };
374
375 let (_, module_path) = self.lazy_module_path(&pipeline.module_path);
376 let next_guard = pipeline
377 .package_execution_guard_handle()
378 .or_else(|| self.package_execution_guard.clone());
379 let previous_package_execution_guard =
380 std::mem::replace(&mut self.package_execution_guard, next_guard);
381 let result = async {
382 let closure = self
383 .load_public_module_callable(&module_path, &pipeline.pipeline_name)
384 .await?;
385 self.call_closure_pub(&closure, args).await
386 }
387 .await;
388 self.package_execution_guard = previous_package_execution_guard;
389 result
390 }
391
392 fn lazy_callable_module_path(&self, lazy: &crate::value::LazyVmCallable) -> (PathBuf, PathBuf) {
393 self.lazy_module_path(&lazy.module_path)
394 }
395
396 fn lazy_module_path(&self, path: &std::path::Path) -> (PathBuf, PathBuf) {
397 let mut module_path = if path.is_absolute() {
398 path.to_path_buf()
399 } else {
400 self.source_dir
401 .clone()
402 .unwrap_or_else(|| PathBuf::from("."))
403 .join(path)
404 };
405 if !module_path.exists() && module_path.extension().is_none() {
406 module_path.set_extension("harn");
407 }
408 let cache_key = module_path
409 .canonicalize()
410 .unwrap_or_else(|_| module_path.clone());
411 (cache_key, module_path)
412 }
413
414 async fn load_module_from_source(
415 &mut self,
416 synthetic: PathBuf,
417 source: &str,
418 ) -> Result<Arc<LoadedModule>, VmError> {
419 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
420 return Ok(loaded);
421 }
422 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
423
424 let mut compile_span = self.module_compile_span();
425 let compiled = compile_module_artifact_from_source(&synthetic, source)?;
426 if let Some(span) = &mut compile_span {
427 span.mark_compile_succeeded();
428 }
429 drop(compile_span);
430 let artifact = {
431 let _load_span = self.module_load_span();
432 PreparedModuleArtifact::from_cached(compiled)
433 };
434
435 self.imported_paths.push(synthetic.clone());
436 let loaded = Arc::new(self.instantiate_module(None, &artifact).await?);
437 self.imported_paths.pop();
438 {
439 let _load_span = self.module_load_span();
440 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
441 }
442 self.record_module_loaded();
443 Ok(loaded)
444 }
445
446 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
455 for name in harn_stdlib::builtin_reexports(module) {
456 if loaded.public_exports.contains_key(*name) {
460 continue;
461 }
462 loaded
463 .public_exports
464 .insert((*name).to_string(), DefKind::Function);
465 loaded.public_values.insert(
466 (*name).to_string(),
467 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
468 );
469 }
470 }
471
472 async fn load_stdlib_module_from_source(
473 &mut self,
474 module: &str,
475 synthetic: PathBuf,
476 source: &'static str,
477 ) -> Result<Arc<LoadedModule>, VmError> {
478 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
479 return Ok(loaded);
480 }
481 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
482
483 let artifact = stdlib_module_artifact(
484 module,
485 &synthetic,
486 source,
487 self.module_phase_recorder.as_ref(),
488 )?;
489 self.imported_paths.push(synthetic.clone());
490 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
491 self.imported_paths.pop();
492 Self::add_builtin_reexports(module, &mut loaded);
493 let loaded = Arc::new(loaded);
494 {
495 let _load_span = self.module_load_span();
496 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
497 }
498 self.record_module_loaded();
499 Ok(loaded)
500 }
501
502 async fn instantiate_stdlib_module(
503 &mut self,
504 artifact: &PreparedModuleArtifact,
505 ) -> Result<LoadedModule, VmError> {
506 self.instantiate_module(None, artifact).await
507 }
508
509 async fn instantiate_module(
517 &mut self,
518 module_source_dir: Option<PathBuf>,
519 artifact: &PreparedModuleArtifact,
520 ) -> Result<LoadedModule, VmError> {
521 let caller_env = self.env.clone();
522 let old_source_dir = self.source_dir.clone();
523 self.env = VmEnv::new();
524 self.source_dir = module_source_dir.clone();
525
526 for import in &artifact.imports {
527 if let Some(alias) = &import.namespace_alias {
528 self.execute_namespace_import_bind(&import.path, alias)
529 .await?;
530 } else {
531 self.execute_import(&import.path, import.selected_names.as_deref())
532 .await?;
533 }
534 }
535
536 let _load_span = self.module_load_span();
539
540 let module_state: crate::value::ModuleState = {
541 let mut init_env = self.env.clone();
542 if artifact.type_schema_init_chunk.is_some() || artifact.init_chunk.is_some() {
543 let saved_env = std::mem::replace(&mut self.env, init_env);
544 let saved_frames = std::mem::take(&mut self.frames);
545 let saved_handlers = std::mem::take(&mut self.exception_handlers);
546 let saved_iterators = std::mem::take(&mut self.iterators);
547 let saved_deadlines = std::mem::take(&mut self.deadlines);
548 let active_context = crate::step_runtime::suspend_active_context();
559 let init_result: Result<(), VmError> = async {
560 if let Some(chunk) = &artifact.type_schema_init_chunk {
561 self.run_chunk(Arc::clone(chunk)).await?;
562 }
563 if let Some(chunk) = &artifact.init_chunk {
564 self.run_chunk(Arc::clone(chunk)).await?;
565 }
566 Ok(())
567 }
568 .await;
569 drop(active_context);
570 init_env = std::mem::replace(&mut self.env, saved_env);
571 self.frames = saved_frames;
572 self.exception_handlers = saved_handlers;
573 self.iterators = saved_iterators;
574 self.deadlines = saved_deadlines;
575 init_result?;
576 }
577 Arc::new(crate::value::VmMutex::new(init_env))
578 };
579
580 let module_env = self.env.clone();
581 let registry: ModuleFunctionRegistry =
582 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
583 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
584 let mut public_exports = artifact.public_exports.clone();
585 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
589 {
590 let state = module_state.lock();
591 for name in &artifact.public_value_names {
592 if let Some(value) = state.get(name) {
593 public_values.insert(name.clone(), value);
594 }
595 }
596 }
597 let public_type_names = artifact.public_type_names.clone();
598 let mut public_type_schemas: BTreeMap<String, VmValue> = {
599 let state = module_state.lock();
600 public_type_names
601 .iter()
602 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
603 .collect()
604 };
605
606 for (name, compiled) in &artifact.functions {
607 let closure = Arc::new(VmClosure {
608 func: Arc::clone(compiled),
609 env: module_env.clone(),
610 source_dir: module_source_dir.clone(),
611 module_functions: Some(Arc::downgrade(®istry)),
612 module_state: Some(Arc::downgrade(&module_state)),
613 retained_module_scope: None,
614 });
615 registry.lock().insert(name.clone(), Arc::clone(&closure));
616 self.env
617 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
618 module_state
619 .lock()
620 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
621 functions.insert(name.clone(), Arc::clone(&closure));
622 }
623
624 for import in artifact.imports.iter().filter(|import| import.is_pub) {
625 let cache_key = self.cache_key_for_import(&import.path)?;
626 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
627 if self.imported_paths.contains(&cache_key) {
634 return Err(VmError::Runtime(format!(
635 "Re-export error: cannot `pub import` from '{}' because it forms an \
636 import cycle with this module (its public surface is still being \
637 built). Use a plain `import` here, or re-export from a module that is \
638 not part of the cycle.",
639 import.path
640 )));
641 }
642 return Err(VmError::Runtime(format!(
643 "Re-export error: imported module '{}' was not loaded",
644 import.path
645 )));
646 };
647 if let Some(alias) = &import.namespace_alias {
650 if public_exports.contains_key(alias) || functions.contains_key(alias) {
651 return Err(VmError::Runtime(format!(
652 "Re-export collision: '{alias}' is defined here and also \
653 re-exported as a namespace from '{}'",
654 import.path
655 )));
656 }
657 let dict = build_namespace_dict(&import.path, &loaded);
658 public_values.insert(alias.clone(), dict);
659 public_exports.insert(alias.clone(), DefKind::Variable);
660 continue;
661 }
662 let names_to_reexport =
663 module_import_names(&import.path, &loaded, import.selected_names.as_deref())?;
664 for name in names_to_reexport {
665 let Some(kind) = loaded.public_exports.get(&name).copied() else {
666 return Err(VmError::Runtime(format!(
667 "Re-export error: '{name}' is not exported by '{}'",
668 import.path
669 )));
670 };
671 let Some(closure) = loaded.functions.get(&name) else {
672 if let Some(value) = loaded.public_values.get(&name) {
676 public_values.insert(name.clone(), value.clone());
677 public_exports.insert(name, kind);
678 continue;
679 }
680 if let Some(schema) = loaded.public_type_schemas.get(&name) {
683 public_type_schemas.insert(name.clone(), schema.clone());
684 }
685 public_exports.insert(name, kind);
686 continue;
687 };
688 if let Some(existing) = functions.get(&name) {
689 if !Arc::ptr_eq(existing, closure) {
690 return Err(VmError::Runtime(format!(
691 "Re-export collision: '{name}' is defined here and also \
692 re-exported from '{}'",
693 import.path
694 )));
695 }
696 }
697 functions.insert(name.clone(), Arc::clone(closure));
698 public_exports.insert(name, kind);
699 }
700 }
701
702 self.env = caller_env;
703 self.source_dir = old_source_dir;
704
705 Ok(LoadedModule {
706 functions,
707 public_exports,
708 public_values,
709 public_type_schemas,
710 package_execution_guard: module_source_dir
711 .as_ref()
712 .and(self.package_execution_guard.clone()),
713 _module_functions: registry,
714 _module_state: module_state,
715 })
716 }
717
718 fn export_namespace_module(
719 &mut self,
720 module_path: &Path,
721 loaded: &LoadedModule,
722 alias: &str,
723 ) -> Result<(), VmError> {
724 let module_name = module_path.display().to_string();
725 if self.env.get(alias).is_some() {
726 return Err(VmError::Runtime(format!(
727 "Import collision: '{alias}' is already defined when importing {module_name}. \
728 Use a different namespace alias: import * as <name> from \"...\""
729 )));
730 }
731 let dict = build_namespace_dict(&module_name, loaded);
732 self.env.define(alias, dict, false)?;
733 Ok(())
734 }
735
736 fn export_loaded_module(
737 &mut self,
738 module_path: &Path,
739 loaded: &LoadedModule,
740 selected_names: Option<&[String]>,
741 ) -> Result<(), VmError> {
742 let module_name = module_path.display().to_string();
743 let export_names = module_import_names(&module_name, loaded, selected_names)?;
744
745 for name in export_names {
746 if let Some(value) = loaded.public_values.get(&name) {
748 if self.env.get(&name).is_some() {
749 return Err(VmError::Runtime(format!(
750 "Import collision: '{name}' is already defined when importing \
751 {module_name}. Use selective imports to disambiguate: \
752 import {{ {name} }} from \"...\""
753 )));
754 }
755 self.env.define(&name, value.clone(), false)?;
756 continue;
757 }
758 if let Some(schema) = loaded.public_type_schemas.get(&name) {
762 self.env.define(&name, schema.clone(), false)?;
763 continue;
764 }
765 if loaded
766 .public_exports
767 .get(&name)
768 .is_some_and(|kind| !kind.has_runtime_value())
769 {
770 continue;
771 }
772 let Some(closure) = loaded.functions.get(&name) else {
773 return Err(VmError::Runtime(format!(
774 "Import error: '{name}' is not defined in {module_name}"
775 )));
776 };
777 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
778 return Err(VmError::Runtime(format!(
779 "Import collision: '{name}' is already defined when importing {module_name}. \
780 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
781 )));
782 }
783 self.env
784 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
785 }
786 Ok(())
787 }
788
789 pub(super) fn execute_import<'a>(
791 &'a mut self,
792 path: &'a str,
793 selected_names: Option<&'a [String]>,
794 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
795 self.execute_import_with_projection(path, ImportProjection::BindCaller(selected_names))
796 }
797
798 pub(super) fn execute_namespace_import_bind<'a>(
800 &'a mut self,
801 path: &'a str,
802 alias: &'a str,
803 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
804 self.execute_import_with_projection(path, ImportProjection::BindNamespace(alias))
805 }
806
807 fn materialize_import<'a>(
808 &'a mut self,
809 path: &'a str,
810 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
811 self.execute_import_with_projection(path, ImportProjection::MaterializeOnly)
812 }
813
814 fn apply_import_projection(
815 &mut self,
816 module_path: &Path,
817 loaded: &LoadedModule,
818 projection: ImportProjection<'_>,
819 ) -> Result<(), VmError> {
820 match projection {
821 ImportProjection::BindCaller(selected_names) => {
822 self.export_loaded_module(module_path, loaded, selected_names)
823 }
824 ImportProjection::BindNamespace(alias) => {
825 self.export_namespace_module(module_path, loaded, alias)
826 }
827 ImportProjection::MaterializeOnly => Ok(()),
828 }
829 }
830
831 fn execute_import_with_projection<'a>(
832 &'a mut self,
833 path: &'a str,
834 projection: ImportProjection<'a>,
835 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
836 Box::pin(async move {
837 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
838
839 let stdlib_module = path
840 .strip_prefix("std/")
841 .or_else(|| (path == "observability").then_some("observability"));
842 if let Some(module) = stdlib_module {
843 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
844 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
845 if self.imported_paths.contains(&synthetic) {
846 return Ok(());
847 }
848 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
849 return self.apply_import_projection(&synthetic, &loaded, projection);
850 }
851 let loaded = self
852 .load_stdlib_module_from_source(module, synthetic.clone(), source)
853 .await?;
854 if !matches!(projection, ImportProjection::MaterializeOnly) {
855 let _load_span = self.module_load_span();
856 self.apply_import_projection(&synthetic, &loaded, projection)?;
857 }
858 return Ok(());
859 }
860 return Err(VmError::Runtime(format!(
861 "Unknown stdlib module: std/{module}"
862 )));
863 }
864
865 let base = self
866 .source_dir
867 .clone()
868 .unwrap_or_else(|| PathBuf::from("."));
869 let file_path = self.resolve_module_import_path(&base, path)?;
870 let verified_source = if let Some(guard) = &self.package_execution_guard {
871 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
872 VmError::Runtime(format!(
873 "installed package {} rejected: {error}",
874 projection.package_rejection_kind()
875 ))
876 })?;
877 Some(verified_package_source(bytes, &file_path)?)
878 } else {
879 None
880 };
881
882 let canonical = file_path
883 .canonicalize()
884 .unwrap_or_else(|_| file_path.clone());
885 if self.imported_paths.contains(&canonical) {
886 match projection {
894 ImportProjection::BindCaller(selected_names) => {
895 if let Some(importer) = self.imported_paths.last().cloned() {
896 if importer != canonical {
897 self.deferred_cyclic_imports.push(DeferredCyclicImport {
898 importer,
899 target: canonical.clone(),
900 selected_names: selected_names.map(<[String]>::to_vec),
901 namespace_alias: None,
902 });
903 }
904 }
905 }
906 ImportProjection::BindNamespace(alias) => {
907 if let Some(importer) = self.imported_paths.last().cloned() {
908 if importer != canonical {
909 self.deferred_cyclic_imports.push(DeferredCyclicImport {
910 importer,
911 target: canonical.clone(),
912 selected_names: None,
913 namespace_alias: Some(alias.to_string()),
914 });
915 }
916 }
917 }
918 ImportProjection::MaterializeOnly => {}
919 }
920 return Ok(());
921 }
922 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
923 if let Some(source) = &verified_source {
924 let cached_source = self.source_cache.get(&canonical).map(Arc::as_ref);
925 if cached_source != Some(source.as_str()) {
926 return Err(VmError::Runtime(format!(
927 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
928 projection.package_rejection_kind(),
929 canonical.display()
930 )));
931 }
932 let active_guard = self
933 .package_execution_guard
934 .as_deref()
935 .expect("verified package source requires an active guard");
936 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
937 return Err(VmError::Runtime(format!(
938 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
939 projection.package_rejection_kind(),
940 canonical.display()
941 )));
942 }
943 }
944 return self.apply_import_projection(&canonical, &loaded, projection);
945 }
946 self.imported_paths.push(canonical.clone());
947
948 let source = {
949 let _load_span = self.module_load_span();
950 match verified_source {
951 Some(source) => Arc::new(ModuleSource::from_text(source)),
954 None => module_source::read(&file_path).map_err(|e| {
955 VmError::Runtime(format!(
960 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
961 file_path.display(),
962 base.display()
963 ))
964 })?,
965 }
966 };
967 {
968 let source_cache = Arc::make_mut(&mut self.source_cache);
969 source_cache.insert(canonical.clone(), Arc::clone(source.text()));
970 source_cache.insert(file_path.clone(), Arc::clone(source.text()));
971 }
972
973 let prepared = {
974 let _load_span = self.module_load_span();
975 if bytecode_cache::cache_enabled() {
976 self.prepared_module_cache.get(&canonical, &source)
977 } else {
978 None
979 }
980 };
981 let artifact = if let Some(prepared) = prepared {
982 prepared
983 } else {
984 let lookup = {
988 let _load_span = self.module_load_span();
989 bytecode_cache::load_module(&file_path, &source)
990 };
991 let cached = if let Some(artifact) = lookup.artifact {
992 artifact
993 } else {
994 let mut compile_span = self.module_compile_span();
995 let compiled =
996 compile_module_artifact_from_source(&file_path, source.as_str())?;
997 if let Some(span) = &mut compile_span {
998 span.mark_compile_succeeded();
999 }
1000 drop(compile_span);
1001 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
1002 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
1003 eprintln!(
1004 "[harn] module cache write skipped for {}: {err}",
1005 file_path.display()
1006 );
1007 }
1008 }
1009 compiled
1010 };
1011 let mut prepared = {
1012 let _load_span = self.module_load_span();
1013 Arc::new(PreparedModuleArtifact::from_cached(cached))
1014 };
1015 if bytecode_cache::cache_enabled() {
1016 prepared =
1017 self.prepared_module_cache
1018 .insert(canonical.clone(), &source, prepared);
1019 }
1020 prepared
1021 };
1022
1023 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
1024 let loaded = Arc::new(
1025 self.instantiate_module(module_source_dir, artifact.as_ref())
1026 .await?,
1027 );
1028 self.imported_paths.pop();
1029 {
1030 let _load_span = self.module_load_span();
1031 Arc::make_mut(&mut self.module_cache)
1032 .insert(canonical.clone(), Arc::clone(&loaded));
1033 }
1034 self.record_module_loaded();
1035 if !matches!(projection, ImportProjection::MaterializeOnly) {
1036 let _load_span = self.module_load_span();
1037 self.apply_import_projection(&canonical, &loaded, projection)?;
1038 }
1039
1040 if self.imported_paths.is_empty() {
1044 let _load_span = self.module_load_span();
1045 self.flush_deferred_cyclic_imports()?;
1046 }
1047
1048 Ok(())
1049 })
1050 }
1051
1052 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
1061 if self.deferred_cyclic_imports.is_empty() {
1062 return Ok(());
1063 }
1064 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
1065 let mut still_pending = Vec::new();
1066 for import in deferred {
1067 let (Some(importer), Some(target)) = (
1068 self.module_cache.get(&import.importer).cloned(),
1069 self.module_cache.get(&import.target).cloned(),
1070 ) else {
1071 still_pending.push(import);
1075 continue;
1076 };
1077
1078 let mut module_state = importer._module_state.lock();
1079 if let Some(alias) = &import.namespace_alias {
1080 if module_state.get(alias).is_none() {
1081 let dict = build_namespace_dict(&import.target.display().to_string(), &target);
1082 module_state.define(alias, dict, false)?;
1083 }
1084 continue;
1085 }
1086
1087 let export_names = module_import_names(
1088 &import.target.display().to_string(),
1089 &target,
1090 import.selected_names.as_deref(),
1091 )?;
1092
1093 for name in export_names {
1094 if module_state.get(&name).is_some() {
1097 continue;
1098 }
1099 if let Some(closure) = target.functions.get(&name) {
1100 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
1101 } else if let Some(value) = target.public_values.get(&name) {
1102 module_state.define(&name, value.clone(), false)?;
1104 } else if target
1105 .public_exports
1106 .get(&name)
1107 .is_some_and(|kind| !kind.has_runtime_value())
1108 {
1109 continue;
1111 } else {
1112 return Err(VmError::Runtime(format!(
1113 "Import error: '{name}' is not defined in {}",
1114 import.target.display()
1115 )));
1116 }
1117 }
1118 }
1119 self.deferred_cyclic_imports = still_pending;
1120 Ok(())
1121 }
1122
1123 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1128 if let Some(module) = path
1129 .strip_prefix("std/")
1130 .or_else(|| (path == "observability").then_some("observability"))
1131 {
1132 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1133 }
1134 let base = self
1135 .source_dir
1136 .clone()
1137 .unwrap_or_else(|| PathBuf::from("."));
1138 let file_path = self.resolve_module_import_path(&base, path)?;
1139 Ok(file_path.canonicalize().unwrap_or(file_path))
1140 }
1141
1142 async fn loaded_module_for_path(
1143 &mut self,
1144 path: &Path,
1145 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1146 self.ensure_execution_available()?;
1147 let path_str = path.to_string_lossy().into_owned();
1148 self.materialize_import(&path_str).await?;
1149
1150 let mut file_path = if path.is_absolute() {
1151 path.to_path_buf()
1152 } else {
1153 self.source_dir
1154 .clone()
1155 .unwrap_or_else(|| PathBuf::from("."))
1156 .join(path)
1157 };
1158 if !file_path.exists() && file_path.extension().is_none() {
1159 file_path.set_extension("harn");
1160 }
1161
1162 let canonical = file_path
1163 .canonicalize()
1164 .unwrap_or_else(|_| file_path.clone());
1165 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1166 VmError::Runtime(format!(
1167 "Import error: failed to cache loaded module '{}'",
1168 canonical.display()
1169 ))
1170 })?;
1171 Ok((canonical, loaded))
1172 }
1173
1174 pub async fn load_public_module_callable(
1176 &mut self,
1177 path: &Path,
1178 name: &str,
1179 ) -> Result<Arc<VmClosure>, VmError> {
1180 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1181 if !loaded.public_exports.contains_key(name) {
1182 let hint = if loaded.functions.contains_key(name) {
1183 "; it is defined there but not `pub`"
1184 } else {
1185 ""
1186 };
1187 return Err(VmError::Runtime(format!(
1188 "callable '{name}' is not exported by module '{}'{hint}",
1189 canonical.display()
1190 )));
1191 }
1192 loaded.functions.get(name).cloned().ok_or_else(|| {
1193 VmError::Runtime(format!(
1194 "Import error: exported callable '{name}' is missing from {}",
1195 canonical.display()
1196 ))
1197 })
1198 }
1199
1200 pub async fn load_module_exports(
1203 &mut self,
1204 path: &Path,
1205 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1206 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1207 exported_function_closures(&loaded, &canonical)
1208 }
1209
1210 pub async fn load_module_exports_from_source(
1213 &mut self,
1214 source_key: impl Into<PathBuf>,
1215 source: &str,
1216 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1217 self.ensure_execution_available()?;
1218 let synthetic = source_key.into();
1219 let loaded = self
1220 .load_module_from_source(synthetic.clone(), source)
1221 .await?;
1222 exported_function_closures(&loaded, &synthetic)
1223 }
1224
1225 pub async fn load_module_callable_from_source(
1230 &mut self,
1231 source_key: impl Into<PathBuf>,
1232 source: &str,
1233 name: &str,
1234 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1235 self.ensure_execution_available()?;
1236 let synthetic = source_key.into();
1237 let loaded = self.load_module_from_source(synthetic, source).await?;
1238 Ok(loaded.functions.get(name).cloned())
1239 }
1240
1241 pub async fn load_module_exports_from_import(
1245 &mut self,
1246 import_path: &str,
1247 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1248 self.ensure_execution_available()?;
1249 self.materialize_import(import_path).await?;
1250
1251 if let Some(module) = import_path
1252 .strip_prefix("std/")
1253 .or_else(|| (import_path == "observability").then_some("observability"))
1254 {
1255 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1256 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1257 VmError::Runtime(format!(
1258 "Import error: failed to cache loaded module '{}'",
1259 synthetic.display()
1260 ))
1261 })?;
1262 return exported_function_closures(&loaded, &synthetic);
1263 }
1264
1265 let base = self
1266 .source_dir
1267 .clone()
1268 .unwrap_or_else(|| PathBuf::from("."));
1269 let file_path = self.resolve_module_import_path(&base, import_path)?;
1270 self.load_module_exports(&file_path).await
1271 }
1272}
1273
1274#[cfg(test)]
1275#[path = "modules_tests.rs"]
1276mod tests;