miden_assembly/linker/resolver/
symbol_resolver.rs

1use alloc::{borrow::Cow, boxed::Box, collections::BTreeSet, sync::Arc};
2use core::ops::ControlFlow;
3
4use miden_assembly_syntax::{
5    ast::{
6        AliasTarget, InvocationTarget, InvokeKind, Path, SymbolResolution, SymbolResolutionError,
7    },
8    debuginfo::{SourceManager, SourceSpan, Span, Spanned},
9    diagnostics::RelatedLabel,
10};
11
12use crate::{GlobalItemIndex, LinkerError, ModuleIndex, linker::Linker};
13
14// HELPER STRUCTS
15// ================================================================================================
16
17/// Represents the context in which symbols should be resolved.
18///
19/// A symbol may be resolved in different ways depending on where it is being referenced from, and
20/// how it is being referenced.
21#[derive(Debug, Clone)]
22pub struct SymbolResolutionContext {
23    /// The source span of the caller/referent
24    pub span: SourceSpan,
25    /// The "where", i.e. index of the caller/referent's module node in the [Linker] module graph.
26    pub module: ModuleIndex,
27    /// The "how", i.e. how the symbol is being referenced/invoked.
28    ///
29    /// This is primarily relevant for procedure invocations, particularly syscalls, as "local"
30    /// names resolve in the kernel module, _not_ in the caller's module. Non-procedure symbols are
31    /// always pure references.
32    pub kind: Option<InvokeKind>,
33}
34
35impl SymbolResolutionContext {
36    #[inline]
37    pub fn in_syscall(&self) -> bool {
38        matches!(self.kind, Some(InvokeKind::SysCall))
39    }
40
41    fn display_kind(&self) -> impl core::fmt::Display {
42        struct Kind(Option<InvokeKind>);
43        impl core::fmt::Display for Kind {
44            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45                match self {
46                    Self(None) => f.write_str("item"),
47                    Self(Some(kind)) => core::fmt::Display::fmt(kind, f),
48                }
49            }
50        }
51
52        Kind(self.kind)
53    }
54}
55
56// SYMBOL RESOLVER
57// ================================================================================================
58
59/// A [SymbolResolver] is used to resolve a procedure invocation target to its concrete definition.
60///
61/// Because modules can re-export/alias the procedures of modules they import, resolving the name of
62/// a procedure can require multiple steps to reach the original concrete definition of the
63/// procedure.
64///
65/// The [SymbolResolver] encapsulates the tricky details of doing this, so that users of the
66/// resolver need only provide a reference to the [Linker], a name they wish to resolve, and some
67/// information about the caller necessary to determine the context in which the name should be
68/// resolved.
69pub struct SymbolResolver<'a> {
70    /// The graph containing already-compiled and partially-resolved modules.
71    graph: &'a Linker,
72}
73
74impl<'a> SymbolResolver<'a> {
75    /// Create a new [SymbolResolver] for the provided [Linker].
76    pub fn new(graph: &'a Linker) -> Self {
77        Self { graph }
78    }
79
80    #[inline(always)]
81    pub fn source_manager(&self) -> &dyn SourceManager {
82        &self.graph.source_manager
83    }
84
85    #[inline(always)]
86    pub fn source_manager_arc(&self) -> Arc<dyn SourceManager> {
87        self.graph.source_manager.clone()
88    }
89
90    #[inline(always)]
91    pub(crate) fn linker(&self) -> &Linker {
92        self.graph
93    }
94
95    /// Resolve `target`, a possibly-resolved symbol reference, to a [SymbolResolution], using
96    /// `context` as the context.
97    pub fn resolve_invoke_target(
98        &self,
99        context: &SymbolResolutionContext,
100        target: &InvocationTarget,
101    ) -> Result<SymbolResolution, LinkerError> {
102        match target {
103            InvocationTarget::MastRoot(mast_root) => {
104                log::debug!(target: "name-resolver::invoke", "resolving {target}");
105                match self.graph.get_procedure_index_by_digest(mast_root) {
106                    None => Ok(SymbolResolution::MastRoot(*mast_root)),
107                    Some(gid) => Ok(SymbolResolution::Exact {
108                        gid,
109                        path: Span::new(mast_root.span(), self.item_path(gid)),
110                    }),
111                }
112            },
113            InvocationTarget::Symbol(symbol) => {
114                self.resolve(context, Span::new(symbol.span(), symbol))
115            },
116            InvocationTarget::Path(path) => match self.resolve_path(context, path.as_deref())? {
117                SymbolResolution::Module { id: _, path: module_path } => {
118                    Err(LinkerError::InvalidInvokeTarget {
119                        span: path.span(),
120                        source_file: self.graph.source_manager.get(path.span().source_id()).ok(),
121                        path: module_path.into_inner(),
122                    })
123                },
124                resolution => Ok(resolution),
125            },
126        }
127    }
128
129    /// Resolve `target`, a possibly-resolved symbol reference, to a [SymbolResolution], using
130    /// `context` as the context.
131    pub fn resolve_alias_target(
132        &self,
133        context: &SymbolResolutionContext,
134        target: &AliasTarget,
135    ) -> Result<SymbolResolution, LinkerError> {
136        match target {
137            AliasTarget::MastRoot(mast_root) => {
138                log::debug!(target: "name-resolver::alias", "resolving alias target {target}");
139                match self.graph.get_procedure_index_by_digest(mast_root) {
140                    None => Ok(SymbolResolution::MastRoot(*mast_root)),
141                    Some(gid) => Ok(SymbolResolution::Exact {
142                        gid,
143                        path: Span::new(mast_root.span(), self.item_path(gid)),
144                    }),
145                }
146            },
147            AliasTarget::Path(path) => self.resolve_path(context, path.as_deref()),
148        }
149    }
150
151    pub fn resolve_path(
152        &self,
153        context: &SymbolResolutionContext,
154        path: Span<&Path>,
155    ) -> Result<SymbolResolution, LinkerError> {
156        log::debug!(target: "name-resolver::path", "resolving path '{path}' (absolute = {})", path.is_absolute());
157        if let Some(symbol) = path.as_ident() {
158            log::debug!(target: "name-resolver::path", "resolving path '{symbol}' as local symbol");
159            return self.resolve(context, Span::new(path.span(), &symbol));
160        }
161
162        // Try to resolve the path to a module first, if the context indicates it is not an
163        // explicit invocation target
164        if context.kind.is_none() {
165            log::debug!(target: "name-resolver::path", "attempting to resolve '{path}' as module path");
166            if let Some(id) = self.get_module_index_by_path(path.inner()) {
167                log::debug!(target: "name-resolver::path", "resolved '{path}' to module id '{id}'");
168                return Ok(SymbolResolution::Module {
169                    id,
170                    path: Span::new(context.span, self.module_path(id).to_path_buf().into()),
171                });
172            }
173        }
174
175        // The path must refer to an item, so resolve the item
176        if path.is_absolute() {
177            self.find(context, path)
178        } else {
179            let (ns, subpath) = path.split_first().unwrap();
180            log::debug!(target: "name-resolver::path", "resolving path as '{subpath}' relative to '{ns}'");
181            // Check if the first component of the namespace was previously imported
182            match self.resolve_import(context, Span::new(path.span(), ns))? {
183                SymbolResolution::Exact { gid, path } => {
184                    if subpath.is_empty() {
185                        log::debug!(target: "name-resolver::path", "resolved '{ns}' to imported item '{path}'");
186                        Ok(SymbolResolution::Exact { gid, path })
187                    } else {
188                        log::error!(target: "name-resolver::path", "resolved '{ns}' to imported item '{path}'");
189                        Err(Box::new(SymbolResolutionError::InvalidSubPath {
190                            span: context.span,
191                            source_file: self
192                                .graph
193                                .source_manager
194                                .get(context.span.source_id())
195                                .ok(),
196                            relative_to: None,
197                        })
198                        .into())
199                    }
200                },
201                SymbolResolution::MastRoot(digest) => {
202                    if subpath.is_empty() {
203                        log::debug!(target: "name-resolver::path", "resolved '{ns}' to imported procedure '{digest}'");
204                        Ok(SymbolResolution::MastRoot(digest))
205                    } else {
206                        log::error!(target: "name-resolver::path", "resolved '{ns}' to imported procedure '{digest}'");
207                        Err(Box::new(SymbolResolutionError::InvalidSubPath {
208                            span: context.span,
209                            source_file: self
210                                .graph
211                                .source_manager
212                                .get(context.span.source_id())
213                                .ok(),
214                            relative_to: None,
215                        })
216                        .into())
217                    }
218                },
219                SymbolResolution::Module { id, path: module_path } => {
220                    log::debug!(target: "name-resolver::path", "resolved '{ns}' to imported module '{module_path}'");
221                    if subpath.is_empty() {
222                        return Ok(SymbolResolution::Module { id, path: module_path });
223                    }
224
225                    let span = path.span();
226                    let path = module_path.join(subpath);
227                    let context = SymbolResolutionContext {
228                        span: module_path.span(),
229                        module: id,
230                        kind: context.kind,
231                    };
232                    self.resolve_path(&context, Span::new(span, path.as_path()))
233                },
234                SymbolResolution::Local(_) | SymbolResolution::External(_) => unreachable!(),
235            }
236        }
237    }
238
239    /// Resolve `symbol` to a [SymbolResolution], using `context` as the resolution context.
240    fn resolve(
241        &self,
242        context: &SymbolResolutionContext,
243        symbol: Span<&str>,
244    ) -> Result<SymbolResolution, LinkerError> {
245        log::debug!(target: "name-resolver::resolve", "resolving symbol '{symbol}'");
246        match self.resolve_local(context, symbol.inner())? {
247            SymbolResolution::Local(index) if context.in_syscall() => {
248                log::debug!(target: "name-resolver::resolve", "resolved symbol to local item '{index}'");
249                let gid = GlobalItemIndex {
250                    module: self.graph.kernel_index.unwrap(),
251                    index: index.into_inner(),
252                };
253                Ok(SymbolResolution::Exact {
254                    gid,
255                    path: Span::new(index.span(), self.item_path(gid)),
256                })
257            },
258            SymbolResolution::Local(index) => {
259                log::debug!(target: "name-resolver::resolve", "resolved symbol to local item '{index}'");
260                let gid = GlobalItemIndex {
261                    module: context.module,
262                    index: index.into_inner(),
263                };
264                Ok(SymbolResolution::Exact {
265                    gid,
266                    path: Span::new(index.span(), self.item_path(gid)),
267                })
268            },
269            SymbolResolution::External(fqn) => match self.find(context, fqn.as_deref())? {
270                resolution @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. }) => {
271                    log::debug!(target: "name-resolver::resolve", "resolved '{symbol}' via '{fqn}': {resolution:?}");
272                    Ok(resolution)
273                },
274                SymbolResolution::External(_)
275                | SymbolResolution::Local(_)
276                | SymbolResolution::MastRoot(_) => unreachable!(),
277            },
278            SymbolResolution::MastRoot(digest) => {
279                log::debug!(target: "name-resolver::resolve", "resolved '{symbol}' to digest {digest}");
280                match self.graph.get_procedure_index_by_digest(&digest) {
281                    Some(gid) => Ok(SymbolResolution::Exact {
282                        gid,
283                        path: Span::new(digest.span(), self.item_path(gid)),
284                    }),
285                    None => Ok(SymbolResolution::MastRoot(digest)),
286                }
287            },
288            res @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. }) => {
289                log::debug!(target: "name-resolver::resolve", "resolved '{symbol}': {res:?}");
290                Ok(res)
291            },
292        }
293    }
294
295    /// Resolve `symbol`, the name of an imported item, to a [Path], using `context`.
296    fn resolve_import(
297        &self,
298        context: &SymbolResolutionContext,
299        symbol: Span<&str>,
300    ) -> Result<SymbolResolution, LinkerError> {
301        log::debug!(target: "name-resolver::import", "resolving import '{symbol}' from module index {}", context.module);
302        let module = &self.graph[context.module];
303        log::debug!(target: "name-resolver::import", "context source type is '{:?}'", module.source());
304
305        let found = module.resolve(symbol, self);
306        log::debug!(target: "name-resolver::import", "local resolution for '{symbol}': {found:?}");
307        match found {
308            Ok(SymbolResolution::External(path)) => {
309                let context = SymbolResolutionContext {
310                    span: symbol.span(),
311                    module: context.module,
312                    kind: None,
313                };
314                self.resolve_path(&context, path.as_deref())
315            },
316            Ok(SymbolResolution::Local(item)) => {
317                let gid = context.module + item.into_inner();
318                Ok(SymbolResolution::Exact {
319                    gid,
320                    path: Span::new(item.span(), self.item_path(gid)),
321                })
322            },
323            Ok(SymbolResolution::MastRoot(digest)) => {
324                match self.graph.get_procedure_index_by_digest(&digest) {
325                    Some(gid) => Ok(SymbolResolution::Exact {
326                        gid,
327                        path: Span::new(digest.span(), self.item_path(gid)),
328                    }),
329                    None => Ok(SymbolResolution::MastRoot(digest)),
330                }
331            },
332            Ok(res @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. })) => Ok(res),
333            Err(err) if matches!(&*err, SymbolResolutionError::UndefinedSymbol { .. }) => {
334                // If we attempted to resolve a symbol to an import, but there is no such import,
335                // then we should attempt to resolve the symbol as a global module name, as it
336                // may simply be an unqualified module path.
337                let path = Path::new(symbol.into_inner());
338                match self.get_module_index_by_path(path) {
339                    // Success
340                    Some(found) => Ok(SymbolResolution::Module {
341                        id: found,
342                        path: Span::new(symbol.span(), self.module_path(found).into()),
343                    }),
344                    // No such module known to the linker, must be an invalid path
345                    None => Err(err.into()),
346                }
347            },
348            Err(err) => Err(err.into()),
349        }
350    }
351
352    pub fn resolve_local(
353        &self,
354        context: &SymbolResolutionContext,
355        symbol: &str,
356    ) -> Result<SymbolResolution, Box<SymbolResolutionError>> {
357        let module = if context.in_syscall() {
358            // Resolve local names relative to the kernel
359            match self.graph.kernel_index {
360                Some(kernel) => kernel,
361                None => {
362                    return Err(Box::new(SymbolResolutionError::UndefinedSymbol {
363                        span: context.span,
364                        source_file: self.source_manager().get(context.span.source_id()).ok(),
365                    }));
366                },
367            }
368        } else {
369            context.module
370        };
371        self.resolve_local_with_index(module, Span::new(context.span, symbol))
372    }
373
374    fn resolve_local_with_index(
375        &self,
376        module: ModuleIndex,
377        symbol: Span<&str>,
378    ) -> Result<SymbolResolution, Box<SymbolResolutionError>> {
379        let module = &self.graph[module];
380        log::debug!(target: "name-resolver::local", "resolving '{symbol}' in module {}", module.path());
381        log::debug!(target: "name-resolver::local", "module status: {:?}", &module.status());
382        module.resolve(symbol, self)
383    }
384
385    /// Resolve `callee` to its concrete definition, returning the corresponding
386    /// [GlobalItemIndex].
387    ///
388    /// If an error occurs during resolution, or the name cannot be resolved, `Err` is returned.
389    fn find(
390        &self,
391        context: &SymbolResolutionContext,
392        path: Span<&Path>,
393    ) -> Result<SymbolResolution, LinkerError> {
394        // If the caller is a syscall, set the invoke kind to `ProcRef` until we have resolved the
395        // procedure, then verify that it is in the kernel module. This bypasses validation until
396        // after resolution
397        let mut current_context = if context.in_syscall() {
398            let mut caller = context.clone();
399            caller.kind = Some(InvokeKind::ProcRef);
400            Cow::Owned(caller)
401        } else {
402            Cow::Borrowed(context)
403        };
404        let mut resolving = path.map(|p| Arc::<Path>::from(p.to_path_buf()));
405        let mut visited = BTreeSet::default();
406        loop {
407            let current_module_path = self.module_path(current_context.module);
408            log::debug!(target: "name-resolver::find", "resolving {} of {resolving} from {} ({current_module_path})", current_context.display_kind(), &current_context.module);
409
410            let (resolving_symbol, resolving_parent) = resolving.split_last().unwrap();
411            let resolving_symbol = Span::new(resolving.span(), resolving_symbol);
412
413            // Handle trivial case where we are resolving the current module path in the context of
414            // that module.
415            if current_module_path == &**resolving {
416                return Ok(SymbolResolution::Module {
417                    id: current_context.module,
418                    path: resolving,
419                });
420            }
421
422            // Handle the case where we are resolving a symbol in the current module
423            if resolving_parent == current_module_path {
424                match self.find_local(
425                    &current_context,
426                    resolving_symbol,
427                    resolving.inner().clone(),
428                    &mut visited,
429                ) {
430                    ControlFlow::Break(result) => break result,
431                    ControlFlow::Continue(LocalFindResult { context, resolving: next }) => {
432                        current_context = Cow::Owned(context);
433                        resolving = next;
434                        continue;
435                    },
436                }
437            }
438
439            // There are three possibilities at this point
440            //
441            // 1. `resolving` refers to a module, and that is how we will resolve it
442            // 2. `resolving` refers to an item, so we need to resolve `resolving_parent` to a
443            //    module first, then resolve `resolving_symbol` relative to that module.
444            // 3. `resolving` refers to an undefined symbol
445
446            // First, check if `resolving` refers to a module in the global module table
447            if let Some(id) =
448                self.find_module_index(current_context.module, resolving.as_deref())?
449            {
450                log::debug!(target: "name-resolver::find", "resolved '{resolving}' to module {id} ({})", self.module_path(id));
451                return Ok(SymbolResolution::Module {
452                    id,
453                    path: Span::new(resolving.span(), self.module_path(id).to_path_buf().into()),
454                });
455            }
456
457            // We must assume that `resolving` is an item path, so we resolve `resolving_parent` as
458            // a module first, and proceed from there.
459            log::debug!(target: "name-resolver::find", "resolving '{resolving_parent}' from {} ({current_module_path})", current_context.module);
460            let module_index = self
461                .find_module_index(
462                    current_context.module,
463                    Span::new(resolving.span(), resolving_parent),
464                )?
465                .ok_or_else(|| {
466                    // If we couldn't resolve `resolving_parent` as a module either, then
467                    // `resolving` must be an undefined symbol path.
468                    LinkerError::UndefinedModule {
469                        span: current_context.span,
470                        source_file: self
471                            .graph
472                            .source_manager
473                            .get(current_context.span.source_id())
474                            .ok(),
475                        path: (*resolving).clone(),
476                    }
477                })?;
478            log::debug!(target: "name-resolver::find", "resolved '{resolving_parent}' to module {module_index} ({})", self.module_path(module_index));
479
480            log::debug!(target: "name-resolver::find", "resolving {resolving_symbol} in module {resolving_parent}");
481            let context = SymbolResolutionContext {
482                module: module_index,
483                span: current_context.span,
484                kind: current_context.kind,
485            };
486            match self.find_local(
487                &context,
488                resolving_symbol,
489                resolving.inner().clone(),
490                &mut visited,
491            ) {
492                ControlFlow::Break(result) => break result,
493                ControlFlow::Continue(LocalFindResult { context, resolving: next }) => {
494                    current_context = Cow::Owned(context);
495                    resolving = next;
496                },
497            }
498        }
499    }
500
501    fn find_local(
502        &self,
503        context: &SymbolResolutionContext,
504        symbol: Span<&str>,
505        resolving: Arc<Path>,
506        visited: &mut BTreeSet<Span<Arc<Path>>>,
507    ) -> ControlFlow<Result<SymbolResolution, LinkerError>, LocalFindResult> {
508        let resolved = self.resolve_local_with_index(context.module, symbol);
509        match resolved {
510            Ok(SymbolResolution::Local(index)) => {
511                log::debug!(target: "name-resolver::find", "resolved {symbol} to local item {index}");
512                let gid = GlobalItemIndex {
513                    module: context.module,
514                    index: index.into_inner(),
515                };
516                if context.in_syscall() && self.graph.kernel_index != Some(context.module) {
517                    return ControlFlow::Break(Err(LinkerError::InvalidSysCallTarget {
518                        span: context.span,
519                        source_file: self.graph.source_manager.get(context.span.source_id()).ok(),
520                        callee: resolving,
521                    }));
522                }
523                ControlFlow::Break(Ok(SymbolResolution::Exact {
524                    gid,
525                    path: Span::new(index.span(), self.item_path(gid)),
526                }))
527            },
528            Ok(SymbolResolution::External(fqn)) => {
529                log::debug!(target: "name-resolver::find", "resolved {symbol} to external path {fqn}");
530                // If we see that we're about to enter an infinite resolver loop because of a
531                // recursive alias, return an error
532                if !visited.insert(fqn.clone()) {
533                    ControlFlow::Break(Err(LinkerError::Failed {
534                                    labels: vec![
535                                        RelatedLabel::error("recursive alias")
536                                            .with_source_file(self.graph.source_manager.get(fqn.span().source_id()).ok())
537                                            .with_labeled_span(fqn.span(), "occurs because this import causes import resolution to loop back on itself"),
538                                        RelatedLabel::advice("recursive alias")
539                                            .with_source_file(self.graph.source_manager.get(context.span.source_id()).ok())
540                                            .with_labeled_span(context.span, "as a result of resolving this procedure reference"),
541                                    ].into(),
542                                }))
543                } else {
544                    ControlFlow::Continue(LocalFindResult {
545                        context: SymbolResolutionContext {
546                            span: fqn.span(),
547                            module: context.module,
548                            kind: context.kind,
549                        },
550                        resolving: fqn,
551                    })
552                }
553            },
554            Ok(SymbolResolution::MastRoot(ref digest)) => {
555                log::debug!(target: "name-resolver::find", "resolved {symbol} to MAST root {digest}");
556                if let Some(gid) = self.graph.get_procedure_index_by_digest(digest) {
557                    ControlFlow::Break(Ok(SymbolResolution::Exact {
558                        gid,
559                        path: Span::new(digest.span(), self.item_path(gid)),
560                    }))
561                } else {
562                    // This is a phantom procedure - we know its root, but do not have its
563                    // definition
564                    ControlFlow::Break(Err(LinkerError::Failed {
565                        labels: vec![
566                            RelatedLabel::error("undefined procedure")
567                                .with_source_file(
568                                    self.graph.source_manager.get(context.span.source_id()).ok(),
569                                )
570                                .with_labeled_span(
571                                    context.span,
572                                    "unable to resolve this reference to its definition",
573                                ),
574                            RelatedLabel::error("name resolution cannot proceed")
575                                .with_source_file(
576                                    self.graph.source_manager.get(symbol.span().source_id()).ok(),
577                                )
578                                .with_labeled_span(symbol.span(), "this name cannot be resolved"),
579                        ]
580                        .into(),
581                    }))
582                }
583            },
584            Ok(res @ (SymbolResolution::Exact { .. } | SymbolResolution::Module { .. })) => {
585                ControlFlow::Break(Ok(res))
586            },
587            Err(err) if context.in_syscall() => {
588                if let SymbolResolutionError::UndefinedSymbol { .. } = &*err {
589                    log::debug!(target: "name-resolver::find", "unable to resolve {symbol}");
590                    if self.graph.has_nonempty_kernel() {
591                        // No kernel, so this invoke is invalid anyway
592                        ControlFlow::Break(Err(LinkerError::Failed {
593                                        labels: vec![
594                                            RelatedLabel::error("undefined kernel procedure")
595                                                .with_source_file(self.graph.source_manager.get(context.span.source_id()).ok())
596                                                .with_labeled_span(context.span, "unable to resolve this reference to a procedure in the current kernel"),
597                                            RelatedLabel::error("invalid syscall")
598                                                .with_source_file(self.graph.source_manager.get(symbol.span().source_id()).ok())
599                                                .with_labeled_span(
600                                                    symbol.span(),
601                                                    "this name cannot be resolved, because the assembler has an empty kernel",
602                                                ),
603                                        ].into()
604                                    }))
605                    } else {
606                        // No such kernel procedure
607                        ControlFlow::Break(Err(LinkerError::Failed {
608                                        labels: vec![
609                                            RelatedLabel::error("undefined kernel procedure")
610                                                .with_source_file(self.graph.source_manager.get(context.span.source_id()).ok())
611                                                .with_labeled_span(context.span, "unable to resolve this reference to a procedure in the current kernel"),
612                                            RelatedLabel::error("name resolution cannot proceed")
613                                                .with_source_file(self.graph.source_manager.get(symbol.span().source_id()).ok())
614                                                .with_labeled_span(
615                                                    symbol.span(),
616                                                    "this name cannot be resolved",
617                                                ),
618                                        ].into()
619                                    }))
620                    }
621                } else {
622                    ControlFlow::Break(Err(LinkerError::SymbolResolution(err)))
623                }
624            },
625            Err(err) => {
626                if matches!(&*err, SymbolResolutionError::UndefinedSymbol { .. }) {
627                    log::debug!(target: "name-resolver::find", "unable to resolve {symbol}");
628                    // No such procedure known to `module`
629                    ControlFlow::Break(Err(LinkerError::Failed {
630                        labels: vec![
631                            RelatedLabel::error("undefined item")
632                                .with_source_file(
633                                    self.graph.source_manager.get(context.span.source_id()).ok(),
634                                )
635                                .with_labeled_span(
636                                    context.span,
637                                    "unable to resolve this reference to its definition",
638                                ),
639                            RelatedLabel::error("name resolution cannot proceed")
640                                .with_source_file(
641                                    self.graph.source_manager.get(symbol.span().source_id()).ok(),
642                                )
643                                .with_labeled_span(symbol.span(), "this name cannot be resolved"),
644                        ]
645                        .into(),
646                    }))
647                } else {
648                    ControlFlow::Break(Err(LinkerError::SymbolResolution(err)))
649                }
650            },
651        }
652    }
653
654    /// Resolve a [Path] from `src` to a [ModuleIndex] in this graph
655    fn find_module_index(
656        &self,
657        src: ModuleIndex,
658        path: Span<&Path>,
659    ) -> Result<Option<ModuleIndex>, Box<SymbolResolutionError>> {
660        log::debug!(target: "name-resolver", "looking up module index for {path} in context of {src}");
661        let found = self.get_module_index_by_path(path.inner());
662        if found.is_some() {
663            return Ok(found);
664        }
665
666        if path.is_absolute() {
667            log::debug!(target: "name-resolver", "{path} is not in the global module table, must be an item path");
668            return Ok(None);
669        }
670
671        log::debug!(target: "name-resolver", "{path} is not in the global module table");
672        log::debug!(target: "name-resolver", "checking if {path} is resolvable via imports in {src}");
673        // The path might be relative to a local import/alias, so attempt to resolve it as such
674        // relative to `src`, but only if `name` is a path with a single component
675        let src_module = &self.graph[src];
676        let resolved_item = src_module.resolve_path(path, self)?;
677        match resolved_item {
678            SymbolResolution::External(path) => {
679                let path = path.to_absolute();
680                Ok(self.get_module_index_by_path(&path))
681            },
682            SymbolResolution::Local(item) => {
683                Err(Box::new(SymbolResolutionError::invalid_symbol_type(
684                    path.span(),
685                    "module",
686                    item.span(),
687                    self.source_manager(),
688                )))
689            },
690            SymbolResolution::MastRoot(item) => {
691                Err(Box::new(SymbolResolutionError::invalid_symbol_type(
692                    path.span(),
693                    "module",
694                    item.span(),
695                    self.source_manager(),
696                )))
697            },
698            SymbolResolution::Exact { gid, .. } => Ok(Some(gid.module)),
699            SymbolResolution::Module { id, .. } => Ok(Some(id)),
700        }
701    }
702
703    fn get_module_index_by_path(&self, path: &Path) -> Option<ModuleIndex> {
704        let path = path.to_absolute();
705        log::debug!(target: "name-resolver", "looking up module index for global symbol {path}");
706        self.graph.modules.iter().find_map(|m| {
707            log::debug!(target: "name-resolver::get_module_index_by_path", "checking against {}: {}", m.path(), path.as_ref() == m.path());
708            if path.as_ref() == m.path() {
709                Some(m.id())
710            } else {
711                None
712            }
713        })
714    }
715
716    #[inline]
717    pub fn module_path(&self, module: ModuleIndex) -> &Path {
718        self.graph[module].path()
719    }
720
721    pub fn item_path(&self, item: GlobalItemIndex) -> Arc<Path> {
722        let module = &self.graph[item.module];
723        let name = module[item.index].name();
724        module.path().join(name).into()
725    }
726}
727
728struct LocalFindResult {
729    context: SymbolResolutionContext,
730    resolving: Span<Arc<Path>>,
731}