1use std::collections::{BTreeMap, HashSet};
13use std::path::{Path, PathBuf};
14use std::sync::{Mutex, OnceLock};
15
16use harn_modules::{public_declarations, DefKind};
17use serde::{Deserialize, Serialize};
18
19use crate::chunk::{CachedChunk, CachedCompiledFunction};
20use crate::value::VmError;
21
22type ImportedEnumCache = BTreeMap<PathBuf, ([u8; 32], Vec<String>)>;
23
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
31pub enum ModuleProvenance {
32 #[default]
33 User,
34 PrivilegedWire,
35 TrustedHostDispatch,
40}
41
42fn imported_enum_cache() -> &'static Mutex<ImportedEnumCache> {
43 static CACHE: OnceLock<Mutex<ImportedEnumCache>> = OnceLock::new();
44 CACHE.get_or_init(|| Mutex::new(BTreeMap::new()))
45}
46
47#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct ModuleImportSpec {
52 pub path: String,
53 pub binding: ModuleImportBinding,
54 pub is_pub: bool,
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize)]
59pub enum ModuleImportBinding {
60 Wildcard,
61 Selected(Vec<String>),
62 Namespace {
63 alias: String,
64 demand: harn_parser::NamespaceDemand,
65 },
66}
67
68#[derive(Clone, Debug, Serialize, Deserialize)]
74pub struct ModuleArtifact {
75 #[serde(default)]
76 pub provenance: ModuleProvenance,
77 pub imports: Vec<ModuleImportSpec>,
78 pub type_schema_init_chunks: Vec<CachedChunk>,
81 pub init_chunk: Option<CachedChunk>,
82 pub functions: BTreeMap<String, CachedCompiledFunction>,
83 pub public_exports: BTreeMap<String, DefKind>,
88 pub public_value_names: HashSet<String>,
92 pub public_type_names: HashSet<String>,
97}
98
99pub fn specialize_module_artifact(
106 program: &[harn_parser::SNode],
107 source_file: Option<String>,
108 mut artifact: ModuleArtifact,
109 demand: &harn_modules::ExportDemand,
110) -> Result<ModuleArtifact, VmError> {
111 use harn_parser::Node;
112 use std::collections::{BTreeSet, HashMap};
113
114 if matches!(demand, harn_modules::ExportDemand::WholeNamespace) {
115 return Ok(artifact);
116 }
117
118 if artifact.imports.iter().any(|import| import.is_pub) {
123 return Ok(artifact);
124 }
125
126 let callable_names = artifact.functions.keys().cloned().collect::<HashSet<_>>();
127 let mut declarations = HashMap::<String, &harn_parser::SNode>::new();
128 for node in program {
129 let inner = match &node.node {
130 Node::AttributedDecl { inner, .. } => inner.as_ref(),
131 _ => node,
132 };
133 let name = match &inner.node {
134 Node::FnDecl { name, .. }
135 | Node::Pipeline { name, .. }
136 | Node::StructDecl { name, .. } => Some(name),
137 _ => None,
138 };
139 if let Some(name) = name {
140 declarations.insert(name.clone(), inner);
141 }
142 }
143
144 let mut pending = Vec::new();
145 if let harn_modules::ExportDemand::Members(members) = demand {
146 pending.extend(
147 members
148 .iter()
149 .filter(|name| callable_names.contains(*name))
150 .cloned(),
151 );
152 }
153 for node in program {
155 let inner = match &node.node {
156 Node::AttributedDecl { inner, .. } => inner.as_ref(),
157 _ => node,
158 };
159 if matches!(
160 &inner.node,
161 Node::LetBinding { .. }
162 | Node::ConstBinding { .. }
163 | Node::EnumDecl { is_pub: true, .. }
164 | Node::ToolDecl { .. }
165 | Node::SkillDecl { .. }
166 | Node::EvalPackDecl { .. }
167 ) {
168 collect_callable_references(inner, &callable_names, &mut pending);
169 }
170 }
171
172 let mut retained = HashSet::new();
173 while let Some(name) = pending.pop() {
174 if !retained.insert(name.clone()) {
175 continue;
176 }
177 if let Some(declaration) = declarations.get(&name) {
178 collect_callable_references(declaration, &callable_names, &mut pending);
179 }
180 }
181 artifact.functions.retain(|name, _| retained.contains(name));
182 artifact
183 .public_exports
184 .retain(|name, _| demand.contains(name));
185 artifact
186 .public_value_names
187 .retain(|name| demand.contains(name));
188 artifact
189 .public_type_names
190 .retain(|name| demand.contains(name));
191
192 let selected_type_names = artifact
193 .public_type_names
194 .iter()
195 .cloned()
196 .collect::<BTreeSet<_>>();
197 artifact.type_schema_init_chunks =
198 crate::Compiler::compile_selected_public_type_schema_initializers(
199 program,
200 source_file,
201 Some(&selected_type_names),
202 )
203 .map_err(|error| VmError::Runtime(format!("Import schema compile error: {error}")))?
204 .into_iter()
205 .map(|chunk| chunk.freeze_for_cache())
206 .collect();
207 Ok(artifact)
208}
209
210fn collect_callable_references(
211 node: &harn_parser::SNode,
212 callable_names: &HashSet<String>,
213 out: &mut Vec<String>,
214) {
215 use harn_parser::Node;
216 let referenced = match &node.node {
217 Node::Identifier(name)
218 | Node::FunctionCall { name, .. }
219 | Node::StructConstruct {
220 struct_name: name, ..
221 }
222 | Node::EnumConstruct {
223 enum_name: name, ..
224 } => Some(name),
225 _ => None,
226 };
227 if let Some(name) = referenced.filter(|name| callable_names.contains(*name)) {
228 out.push(name.clone());
229 }
230 for child in harn_parser::visit::immediate_children(node) {
231 collect_callable_references(child, callable_names, out);
232 }
233}
234
235impl ModuleArtifact {
236 pub(crate) fn bind_source_file(&mut self, source_path: &Path) {
243 let source_file = source_path.display().to_string();
244 for chunk in &mut self.type_schema_init_chunks {
245 bind_chunk_source_file(chunk, &source_file);
246 }
247 if let Some(chunk) = &mut self.init_chunk {
248 bind_chunk_source_file(chunk, &source_file);
249 }
250 for function in self.functions.values_mut() {
251 bind_chunk_source_file(&mut function.chunk, &source_file);
252 }
253 }
254}
255
256fn bind_chunk_source_file(chunk: &mut CachedChunk, source_file: &str) {
257 chunk.source_file = Some(source_file.to_string());
258 for function in &mut chunk.functions {
259 bind_chunk_source_file(&mut function.chunk, source_file);
260 }
261}
262
263pub fn compile_module_artifact(
268 program: &[harn_parser::SNode],
269 module_source_file: Option<String>,
270) -> Result<ModuleArtifact, VmError> {
271 let imported_enum_candidates = module_source_file
272 .as_deref()
273 .filter(|_| needs_imported_enum_candidates(program))
274 .and_then(|path| {
275 harn_modules::build(&[Path::new(path).to_path_buf()])
276 .imported_names_by_kind_for_file(Path::new(path), DefKind::Enum)
277 })
278 .unwrap_or_default();
279 compile_module_artifact_with_imported_enums(
280 program,
281 module_source_file,
282 &imported_enum_candidates.into_iter().collect::<Vec<_>>(),
283 )
284}
285
286fn compile_module_artifact_with_imported_enums(
287 program: &[harn_parser::SNode],
288 module_source_file: Option<String>,
289 imported_enum_candidates: &[String],
290) -> Result<ModuleArtifact, VmError> {
291 compile_module_artifact_with_provenance(
292 program,
293 module_source_file,
294 imported_enum_candidates,
295 ModuleProvenance::User,
296 )
297}
298
299fn compile_module_artifact_with_provenance(
300 program: &[harn_parser::SNode],
301 module_source_file: Option<String>,
302 imported_enum_candidates: &[String],
303 provenance: ModuleProvenance,
304) -> Result<ModuleArtifact, VmError> {
305 let namespace_demands = harn_parser::namespace_import_demands(program);
306 let imports: Vec<ModuleImportSpec> = program
307 .iter()
308 .filter_map(|node| match &node.node {
309 harn_parser::Node::ImportDecl { path, is_pub } => Some(ModuleImportSpec {
310 path: path.clone(),
311 binding: ModuleImportBinding::Wildcard,
312 is_pub: *is_pub,
313 }),
314 harn_parser::Node::SelectiveImport {
315 names,
316 path,
317 is_pub,
318 } => Some(ModuleImportSpec {
319 path: path.clone(),
320 binding: ModuleImportBinding::Selected(names.clone()),
321 is_pub: *is_pub,
322 }),
323 harn_parser::Node::NamespaceImport {
324 alias,
325 path,
326 is_pub,
327 } => Some(ModuleImportSpec {
328 path: path.clone(),
329 binding: ModuleImportBinding::Namespace {
330 alias: alias.clone(),
331 demand: namespace_demands
332 .get(alias)
333 .cloned()
334 .unwrap_or(harn_parser::NamespaceDemand::Whole),
335 },
336 is_pub: *is_pub,
337 }),
338 _ => None,
339 })
340 .collect();
341
342 if provenance == ModuleProvenance::PrivilegedWire {
343 validate_privileged_wire_surface(program, &imports)?;
344 }
345
346 let compiler = || match provenance {
347 ModuleProvenance::User => crate::Compiler::new(),
348 ModuleProvenance::PrivilegedWire => {
349 crate::Compiler::with_options(crate::CompilerOptions::privileged_wire())
350 }
351 ModuleProvenance::TrustedHostDispatch => {
352 crate::Compiler::with_options(crate::CompilerOptions::privileged_wire())
353 }
354 };
355
356 let init_nodes: Vec<harn_parser::SNode> = program
357 .iter()
358 .filter(|sn| {
359 let inner = match &sn.node {
360 harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
361 _ => sn,
362 };
363 matches!(
364 &inner.node,
365 harn_parser::Node::LetBinding { .. }
366 | harn_parser::Node::ConstBinding { .. }
367 | harn_parser::Node::EnumDecl { is_pub: true, .. }
374 | harn_parser::Node::ToolDecl { .. }
375 | harn_parser::Node::SkillDecl { .. }
376 | harn_parser::Node::EvalPackDecl { .. }
377 )
378 })
379 .cloned()
380 .collect();
381 let init_chunk = if init_nodes.is_empty() {
382 None
383 } else {
384 let compiler = compiler();
385 Some(
386 compiler
387 .compile_module_init(program, &init_nodes, imported_enum_candidates)
388 .map_err(|e| VmError::Runtime(format!("Import init compile error: {e}")))?
389 .freeze_for_cache(),
390 )
391 };
392
393 let public_exports: BTreeMap<String, DefKind> = program
394 .iter()
395 .flat_map(public_declarations)
396 .map(|export| (export.name, export.kind))
397 .collect();
398 let public_value_names = public_exports
399 .iter()
400 .filter(|(_, kind)| {
401 matches!(
402 kind,
403 DefKind::Variable
404 | DefKind::Enum
405 | DefKind::Tool
406 | DefKind::Skill
407 | DefKind::EvalPack
408 )
409 })
410 .map(|(name, _)| name.clone())
411 .collect();
412 let public_type_names = public_exports
413 .iter()
414 .filter(|(_, kind)| !kind.has_runtime_value())
415 .map(|(name, _)| name.clone())
416 .collect();
417
418 let mut functions = BTreeMap::new();
419 for node in program {
420 let inner = match &node.node {
421 harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
422 _ => node,
423 };
424 if let harn_parser::Node::StructDecl { name, fields, .. } = &inner.node {
425 let constructor = compiler()
430 .compile_struct_constructor(name, fields)
431 .map_err(|error| VmError::Runtime(format!("Import compile error: {error}")))?;
432 functions.insert(name.clone(), constructor.freeze_for_cache());
433 continue;
434 }
435 if let harn_parser::Node::Pipeline {
436 name,
437 params,
438 body,
439 extends,
440 ..
441 } = &inner.node
442 {
443 let mut compiler = compiler();
444 compiler.add_imported_enum_candidates(imported_enum_candidates.iter().cloned());
445 let pipeline = compiler
446 .compile_pipeline_callable(program, name, params, body, extends.as_deref())
447 .map_err(|error| VmError::Runtime(format!("Import compile error: {error}")))?;
448 functions.insert(name.clone(), pipeline.freeze_for_cache());
449 continue;
450 }
451 let harn_parser::Node::FnDecl {
452 name,
453 type_params,
454 params,
455 body,
456 ..
457 } = &inner.node
458 else {
459 continue;
460 };
461
462 let mut compiler = compiler();
463 compiler.add_imported_enum_candidates(imported_enum_candidates.iter().cloned());
464 compiler.prepare_module_context(program);
465 let func_chunk = compiler
466 .compile_fn_body(type_params, params, body, module_source_file.clone())
467 .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
468 functions.insert(name.clone(), func_chunk.freeze_for_cache());
469 }
470
471 let type_schema_init_chunks =
472 crate::Compiler::compile_public_type_schema_initializers(program, module_source_file)
473 .map_err(|error| VmError::Runtime(format!("Import schema compile error: {error}")))?
474 .into_iter()
475 .map(|chunk| chunk.freeze_for_cache())
476 .collect();
477
478 Ok(ModuleArtifact {
479 provenance,
480 imports,
481 type_schema_init_chunks,
482 init_chunk,
483 functions,
484 public_exports,
485 public_value_names,
486 public_type_names,
487 })
488}
489
490fn validate_privileged_wire_surface(
491 program: &[harn_parser::SNode],
492 imports: &[ModuleImportSpec],
493) -> Result<(), VmError> {
494 if imports.iter().any(|import| import.is_pub) {
495 return Err(VmError::Runtime(
496 "Privileged wire modules cannot re-export imports".to_string(),
497 ));
498 }
499 for export in program.iter().flat_map(public_declarations) {
500 if export.kind.has_runtime_value() && export.kind != DefKind::Variable {
501 return Err(VmError::Runtime(format!(
502 "Privileged wire module export `{}` is a {:?}; only explicit capability-value bindings may cross the wire boundary",
503 export.name, export.kind
504 )));
505 }
506 }
507 Ok(())
508}
509
510pub fn compile_module_artifact_from_source(
514 source_path: &Path,
515 source: &str,
516) -> Result<ModuleArtifact, VmError> {
517 let program = parse_module_source(source_path, source)?;
518 let imported_enum_candidates =
519 imported_enum_candidates_for_program(source_path, source, &program);
520 compile_module_artifact_with_imported_enums(
521 &program,
522 Some(source_path.display().to_string()),
523 &imported_enum_candidates,
524 )
525}
526
527pub fn compile_privileged_wire_module_artifact_from_source(
536 source_path: &Path,
537 source: &str,
538) -> Result<ModuleArtifact, VmError> {
539 let program = parse_module_source(source_path, source)?;
540 let imported_enum_candidates =
541 imported_enum_candidates_for_program(source_path, source, &program);
542 compile_module_artifact_with_provenance(
543 &program,
544 Some(source_path.display().to_string()),
545 &imported_enum_candidates,
546 ModuleProvenance::PrivilegedWire,
547 )
548}
549
550pub fn compile_trusted_host_dispatch_module_artifact_from_source(
555 source_path: &Path,
556 source: &str,
557) -> Result<ModuleArtifact, VmError> {
558 let program = parse_module_source(source_path, source)?;
559 let imported_enum_candidates =
560 imported_enum_candidates_for_program(source_path, source, &program);
561 compile_module_artifact_with_provenance(
562 &program,
563 Some(source_path.display().to_string()),
564 &imported_enum_candidates,
565 ModuleProvenance::TrustedHostDispatch,
566 )
567}
568
569pub fn compile_trusted_host_dispatch_module_artifact_from_source_with_imported_enums(
574 source_path: &Path,
575 source: &str,
576 imported_enum_candidates: impl IntoIterator<Item = String>,
577) -> Result<ModuleArtifact, VmError> {
578 let program = parse_module_source(source_path, source)?;
579 let imported_enum_candidates = imported_enum_candidates.into_iter().collect::<Vec<_>>();
580 compile_module_artifact_with_provenance(
581 &program,
582 Some(source_path.display().to_string()),
583 &imported_enum_candidates,
584 ModuleProvenance::TrustedHostDispatch,
585 )
586}
587
588fn imported_enum_candidates_for_program(
593 source_path: &Path,
594 source: &str,
595 program: &[harn_parser::SNode],
596) -> Vec<String> {
597 if !needs_imported_enum_candidates(program) {
598 return Vec::new();
599 }
600 let source_hash = *blake3::hash(source.as_bytes()).as_bytes();
601 let cache_key = harn_modules::canonical_path(source_path);
602 let cacheable = is_immutable_stdlib_path(source_path);
603 if cacheable {
604 if let Some((_cached_hash, candidates)) = imported_enum_cache()
605 .lock()
606 .expect("imported enum cache lock poisoned")
607 .get(&cache_key)
608 .filter(|(cached_hash, _)| *cached_hash == source_hash)
609 {
610 return candidates.clone();
611 }
612 }
613
614 let graph = harn_modules::build_with_source(source_path, source);
619 if !cacheable {
620 return sorted_imported_enum_candidates(&graph, source_path);
621 }
622 let mut projections = Vec::new();
623 for path in graph.module_paths() {
624 let module_source = if path == cache_key {
625 Some(source.to_string())
626 } else {
627 harn_modules::read_module_source(&path).or_else(|| std::fs::read_to_string(&path).ok())
628 };
629 let Some(module_source) = module_source else {
630 continue;
631 };
632 let candidates = sorted_imported_enum_candidates(&graph, &path);
633 projections.push((
634 path,
635 (
636 *blake3::hash(module_source.as_bytes()).as_bytes(),
637 candidates,
638 ),
639 ));
640 }
641 let mut cache = imported_enum_cache()
642 .lock()
643 .expect("imported enum cache lock poisoned");
644 for (path, projection) in projections {
645 if is_immutable_stdlib_path(&path) {
646 cache.insert(path, projection);
647 }
648 }
649 cache
650 .get(&cache_key)
651 .filter(|(cached_hash, _)| *cached_hash == source_hash)
652 .map(|(_, candidates)| candidates.clone())
653 .unwrap_or_default()
654}
655
656fn sorted_imported_enum_candidates(
657 graph: &harn_modules::ModuleGraph,
658 source_path: &Path,
659) -> Vec<String> {
660 let mut candidates = graph
661 .imported_names_by_kind_for_file(source_path, DefKind::Enum)
662 .unwrap_or_default()
663 .into_iter()
664 .collect::<Vec<_>>();
665 candidates.sort_unstable();
666 candidates
667}
668
669fn is_immutable_stdlib_path(path: &Path) -> bool {
670 path.to_str()
671 .is_some_and(|path| path.starts_with("<stdlib>/") || path.starts_with("<std>/"))
672}
673
674fn needs_imported_enum_candidates(program: &[harn_parser::SNode]) -> bool {
675 harn_parser::visit::contains_identifier_enum_pattern(program)
676}
677
678fn parse_module_source(
679 source_path: &Path,
680 source: &str,
681) -> Result<Vec<harn_parser::SNode>, VmError> {
682 let mut lexer = harn_lexer::Lexer::new(source);
683 let tokens = lexer.tokenize().map_err(|e| {
684 VmError::Runtime(format!(
685 "Import lex error in {}: {e}",
686 source_path.display()
687 ))
688 })?;
689 let mut parser = harn_parser::Parser::new(tokens);
690 parser.parse().map_err(|e| {
691 VmError::Runtime(format!(
692 "Import parse error in {}: {e}",
693 source_path.display()
694 ))
695 })
696}
697
698pub fn compile_module_artifact_from_source_with_imported_enums(
703 source_path: &Path,
704 source: &str,
705 imported_enum_candidates: impl IntoIterator<Item = String>,
706) -> Result<ModuleArtifact, VmError> {
707 let program = parse_module_source(source_path, source)?;
708 let imported_enum_candidates = imported_enum_candidates.into_iter().collect::<Vec<_>>();
709 compile_module_artifact_with_imported_enums(
710 &program,
711 Some(source_path.display().to_string()),
712 &imported_enum_candidates,
713 )
714}
715
716#[cfg(test)]
717mod tests {
718 use std::path::Path;
719
720 use harn_lexer::Lexer;
721 use harn_parser::Parser;
722
723 use super::{
724 compile_module_artifact, compile_module_artifact_from_source,
725 compile_privileged_wire_module_artifact_from_source, needs_imported_enum_candidates,
726 parse_module_source, ModuleImportBinding, ModuleProvenance,
727 };
728 use crate::chunk::Constant;
729
730 #[test]
731 fn module_init_schema_of_uses_full_program_aliases() {
732 let source = r"
733pub type Item = {id: string}
734const ITEM_SCHEMA: Schema<Item> = schema_of(Item)
735";
736 let mut lexer = Lexer::new(source);
737 let tokens = lexer.tokenize().unwrap();
738 let mut parser = Parser::new(tokens);
739 let program = parser.parse().unwrap();
740 let artifact = compile_module_artifact(&program, None).unwrap();
741 let constants = &artifact.init_chunk.expect("init chunk").constants;
742 let strings = constants
743 .iter()
744 .filter_map(|constant| match constant {
745 Constant::String(value) => Some(value.as_str()),
746 _ => None,
747 })
748 .collect::<Vec<_>>();
749 assert!(strings.contains(&"id"), "{strings:?}");
750 assert!(!strings.contains(&"Item"), "{strings:?}");
751 }
752
753 #[test]
754 fn type_only_modules_use_a_separate_schema_initializer() {
755 let source = r"
756pub type UserShape = {name: string, active?: bool}
757pub type UserList = list<UserShape>
758";
759
760 let artifact =
761 compile_module_artifact_from_source(Path::new("<test>/schemas.harn"), source)
762 .expect("module compiles");
763
764 assert!(
765 artifact.init_chunk.is_none(),
766 "erased type aliases must not inflate module init bytecode"
767 );
768 assert!(artifact.public_type_names.contains("UserShape"));
769 assert!(artifact.public_type_names.contains("UserList"));
770 assert_eq!(artifact.type_schema_init_chunks.len(), 2);
771 }
772
773 #[test]
774 fn specialization_prunes_dead_pipeline_struct_and_enum_exports() {
775 let source = r#"
776pub enum KeptStatus { Ready }
777pub enum DeadStatus { Gone }
778pub struct KeptConfig { value: int }
779pub struct DeadConfig { value: string }
780pub pipeline kept_pipeline(harness: Harness) { return KeptConfig({value: 7}) }
781pub pipeline dead_pipeline(harness: Harness) { return DeadConfig({value: "dead"}) }
782"#;
783 let source_path = Path::new("<test>/declarations.harn");
784 let parsed = parse_module_source(source_path, source).expect("module parses");
785 let full = compile_module_artifact(&parsed, Some(source_path.display().to_string()))
786 .expect("module compiles");
787 let selected = super::specialize_module_artifact(
788 &parsed,
789 Some(source_path.display().to_string()),
790 full,
791 &harn_modules::ExportDemand::Members(std::collections::BTreeSet::from([
792 "KeptStatus".to_string(),
793 "KeptConfig".to_string(),
794 "kept_pipeline".to_string(),
795 ])),
796 )
797 .expect("specialization succeeds");
798
799 assert!(selected.public_exports.contains_key("KeptStatus"));
800 assert!(selected.public_exports.contains_key("KeptConfig"));
801 assert!(selected.public_exports.contains_key("kept_pipeline"));
802 assert!(!selected.public_exports.contains_key("DeadStatus"));
803 assert!(!selected.public_exports.contains_key("DeadConfig"));
804 assert!(!selected.public_exports.contains_key("dead_pipeline"));
805 assert!(selected.functions.contains_key("KeptConfig"));
806 assert!(selected.functions.contains_key("kept_pipeline"));
807 assert!(!selected.functions.contains_key("DeadConfig"));
808 assert!(!selected.functions.contains_key("dead_pipeline"));
809 }
810
811 #[test]
812 fn nested_namespace_import_retains_static_member_demand() {
813 let artifact = compile_module_artifact_from_source(
814 Path::new("<test>/wrapper.harn"),
815 r#"
816import * as lib from "./lib"
817pub fn call() { return lib.greet() }
818"#,
819 )
820 .expect("module compiles");
821
822 let ModuleImportBinding::Namespace { alias, demand } = &artifact.imports[0].binding else {
823 panic!("expected namespace import metadata");
824 };
825 assert_eq!(alias, "lib");
826 assert_eq!(
827 demand,
828 &harn_parser::NamespaceDemand::Members(std::collections::BTreeSet::from([
829 "greet".to_string(),
830 ]))
831 );
832 }
833
834 #[test]
835 fn ordinary_modules_cannot_name_privileged_wire_builtins() {
836 let error = compile_module_artifact_from_source(
837 Path::new("<test>/user.harn"),
838 r#"fn probe() { host_call("project.scan", {}) }"#,
839 )
840 .expect_err("ordinary source must not acquire wire authority");
841 assert!(
842 error.to_string().contains("not callable source API"),
843 "{error}"
844 );
845 }
846
847 #[test]
848 fn explicit_privileged_compilation_stamps_private_wire_code() {
849 let artifact = compile_privileged_wire_module_artifact_from_source(
850 Path::new("<trusted>/wire.harn"),
851 r#"fn probe() { host_call("project.scan", {}) }"#,
852 )
853 .expect("trusted private wire function compiles");
854 assert_eq!(artifact.provenance, ModuleProvenance::PrivilegedWire);
855 assert!(artifact.functions.contains_key("probe"));
856 assert!(artifact.public_exports.is_empty());
857 }
858
859 #[test]
860 fn privileged_wire_functions_cannot_cross_the_module_boundary() {
861 let error = compile_privileged_wire_module_artifact_from_source(
862 Path::new("<trusted>/wire.harn"),
863 r#"pub fn probe() { host_call("project.scan", {}) }"#,
864 )
865 .expect_err("wire closures must not be exportable");
866 assert!(
867 error
868 .to_string()
869 .contains("only explicit capability-value bindings"),
870 "{error}"
871 );
872 }
873
874 #[test]
875 fn privileged_wire_modules_cannot_reexport_imports() {
876 let error = compile_privileged_wire_module_artifact_from_source(
877 Path::new("<trusted>/wire.harn"),
878 r#"pub import { probe } from "./other""#,
879 )
880 .expect_err("wire authority must be non-reexportable");
881 assert!(
882 error.to_string().contains("cannot re-export imports"),
883 "{error}"
884 );
885 }
886
887 #[test]
888 fn schema_initializer_keeps_imported_alias_lookup_and_source() {
889 let source = r#"
890import { External } from "./external"
891pub type Wrapped = {value: External}
892"#;
893 let source_path = Path::new("<test>/wrapped.harn");
894 let artifact =
895 compile_module_artifact_from_source(source_path, source).expect("module compiles");
896 let chunk = artifact
897 .type_schema_init_chunks
898 .into_iter()
899 .next()
900 .expect("schema initializer");
901 assert_eq!(chunk.source_file.as_deref(), Some("<test>/wrapped.harn"));
902 assert!(chunk
903 .constants
904 .iter()
905 .any(|constant| matches!(constant, Constant::String(value) if value == "External")));
906 }
907
908 #[test]
909 fn imported_enum_graph_lookup_is_lazy_for_plain_modules() {
910 let plain = parse_module_source(
911 Path::new("<test>/plain.harn"),
912 r#"
913import { helper } from "./support"
914pub fn run() -> int { return helper(1) }
915"#,
916 )
917 .expect("plain module parses");
918 assert!(!needs_imported_enum_candidates(&plain));
919
920 let qualified = parse_module_source(
921 Path::new("<test>/qualified.harn"),
922 r#"
923import { Status } from "./status"
924pub fn run(value: Status) {
925 match value {
926 Status.Ready -> { return 1 }
927 _ -> { return 0 }
928 }
929}
930"#,
931 )
932 .expect("qualified module parses");
933 assert!(needs_imported_enum_candidates(&qualified));
934 }
935
936 #[test]
937 fn private_declarations_do_not_expand_module_init() {
938 let artifact = compile_module_artifact_from_source(
939 Path::new("<test>/private-declarations.harn"),
940 r"
941enum PrivateStatus { Ready }
942struct PrivateConfig { value: int }
943pub fn run() { return PrivateStatus.Ready }
944",
945 )
946 .expect("private declarations compile");
947
948 assert!(artifact.init_chunk.is_none());
949 assert!(artifact.functions.contains_key("PrivateConfig"));
950 assert!(!artifact.public_exports.contains_key("PrivateStatus"));
951 assert!(!artifact.public_exports.contains_key("PrivateConfig"));
952 }
953}