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