Skip to main content

miden_assembly/linker/
namespaces.rs

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/// A graph of modules, concrete items, submodule declarations, and imports known to the linker.
18///
19/// This is intentionally narrower than the full linker graph: it answers namespace questions only,
20/// and does not know about package linkage, MAST forests, call graph state, or AST rewrites.
21#[derive(Debug, Clone)]
22pub struct NamespaceGraph {
23    modules: Vec<ModuleNode>,
24    modules_by_path: BTreeMap<Arc<Path>, ModuleIndex>,
25}
26
27/// A module in the linker namespace graph.
28#[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/// A declared child module edge.
40#[derive(Debug, Clone)]
41pub struct ModuleEdge {
42    name: String,
43    child: ModuleIndex,
44    visibility: Visibility,
45    span: SourceSpan,
46}
47
48/// A concrete item definition.
49#[derive(Debug, Clone)]
50pub struct ItemDef {
51    id: GlobalItemIndex,
52    visibility: Visibility,
53    span: SourceSpan,
54}
55
56/// An import declaration.
57#[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/// The result of resolving an import target.
68#[derive(Debug, Copy, Clone, PartialEq, Eq)]
69pub enum ResolvedUse {
70    Module(ModuleIndex),
71    Item(GlobalItemIndex),
72}
73
74/// Import resolutions keyed by the module that owns the import and the local alias name.
75#[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    /// Build a namespace graph from the modules currently registered in `linker`.
89    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    /// Find a module by exact path.
112    #[inline]
113    pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
114        self.modules_by_path.get(path).copied()
115    }
116
117    /// Get a module node by id.
118    #[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    /// Return every module reachable from `root` by following public submodule declarations.
129    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    /// Resolve all path imports without consulting any imports from the importing module.
149    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                // Reject re-export of kernel syscalls from any module other than the root kernel
245                // module itself
246                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    /// Resolve a path referenced from code in `owner`.
384    ///
385    /// Unlike import declarations, code references may start with a local import alias. Imports are
386    /// still not expanded recursively here; this only consults the already-resolved import table.
387    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}