1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 string::{String, ToString},
4 sync::Arc,
5 vec::Vec,
6};
7
8use miden_assembly_syntax::{
9 Path, PathBuf,
10 ast::{GlobalItemIndex, ImportKind, ItemIndex, ModuleIndex, SymbolResolutionError, Visibility},
11 debuginfo::{SourceManager, SourceSpan, Span, Spanned},
12 diagnostics::RelatedLabel,
13};
14
15use super::{Linker, LinkerError, ModuleSource};
16
17#[derive(Debug, Clone)]
22pub struct NamespaceGraph {
23 modules: Vec<ModuleNode>,
24 modules_by_path: BTreeMap<Arc<Path>, ModuleIndex>,
25}
26
27#[derive(Debug, Clone)]
29pub struct ModuleNode {
30 id: ModuleIndex,
31 path: Arc<Path>,
32 source: ModuleSource,
33 parent: Option<ModuleIndex>,
34 items: BTreeMap<String, ItemDef>,
35 submodules: BTreeMap<String, ModuleEdge>,
36 imports: BTreeMap<String, UseDecl>,
37}
38
39#[derive(Debug, Clone)]
41pub struct ModuleEdge {
42 name: String,
43 child: ModuleIndex,
44 visibility: Visibility,
45 span: SourceSpan,
46}
47
48#[derive(Debug, Clone)]
50pub struct ItemDef {
51 id: GlobalItemIndex,
52 visibility: Visibility,
53 span: SourceSpan,
54}
55
56#[derive(Debug, Clone)]
58pub struct UseDecl {
59 owner: ModuleIndex,
60 alias: String,
61 kind: ImportKind,
62 visibility: Visibility,
63 target: Span<Arc<Path>>,
64 span: SourceSpan,
65}
66
67#[derive(Debug, Copy, Clone, PartialEq, Eq)]
69pub enum ResolvedUse {
70 Module(ModuleIndex),
71 Item(GlobalItemIndex),
72}
73
74#[derive(Debug, Default, Clone)]
76pub struct ResolvedImports {
77 imports: BTreeMap<(ModuleIndex, String), ResolvedUse>,
78}
79
80impl ResolvedImports {
81 #[inline]
82 pub fn get(&self, owner: ModuleIndex, alias: &str) -> Option<ResolvedUse> {
83 self.imports.get(&(owner, alias.to_string())).copied()
84 }
85}
86
87impl NamespaceGraph {
88 pub fn build(linker: &Linker) -> Result<Self, LinkerError> {
90 let mut modules_by_path = BTreeMap::new();
91
92 for module in linker.modules.iter() {
93 if modules_by_path.insert(module.path().clone(), module.id()).is_some() {
94 return Err(LinkerError::DuplicateModule { path: module.path().clone() });
95 }
96 }
97
98 let mut graph = Self {
99 modules: linker
100 .modules
101 .iter()
102 .map(|module| ModuleNode::from_link_module(module, linker))
103 .collect::<Result<Vec<_>, _>>()?,
104 modules_by_path,
105 };
106 graph.connect_submodule_edges(linker)?;
107 graph.validate_source_module_declarations()?;
108 Ok(graph)
109 }
110
111 #[inline]
113 pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
114 self.modules_by_path.get(path).copied()
115 }
116
117 #[inline]
119 pub fn module(&self, id: ModuleIndex) -> &ModuleNode {
120 &self.modules[id.as_usize()]
121 }
122
123 #[cfg(test)]
124 fn num_modules(&self) -> usize {
125 self.modules.len()
126 }
127
128 pub fn reachable_from_root(&self, root: ModuleIndex) -> Vec<ModuleIndex> {
130 let mut reachable = BTreeSet::new();
131 let mut stack = vec![root];
132
133 while let Some(module_index) = stack.pop() {
134 if !reachable.insert(module_index) {
135 continue;
136 }
137
138 for edge in self.module(module_index).submodules.values() {
139 if edge.visibility.is_public() {
140 stack.push(edge.child);
141 }
142 }
143 }
144
145 reachable.into_iter().collect()
146 }
147
148 pub fn resolve_imports(&self, linker: &Linker) -> Result<ResolvedImports, LinkerError> {
150 let mut imports = ResolvedImports::default();
151 let mut pending = self
152 .modules
153 .iter()
154 .flat_map(|module| module.imports.values())
155 .collect::<Vec<_>>();
156
157 while !pending.is_empty() {
158 let mut next = Vec::new();
159 let mut progress = false;
160
161 for import in pending {
162 if matches!(import.kind(), ImportKind::Module) && import.visibility().is_public() {
163 return Err(LinkerError::ModuleReExport {
164 span: import.span(),
165 source_file: source_file(linker.source_manager.as_ref(), import.span()),
166 path: import.target().inner().clone(),
167 });
168 }
169
170 let resolved = match self.resolve_import_decl(import, &imports, linker) {
171 Ok(resolved) => resolved,
172 Err(err @ LinkerError::UndefinedSymbol { .. }) => {
173 next.push(import);
174 let _ = err;
175 continue;
176 },
177 Err(err) => return Err(err),
178 };
179
180 self.validate_resolved_import(import, resolved, linker)?;
181
182 imports.imports.insert((import.owner(), import.alias().to_string()), resolved);
183 progress = true;
184 }
185
186 if next.is_empty() {
187 break;
188 }
189
190 if !progress {
191 if let Some(import) = next.iter().copied().find(|import| {
192 self.public_import_dependency(import).is_some_and(|dependency| {
193 next.iter().any(|candidate| candidate.key() == dependency)
194 })
195 }) {
196 return Err(LinkerError::ImportReExportCycle {
197 span: import.span(),
198 source_file: source_file(linker.source_manager.as_ref(), import.span()),
199 path: import.target().inner().clone(),
200 });
201 }
202
203 let import = next[0];
204 return self.resolve_import_decl(import, &imports, linker).map(|_| imports);
205 }
206
207 pending = next;
208 }
209
210 Ok(imports)
211 }
212
213 fn resolve_import_decl(
214 &self,
215 import: &UseDecl,
216 imports: &ResolvedImports,
217 linker: &Linker,
218 ) -> Result<ResolvedUse, LinkerError> {
219 self.resolve_import_target(
220 import.owner(),
221 import.alias(),
222 import.target().as_deref(),
223 imports,
224 linker,
225 )
226 }
227
228 fn validate_resolved_import(
229 &self,
230 import: &UseDecl,
231 resolved: ResolvedUse,
232 linker: &Linker,
233 ) -> Result<(), LinkerError> {
234 match (import.kind(), resolved) {
235 (ImportKind::Module, ResolvedUse::Module(_)) => Ok(()),
236 (ImportKind::Module, ResolvedUse::Item(item)) => {
237 Err(LinkerError::InvalidModuleImportTarget {
238 span: import.span(),
239 source_file: source_file(linker.source_manager.as_ref(), import.span()),
240 path: item_path(linker, item),
241 })
242 },
243 (ImportKind::Item, ResolvedUse::Item(item)) => {
244 if import.visibility().is_public()
247 && linker[item.module].path().is_kernel_path()
248 && linker[item].is_procedure()
249 && import.owner != item.module
250 {
251 let span = import.span();
252 let source_file = source_file(linker.source_manager.as_ref(), span);
253 Err(LinkerError::InvalidReExportOfKernelSyscall {
254 span,
255 source_file,
256 path: import.target().inner().clone(),
257 })
258 } else {
259 Ok(())
260 }
261 },
262 (ImportKind::Item, ResolvedUse::Module(id)) => {
263 Err(LinkerError::InvalidItemImportTarget {
264 span: import.span(),
265 source_file: source_file(linker.source_manager.as_ref(), import.span()),
266 path: self.module(id).path.clone(),
267 })
268 },
269 }
270 }
271
272 fn public_import_dependency(&self, import: &UseDecl) -> Option<(ModuleIndex, String)> {
273 let path = import.target().as_deref();
274 let (name, parent_path) = path.split_last()?;
275 let parent = self.find_import_target_parent(import.owner(), parent_path)?;
276 let dependency = self.module(parent).import(name)?;
277 dependency.visibility().is_public().then(|| dependency.key())
278 }
279
280 fn find_import_target_parent(&self, owner: ModuleIndex, path: &Path) -> Option<ModuleIndex> {
281 let (first, rest) = path.split_first()?;
282
283 if first == "self" {
284 return if rest.is_empty() {
285 Some(owner)
286 } else {
287 self.find_self_relative_module_index(owner, rest)
288 };
289 }
290
291 self.find_global_module_index(path)
292 }
293
294 fn find_self_relative_module_index(
295 &self,
296 owner: ModuleIndex,
297 path: &Path,
298 ) -> Option<ModuleIndex> {
299 let mut current = owner;
300 let mut remaining = path;
301
302 while let Some((component, rest)) = remaining.split_first() {
303 let edge = self.module(current).submodule(component)?;
304 current = edge.child();
305 remaining = rest;
306 }
307
308 Some(current)
309 }
310
311 fn resolve_import_target(
312 &self,
313 owner: ModuleIndex,
314 current_alias: &str,
315 path: Span<&Path>,
316 imports: &ResolvedImports,
317 linker: &Linker,
318 ) -> Result<ResolvedUse, LinkerError> {
319 let Some((first, rest)) = path.split_first() else {
320 return Err(undefined_symbol(linker, path));
321 };
322
323 if first == "self" {
324 if rest.is_empty() {
325 return Err(undefined_symbol(linker, path));
326 }
327 let resolved =
328 self.resolve_self_relative_path(owner, rest, path.span(), imports, linker)?;
329 return self.validate_import_target_module(owner, resolved, path, linker);
330 }
331
332 let owner_module = self.module(owner);
333 if !path.is_absolute()
334 && let Some(import) = owner_module.import(first)
335 && import.alias() != current_alias
336 {
337 return Err(LinkerError::ImportTargetUsesImport {
338 span: path.span(),
339 source_file: source_file(linker.source_manager.as_ref(), path.span()),
340 path: path.into_inner().to_path_buf().into_boxed_path().into(),
341 alias: first.to_string(),
342 });
343 }
344
345 match self.resolve_global_path(owner, path.into_inner(), path.span(), Some(imports), linker)
346 {
347 Ok(resolved) => self.validate_import_target_module(owner, resolved, path, linker),
348 Err(LinkerError::UndefinedSymbol { .. }) => Err(undefined_symbol(linker, path)),
349 Err(err) => Err(err),
350 }
351 }
352
353 fn validate_import_target_module(
354 &self,
355 owner: ModuleIndex,
356 resolved: ResolvedUse,
357 path: Span<&Path>,
358 linker: &Linker,
359 ) -> Result<ResolvedUse, LinkerError> {
360 let ResolvedUse::Module(module) = resolved else {
361 return Ok(resolved);
362 };
363
364 if module == owner {
365 return Err(LinkerError::SelfReferentialImport {
366 span: path.span(),
367 source_file: source_file(linker.source_manager.as_ref(), path.span()),
368 path: path.into_inner().to_path_buf().into_boxed_path().into(),
369 });
370 }
371
372 if self.module(module).parent() == Some(owner) {
373 return Err(LinkerError::ImportTargetIsLocalSubmodule {
374 span: path.span(),
375 source_file: source_file(linker.source_manager.as_ref(), path.span()),
376 path: self.module(module).path.clone(),
377 });
378 }
379
380 Ok(resolved)
381 }
382
383 pub fn resolve_code_path(
388 &self,
389 owner: ModuleIndex,
390 path: Span<&Path>,
391 imports: &ResolvedImports,
392 linker: &Linker,
393 ) -> Result<ResolvedUse, LinkerError> {
394 if path.is_absolute() {
395 return self.resolve_global_path(
396 owner,
397 path.into_inner(),
398 path.span(),
399 Some(imports),
400 linker,
401 );
402 }
403
404 let Some((first, rest)) = path.split_first() else {
405 return Err(undefined_symbol(linker, path));
406 };
407
408 if first == "self" {
409 if rest.is_empty() {
410 return Err(undefined_symbol(linker, path));
411 }
412 return self.resolve_self_relative_path(owner, rest, path.span(), imports, linker);
413 }
414
415 let owner_module = self.module(owner);
416 if rest.is_empty() {
417 if let Some(item) = owner_module.item(first) {
418 self.ensure_item_visible(owner, item, path.span(), linker)?;
419 return Ok(ResolvedUse::Item(item.id()));
420 }
421
422 if let Some(resolved) = imports.get(owner, first) {
423 return Ok(resolved);
424 }
425
426 if let Some(edge) = owner_module.submodule(first) {
427 self.ensure_submodule_visible(owner, owner, edge, path.span(), linker)?;
428 return Ok(ResolvedUse::Module(edge.child()));
429 }
430 } else {
431 if let Some(item) = owner_module.item(first) {
432 return Err(SymbolResolutionError::invalid_sub_path(
433 path.span(),
434 item.span(),
435 linker.source_manager.as_ref(),
436 )
437 .into());
438 }
439
440 if let Some(resolved) = imports.get(owner, first) {
441 return match resolved {
442 ResolvedUse::Module(module) => self.resolve_path_from_module(
443 owner,
444 module,
445 rest,
446 path.span(),
447 imports,
448 linker,
449 ),
450 ResolvedUse::Item(item) => Err(SymbolResolutionError::invalid_sub_path(
451 path.span(),
452 linker[item.module][item.index].name().span(),
453 linker.source_manager.as_ref(),
454 )
455 .into()),
456 };
457 }
458
459 if let Some(edge) = owner_module.submodule(first) {
460 self.ensure_submodule_visible(owner, owner, edge, path.span(), linker)?;
461 return self.resolve_path_from_module(
462 owner,
463 edge.child(),
464 rest,
465 path.span(),
466 imports,
467 linker,
468 );
469 }
470 }
471
472 if rest.is_empty() {
473 Err(undefined_symbol(linker, path))
474 } else {
475 Err(LinkerError::InvalidRelativePath {
476 span: path.span(),
477 source_file: source_file(linker.source_manager.as_ref(), path.span()),
478 path: path.into_inner().to_path_buf().into_boxed_path().into(),
479 })
480 }
481 }
482
483 fn resolve_self_relative_path(
484 &self,
485 owner: ModuleIndex,
486 path: &Path,
487 span: SourceSpan,
488 imports: &ResolvedImports,
489 linker: &Linker,
490 ) -> Result<ResolvedUse, LinkerError> {
491 self.resolve_path_from_module(owner, owner, path, span, imports, linker)
492 }
493
494 fn resolve_path_from_module(
495 &self,
496 owner: ModuleIndex,
497 module: ModuleIndex,
498 path: &Path,
499 span: SourceSpan,
500 imports: &ResolvedImports,
501 linker: &Linker,
502 ) -> Result<ResolvedUse, LinkerError> {
503 let mut current = module;
504 let mut remaining = path;
505
506 loop {
507 let Some((component, rest)) = remaining.split_first() else {
508 return Ok(ResolvedUse::Module(current));
509 };
510 let module = self.module(current);
511
512 if rest.is_empty() {
513 if let Some(item) = module.item(component) {
514 self.ensure_item_visible(owner, item, span, linker)?;
515 return Ok(ResolvedUse::Item(item.id()));
516 }
517
518 if let Some(edge) = module.submodule(component) {
519 self.ensure_submodule_visible(owner, current, edge, span, linker)?;
520 return Ok(ResolvedUse::Module(edge.child()));
521 }
522
523 if let Some(import) = module.import(component)
524 && import.visibility().is_public()
525 && let Some(resolved) = imports.get(current, component)
526 {
527 return Ok(resolved);
528 }
529
530 return Err(undefined_symbol_from_path(linker, span, path));
531 }
532
533 if let Some(edge) = module.submodule(component) {
534 self.ensure_submodule_visible(owner, current, edge, span, linker)?;
535 current = edge.child();
536 remaining = rest;
537 continue;
538 }
539
540 if let Some(item) = module.item(component) {
541 return Err(SymbolResolutionError::invalid_sub_path(
542 span,
543 item.span(),
544 linker.source_manager.as_ref(),
545 )
546 .into());
547 }
548
549 return Err(undefined_symbol_from_path(linker, span, path));
550 }
551 }
552
553 fn resolve_global_path(
554 &self,
555 owner: ModuleIndex,
556 path: &Path,
557 span: SourceSpan,
558 imports: Option<&ResolvedImports>,
559 linker: &Linker,
560 ) -> Result<ResolvedUse, LinkerError> {
561 if let Some(module) = self.find_global_module_index(path) {
562 self.ensure_module_visible(owner, module, span, linker)?;
563 return Ok(ResolvedUse::Module(module));
564 }
565
566 let Some((name, parent_path)) = path.split_last() else {
567 return Err(undefined_symbol_from_path(linker, span, path));
568 };
569
570 if parent_path.is_empty() && !parent_path.is_absolute() {
571 return Err(undefined_symbol_from_path(linker, span, path));
572 }
573
574 let Some(parent) = self.find_global_module_index(parent_path) else {
575 if let Some(err) = self.invalid_global_subpath_error(path, span, linker) {
576 return Err(err);
577 }
578 return Err(undefined_symbol_from_path(linker, span, path));
579 };
580 self.ensure_module_visible(owner, parent, span, linker)?;
581 let module = self.module(parent);
582
583 if let Some(item) = module.item(name) {
584 self.ensure_item_visible(owner, item, span, linker)?;
585 return Ok(ResolvedUse::Item(item.id()));
586 }
587
588 if let Some(edge) = module.submodule(name) {
589 self.ensure_submodule_visible(owner, parent, edge, span, linker)?;
590 return Ok(ResolvedUse::Module(edge.child()));
591 }
592
593 if let Some(imports) = imports
594 && let Some(import) = module.import(name)
595 && import.visibility().is_public()
596 && let Some(resolved) = imports.get(parent, name)
597 {
598 return Ok(resolved);
599 }
600
601 Err(undefined_symbol_from_path(linker, span, path))
602 }
603
604 fn invalid_global_subpath_error(
605 &self,
606 path: &Path,
607 span: SourceSpan,
608 linker: &Linker,
609 ) -> Option<LinkerError> {
610 let mut prefix = PathBuf::with_capacity(path.byte_len());
611 if path.is_absolute() {
612 prefix.push_component("::");
613 }
614
615 let mut remaining = path;
616 while let Some((component, rest)) = remaining.split_first() {
617 prefix.push_component(component);
618
619 if let Some(module) = self.find_global_module_index(prefix.as_path())
620 && let Some((next, _)) = rest.split_first()
621 && let Some(item) = self.module(module).item(next)
622 {
623 return Some(
624 SymbolResolutionError::invalid_sub_path(
625 span,
626 item.span(),
627 linker.source_manager.as_ref(),
628 )
629 .into(),
630 );
631 }
632
633 remaining = rest;
634 }
635
636 None
637 }
638
639 fn find_global_module_index(&self, path: &Path) -> Option<ModuleIndex> {
640 self.find_module_index(path)
641 .or_else(|| {
642 path.is_absolute().then(|| self.find_module_index(path.to_relative())).flatten()
643 })
644 .or_else(|| {
645 if path.is_absolute() {
646 None
647 } else {
648 path.to_absolute()
649 .ok()
650 .and_then(|absolute| self.find_module_index(absolute.as_ref()))
651 }
652 })
653 }
654
655 fn ensure_module_visible(
656 &self,
657 owner: ModuleIndex,
658 module: ModuleIndex,
659 span: SourceSpan,
660 linker: &Linker,
661 ) -> Result<(), LinkerError> {
662 let mut child = module;
663 while let Some(parent) = self.module(child).parent() {
664 let edge = self
665 .module(parent)
666 .submodules
667 .values()
668 .find(|edge| edge.child == child)
669 .expect("child parent edge must exist");
670 self.ensure_submodule_visible(owner, parent, edge, span, linker)?;
671 child = parent;
672 }
673
674 Ok(())
675 }
676
677 fn ensure_submodule_visible(
678 &self,
679 owner: ModuleIndex,
680 parent: ModuleIndex,
681 edge: &ModuleEdge,
682 span: SourceSpan,
683 linker: &Linker,
684 ) -> Result<(), LinkerError> {
685 if edge.visibility().is_public() || self.is_module_in_scope_of(owner, parent) {
686 return Ok(());
687 }
688
689 let child = self.module(edge.child());
690 let defined_source_file = source_file(linker.source_manager.as_ref(), edge.span());
691 let source_file = source_file(linker.source_manager.as_ref(), span);
692 Err(LinkerError::PrivateSubmodule {
693 span,
694 source_file,
695 module: child.path.clone(),
696 defined: Some(
697 RelatedLabel::advice("the referenced submodule is private")
698 .with_labeled_span(edge.span(), "the referenced submodule is private")
699 .with_source_file(defined_source_file),
700 ),
701 })
702 }
703
704 fn is_module_in_scope_of(&self, module: ModuleIndex, ancestor: ModuleIndex) -> bool {
705 let mut current = Some(module);
706 while let Some(id) = current {
707 if id == ancestor {
708 return true;
709 }
710 current = self.module(id).parent();
711 }
712
713 false
714 }
715
716 fn ensure_item_visible(
717 &self,
718 owner: ModuleIndex,
719 item: &ItemDef,
720 span: SourceSpan,
721 linker: &Linker,
722 ) -> Result<(), LinkerError> {
723 if owner == item.id().module || item.visibility().is_public() {
724 return Ok(());
725 }
726
727 Err(SymbolResolutionError::private_symbol(
728 span,
729 item.span(),
730 linker.source_manager.as_ref(),
731 )
732 .into())
733 }
734
735 fn connect_submodule_edges(&mut self, linker: &Linker) -> Result<(), LinkerError> {
736 for parent in linker.modules.iter() {
737 let parent_id = parent.id();
738 for decl in parent.submodules() {
739 let name = decl.name.as_str();
740 if self.modules[parent_id.as_usize()].contains_member(name) {
741 return Err(name_conflict(linker, parent, name, decl.name.span(), "submodule"));
742 }
743
744 let child_path = parent.path().join(&decl.name);
745 let child = self.find_module_index(child_path.as_path()).ok_or_else(|| {
746 LinkerError::UndefinedModule {
747 span: decl.name.span(),
748 source_file: source_file(linker.source_manager.as_ref(), decl.name.span()),
749 path: child_path.into_boxed_path().into(),
750 }
751 })?;
752
753 let edge = ModuleEdge {
754 name: name.to_string(),
755 child,
756 visibility: decl.visibility,
757 span: decl.name.span(),
758 };
759 self.modules[parent_id.as_usize()].submodules.insert(edge.name.clone(), edge);
760
761 let child_node = &mut self.modules[child.as_usize()];
762 child_node.parent.get_or_insert(parent_id);
763 }
764 }
765
766 Ok(())
767 }
768
769 fn validate_source_module_declarations(&self) -> Result<(), LinkerError> {
770 for module in self.modules.iter().filter(|module| module.source == ModuleSource::Ast) {
771 let Some((name, parent_path)) = module.path.split_last() else {
772 continue;
773 };
774
775 if parent_path.is_empty() {
776 continue;
777 }
778
779 let Some(parent_id) = self.find_module_index(parent_path) else {
780 continue;
781 };
782 let parent = self.module(parent_id);
783
784 match parent.submodule(name) {
785 Some(edge) if edge.child == module.id => (),
786 _ => {
787 return Err(LinkerError::UndeclaredSubmodule {
788 path: module.path.clone(),
789 parent: parent.path.clone(),
790 name: name.to_string(),
791 });
792 },
793 }
794 }
795
796 Ok(())
797 }
798}
799
800impl ModuleNode {
801 fn from_link_module(module: &super::LinkModule, linker: &Linker) -> Result<Self, LinkerError> {
802 let mut node = Self {
803 id: module.id(),
804 path: module.path().clone(),
805 source: module.source(),
806 parent: None,
807 items: BTreeMap::default(),
808 submodules: BTreeMap::default(),
809 imports: BTreeMap::default(),
810 };
811
812 for (index, symbol) in module.symbols().enumerate() {
813 let name = symbol.name().as_str().to_string();
814 let span = symbol.name().span();
815 if node.contains_member(&name) {
816 return Err(name_conflict(linker, module, &name, span, "item"));
817 }
818
819 node.items.insert(
820 name,
821 ItemDef {
822 id: module.id() + ItemIndex::new(index),
823 visibility: symbol.visibility(),
824 span,
825 },
826 );
827 }
828
829 for import in module.imports() {
830 let name = import.local_name().as_str().to_string();
831 let span = import.local_name().span();
832 if node.contains_member(&name) {
833 return Err(name_conflict(linker, module, &name, span, "import"));
834 }
835
836 node.imports.insert(
837 name.clone(),
838 UseDecl {
839 owner: module.id(),
840 alias: name,
841 kind: import.kind(),
842 visibility: import.visibility(),
843 target: import.target_path(),
844 span: import.span(),
845 },
846 );
847 }
848
849 Ok(node)
850 }
851
852 fn contains_member(&self, name: &str) -> bool {
853 self.items.contains_key(name)
854 || self.imports.contains_key(name)
855 || self.submodules.contains_key(name)
856 }
857
858 #[inline]
859 pub fn parent(&self) -> Option<ModuleIndex> {
860 self.parent
861 }
862
863 #[inline]
864 pub fn item(&self, name: &str) -> Option<&ItemDef> {
865 self.items.get(name)
866 }
867
868 #[inline]
869 pub fn submodule(&self, name: &str) -> Option<&ModuleEdge> {
870 self.submodules.get(name)
871 }
872
873 #[inline]
874 pub fn import(&self, name: &str) -> Option<&UseDecl> {
875 self.imports.get(name)
876 }
877}
878
879impl ModuleEdge {
880 #[inline]
881 pub fn child(&self) -> ModuleIndex {
882 self.child
883 }
884
885 #[inline]
886 pub fn visibility(&self) -> Visibility {
887 self.visibility
888 }
889
890 #[inline]
891 pub fn span(&self) -> SourceSpan {
892 self.span
893 }
894}
895
896impl ItemDef {
897 #[inline]
898 pub fn id(&self) -> GlobalItemIndex {
899 self.id
900 }
901
902 #[inline]
903 pub fn visibility(&self) -> Visibility {
904 self.visibility
905 }
906
907 #[inline]
908 pub fn span(&self) -> SourceSpan {
909 self.span
910 }
911}
912
913impl UseDecl {
914 #[inline]
915 pub fn key(&self) -> (ModuleIndex, String) {
916 (self.owner, self.alias.clone())
917 }
918
919 #[inline]
920 pub fn owner(&self) -> ModuleIndex {
921 self.owner
922 }
923
924 #[inline]
925 pub fn alias(&self) -> &str {
926 &self.alias
927 }
928
929 #[inline]
930 pub fn visibility(&self) -> Visibility {
931 self.visibility
932 }
933
934 #[inline]
935 pub fn kind(&self) -> ImportKind {
936 self.kind
937 }
938
939 #[inline]
940 pub fn target(&self) -> &Span<Arc<Path>> {
941 &self.target
942 }
943
944 #[inline]
945 pub fn span(&self) -> SourceSpan {
946 self.span
947 }
948}
949
950fn name_conflict(
951 linker: &Linker,
952 module: &super::LinkModule,
953 name: &str,
954 span: SourceSpan,
955 kind: &'static str,
956) -> LinkerError {
957 LinkerError::NamespaceNameConflict {
958 span,
959 source_file: source_file(linker.source_manager.as_ref(), span),
960 module: module.path().clone(),
961 name: name.to_string(),
962 kind,
963 }
964}
965
966fn undefined_symbol(linker: &Linker, path: Span<&Path>) -> LinkerError {
967 undefined_symbol_from_path(linker, path.span(), path.into_inner())
968}
969
970fn undefined_symbol_from_path(linker: &Linker, span: SourceSpan, path: &Path) -> LinkerError {
971 LinkerError::UndefinedSymbol {
972 span,
973 source_file: source_file(linker.source_manager.as_ref(), span),
974 path: path.to_path_buf().into_boxed_path().into(),
975 }
976}
977
978fn item_path(linker: &Linker, item: GlobalItemIndex) -> Arc<Path> {
979 linker[item.module].path().join(linker[item.module][item.index].name()).into()
980}
981
982fn source_file(
983 source_manager: &dyn SourceManager,
984 span: SourceSpan,
985) -> Option<Arc<miden_assembly_syntax::debuginfo::SourceFile>> {
986 source_manager.get(span.source_id()).ok()
987}
988
989#[cfg(test)]
990mod tests {
991 use alloc::{boxed::Box, sync::Arc};
992
993 use miden_assembly_syntax::{
994 Parse, Path,
995 debuginfo::{DefaultSourceManager, SourceLanguage, SourceManager, Span},
996 };
997
998 use super::*;
999
1000 fn parse_module(
1001 source_manager: Arc<dyn SourceManager>,
1002 name: &str,
1003 source: &str,
1004 ) -> Box<miden_assembly_syntax::ast::Module> {
1005 source_manager
1006 .load(SourceLanguage::Masm, name.into(), source.to_string())
1007 .parse(false, source_manager)
1008 .expect("module should parse")
1009 }
1010
1011 #[test]
1012 fn namespace_graph_records_items_imports_and_public_submodule_edges() {
1013 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1014 let mut root = parse_module(
1015 source_manager.clone(),
1016 "root.masm",
1017 r#"
1018 namespace ::root
1019
1020 pub mod child
1021
1022 use external::module as external
1023
1024 pub proc entry
1025 push.1
1026 end
1027 "#,
1028 );
1029 let mut child = parse_module(
1030 source_manager.clone(),
1031 "child.masm",
1032 r#"
1033 namespace ::root::child
1034
1035 pub const VALUE = 1
1036 "#,
1037 );
1038
1039 let mut linker = Linker::new(source_manager);
1040 let root_id = linker.link_module(&mut root).expect("root link should succeed");
1041 let child_id = linker.link_module(&mut child).expect("child link should succeed");
1042
1043 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1044
1045 assert_eq!(graph.num_modules(), 2);
1046 assert_eq!(graph.find_module_index(Path::new("::root")), Some(root_id));
1047 assert_eq!(graph.find_module_index(Path::new("::root::child")), Some(child_id));
1048 assert_eq!(graph.module(root_id).submodule("child").unwrap().child(), child_id);
1049 assert_eq!(graph.module(child_id).parent(), Some(root_id));
1050 assert!(graph.module(root_id).item("entry").is_some());
1051 assert!(graph.module(root_id).import("external").is_some());
1052 assert!(graph.module(child_id).item("VALUE").is_some());
1053 }
1054
1055 #[test]
1056 fn namespace_graph_rejects_declared_missing_child_module() {
1057 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1058 let mut root = parse_module(
1059 source_manager.clone(),
1060 "root.masm",
1061 r#"
1062 namespace ::root
1063
1064 pub mod missing
1065 "#,
1066 );
1067
1068 let mut linker = Linker::new(source_manager);
1069 linker.link_module(&mut root).expect("root link should succeed");
1070
1071 let err = NamespaceGraph::build(&linker).expect_err("missing child should fail");
1072 assert!(matches!(err, LinkerError::UndefinedModule { .. }));
1073 }
1074
1075 #[test]
1076 fn namespace_graph_rejects_undeclared_child_module() {
1077 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1078 let mut root = parse_module(
1079 source_manager.clone(),
1080 "root.masm",
1081 r#"
1082 namespace ::root
1083 "#,
1084 );
1085 let mut child = parse_module(
1086 source_manager.clone(),
1087 "child.masm",
1088 r#"
1089 namespace ::root::child
1090 "#,
1091 );
1092
1093 let mut linker = Linker::new(source_manager);
1094 linker.link_module(&mut root).expect("root link should succeed");
1095 linker.link_module(&mut child).expect("child link should succeed");
1096
1097 let err = NamespaceGraph::build(&linker).expect_err("undeclared child should fail");
1098 assert!(matches!(err, LinkerError::UndeclaredSubmodule { .. }));
1099 }
1100
1101 #[test]
1102 fn namespace_graph_resolves_module_and_item_imports_independently() {
1103 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1104 let mut imported = parse_module(
1105 source_manager.clone(),
1106 "imported.masm",
1107 r#"
1108 namespace lib::mod
1109
1110 pub const VALUE = 1
1111 "#,
1112 );
1113 let mut consumer = parse_module(
1114 source_manager.clone(),
1115 "consumer.masm",
1116 r#"
1117 namespace app
1118
1119 use lib::mod
1120 use {VALUE} from lib::mod
1121 "#,
1122 );
1123
1124 let mut linker = Linker::new(source_manager);
1125 let imported_id = linker.link_module(&mut imported).expect("imported link should succeed");
1126 let consumer_id = linker.link_module(&mut consumer).expect("consumer link should succeed");
1127
1128 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1129 let imports = graph.resolve_imports(&linker).expect("imports should resolve");
1130
1131 assert_eq!(imports.get(consumer_id, "mod"), Some(ResolvedUse::Module(imported_id)));
1132 assert!(matches!(imports.get(consumer_id, "VALUE"), Some(ResolvedUse::Item(_))));
1133 }
1134
1135 #[test]
1136 fn namespace_graph_resolves_code_paths_through_imported_modules() {
1137 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1138 let mut imported = parse_module(
1139 source_manager.clone(),
1140 "imported.masm",
1141 r#"
1142 namespace lib::mod
1143
1144 pub const VALUE = 1
1145 "#,
1146 );
1147 let mut consumer = parse_module(
1148 source_manager.clone(),
1149 "consumer.masm",
1150 r#"
1151 namespace app
1152
1153 use lib::mod
1154 "#,
1155 );
1156
1157 let mut linker = Linker::new(source_manager);
1158 linker.link_module(&mut imported).expect("imported link should succeed");
1159 let consumer_id = linker.link_module(&mut consumer).expect("consumer link should succeed");
1160
1161 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1162 let imports = graph.resolve_imports(&linker).expect("imports should resolve");
1163 let resolved = graph
1164 .resolve_code_path(
1165 consumer_id,
1166 Span::unknown(Path::new("mod::VALUE")),
1167 &imports,
1168 &linker,
1169 )
1170 .expect("code path should resolve through imported module");
1171
1172 assert!(matches!(resolved, ResolvedUse::Item(_)));
1173 }
1174
1175 #[test]
1176 fn namespace_graph_resolves_absolute_code_paths_globally() {
1177 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1178 let mut imported = parse_module(
1179 source_manager.clone(),
1180 "imported.masm",
1181 r#"
1182 namespace real::mod
1183
1184 pub const VALUE = 1
1185 "#,
1186 );
1187 let mut global = parse_module(
1188 source_manager.clone(),
1189 "global.masm",
1190 r#"
1191 namespace lib
1192
1193 pub const VALUE = 2
1194 "#,
1195 );
1196 let mut consumer = parse_module(
1197 source_manager.clone(),
1198 "consumer.masm",
1199 r#"
1200 namespace app
1201
1202 use real::mod as lib
1203 "#,
1204 );
1205
1206 let mut linker = Linker::new(source_manager);
1207 linker.link_module(&mut imported).expect("imported link should succeed");
1208 let global_id = linker.link_module(&mut global).expect("global link should succeed");
1209 let consumer_id = linker.link_module(&mut consumer).expect("consumer link should succeed");
1210
1211 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1212 let imports = graph.resolve_imports(&linker).expect("imports should resolve");
1213 let resolved = graph
1214 .resolve_code_path(
1215 consumer_id,
1216 Span::unknown(Path::new("::lib::VALUE")),
1217 &imports,
1218 &linker,
1219 )
1220 .expect("absolute code path should resolve globally");
1221
1222 assert!(matches!(resolved, ResolvedUse::Item(gid) if gid.module == global_id));
1223 }
1224
1225 #[test]
1226 fn namespace_graph_resolves_absolute_code_paths_to_public_item_reexports() {
1227 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1228 let mut dep = parse_module(
1229 source_manager.clone(),
1230 "dep.masm",
1231 r#"
1232 namespace dep
1233
1234 pub const VALUE = 1
1235 "#,
1236 );
1237 let mut root = parse_module(
1238 source_manager.clone(),
1239 "root.masm",
1240 r#"
1241 namespace root
1242
1243 pub use {VALUE as ALIAS} from dep
1244 "#,
1245 );
1246
1247 let mut linker = Linker::new(source_manager);
1248 let dep_id = linker.link_module(&mut dep).expect("dep link should succeed");
1249 let root_id = linker.link_module(&mut root).expect("root link should succeed");
1250
1251 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1252 let imports = graph.resolve_imports(&linker).expect("imports should resolve");
1253 let resolved = graph
1254 .resolve_code_path(
1255 root_id,
1256 Span::unknown(Path::new("::root::ALIAS")),
1257 &imports,
1258 &linker,
1259 )
1260 .expect("absolute code path should resolve to public item re-export");
1261
1262 assert!(matches!(resolved, ResolvedUse::Item(gid) if gid.module == dep_id));
1263 }
1264
1265 #[test]
1266 fn namespace_graph_rejects_private_submodule_import() {
1267 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1268 let mut root = parse_module(
1269 source_manager.clone(),
1270 "root.masm",
1271 r#"
1272 namespace root
1273
1274 mod child
1275 "#,
1276 );
1277 let mut child = parse_module(
1278 source_manager.clone(),
1279 "child.masm",
1280 r#"
1281 namespace root::child
1282 "#,
1283 );
1284 let mut consumer = parse_module(
1285 source_manager.clone(),
1286 "consumer.masm",
1287 r#"
1288 namespace app
1289
1290 use root::child
1291 "#,
1292 );
1293
1294 let mut linker = Linker::new(source_manager);
1295 linker.link_module(&mut root).expect("root link should succeed");
1296 linker.link_module(&mut child).expect("child link should succeed");
1297 linker.link_module(&mut consumer).expect("consumer link should succeed");
1298
1299 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1300 let err = graph.resolve_imports(&linker).expect_err("private submodule should fail");
1301 assert!(matches!(err, LinkerError::PrivateSubmodule { .. }));
1302 }
1303
1304 #[test]
1305 fn namespace_graph_rejects_module_reexport() {
1306 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1307 let mut root = parse_module(
1308 source_manager.clone(),
1309 "root.masm",
1310 r#"
1311 namespace root
1312
1313 pub mod child
1314 "#,
1315 );
1316 let mut child = parse_module(
1317 source_manager.clone(),
1318 "child.masm",
1319 r#"
1320 namespace root::child
1321 "#,
1322 );
1323 let mut consumer = parse_module(
1324 source_manager.clone(),
1325 "consumer.masm",
1326 r#"
1327 namespace app
1328
1329 pub use {child} from root
1330 "#,
1331 );
1332
1333 let mut linker = Linker::new(source_manager);
1334 linker.link_module(&mut root).expect("root link should succeed");
1335 linker.link_module(&mut child).expect("child link should succeed");
1336 linker.link_module(&mut consumer).expect("consumer link should succeed");
1337
1338 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1339 let err = graph.resolve_imports(&linker).expect_err("module re-export should fail");
1340 assert!(matches!(err, LinkerError::InvalidItemImportTarget { .. }));
1341 }
1342
1343 #[test]
1344 fn namespace_graph_rejects_imports_through_other_imports() {
1345 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
1346 let mut root = parse_module(
1347 source_manager.clone(),
1348 "root.masm",
1349 r#"
1350 namespace root
1351
1352 pub mod child
1353 "#,
1354 );
1355 let mut child = parse_module(
1356 source_manager.clone(),
1357 "child.masm",
1358 r#"
1359 namespace root::child
1360
1361 pub const VALUE = 1
1362 "#,
1363 );
1364 let mut consumer = parse_module(
1365 source_manager.clone(),
1366 "consumer.masm",
1367 r#"
1368 namespace app
1369
1370 use root::child
1371 use {VALUE} from child
1372 "#,
1373 );
1374
1375 let mut linker = Linker::new(source_manager);
1376 linker.link_module(&mut root).expect("root link should succeed");
1377 linker.link_module(&mut child).expect("child link should succeed");
1378 linker.link_module(&mut consumer).expect("consumer link should succeed");
1379
1380 let graph = NamespaceGraph::build(&linker).expect("namespace graph should build");
1381 let err = graph.resolve_imports(&linker).expect_err("import chaining should fail");
1382 assert!(matches!(err, LinkerError::ImportTargetUsesImport { .. }));
1383 }
1384}