sway_core/language/
call_path.rs

1use crate::{
2    engine_threading::{
3        DebugWithEngines, DisplayWithEngines, EqWithEngines, HashWithEngines, OrdWithEngines,
4        OrdWithEnginesContext, PartialEqWithEngines, PartialEqWithEnginesContext,
5    },
6    parsed::QualifiedPathType,
7    Engines, GenericArgument, Ident, Namespace,
8};
9use serde::{Deserialize, Serialize};
10use std::{
11    cmp::Ordering,
12    fmt,
13    hash::{Hash, Hasher},
14    sync::Arc,
15};
16use sway_error::{
17    error::CompileError,
18    handler::{ErrorEmitted, Handler},
19};
20use sway_types::{span::Span, Spanned};
21
22#[derive(Clone, Debug, Serialize, Deserialize)]
23pub struct CallPathTree {
24    pub qualified_call_path: QualifiedCallPath,
25    pub children: Vec<CallPathTree>,
26}
27
28impl HashWithEngines for CallPathTree {
29    fn hash<H: Hasher>(&self, state: &mut H, engines: &Engines) {
30        let CallPathTree {
31            qualified_call_path,
32            children,
33        } = self;
34        qualified_call_path.hash(state, engines);
35        children.hash(state, engines);
36    }
37}
38
39impl EqWithEngines for CallPathTree {}
40impl PartialEqWithEngines for CallPathTree {
41    fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool {
42        let CallPathTree {
43            qualified_call_path,
44            children,
45        } = self;
46        qualified_call_path.eq(&other.qualified_call_path, ctx) && children.eq(&other.children, ctx)
47    }
48}
49
50impl<T: PartialEqWithEngines> EqWithEngines for Vec<T> {}
51impl<T: PartialEqWithEngines> PartialEqWithEngines for Vec<T> {
52    fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool {
53        if self.len() != other.len() {
54            return false;
55        }
56        self.iter().zip(other.iter()).all(|(a, b)| a.eq(b, ctx))
57    }
58}
59
60impl OrdWithEngines for CallPathTree {
61    fn cmp(&self, other: &Self, ctx: &OrdWithEnginesContext) -> Ordering {
62        let CallPathTree {
63            qualified_call_path: l_call_path,
64            children: l_children,
65        } = self;
66        let CallPathTree {
67            qualified_call_path: r_call_path,
68            children: r_children,
69        } = other;
70        l_call_path
71            .cmp(r_call_path, ctx)
72            .then_with(|| l_children.cmp(r_children, ctx))
73    }
74}
75
76#[derive(Clone, Debug, Serialize, Deserialize)]
77
78pub struct QualifiedCallPath {
79    pub call_path: CallPath,
80    pub qualified_path_root: Option<Box<QualifiedPathType>>,
81}
82
83impl std::convert::From<Ident> for QualifiedCallPath {
84    fn from(other: Ident) -> Self {
85        QualifiedCallPath {
86            call_path: CallPath {
87                prefixes: vec![],
88                suffix: other,
89                callpath_type: CallPathType::Ambiguous,
90            },
91            qualified_path_root: None,
92        }
93    }
94}
95
96impl std::convert::From<CallPath> for QualifiedCallPath {
97    fn from(other: CallPath) -> Self {
98        QualifiedCallPath {
99            call_path: other,
100            qualified_path_root: None,
101        }
102    }
103}
104
105impl QualifiedCallPath {
106    pub fn to_call_path(self, handler: &Handler) -> Result<CallPath, ErrorEmitted> {
107        if let Some(qualified_path_root) = self.qualified_path_root {
108            Err(handler.emit_err(CompileError::Internal(
109                "Unexpected qualified path.",
110                qualified_path_root.as_trait_span,
111            )))
112        } else {
113            Ok(self.call_path)
114        }
115    }
116}
117
118impl HashWithEngines for QualifiedCallPath {
119    fn hash<H: Hasher>(&self, state: &mut H, engines: &Engines) {
120        let QualifiedCallPath {
121            call_path,
122            qualified_path_root,
123        } = self;
124        call_path.hash(state);
125        qualified_path_root.hash(state, engines);
126    }
127}
128
129impl EqWithEngines for QualifiedCallPath {}
130impl PartialEqWithEngines for QualifiedCallPath {
131    fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool {
132        let QualifiedCallPath {
133            call_path,
134            qualified_path_root,
135        } = self;
136        PartialEqWithEngines::eq(call_path, &other.call_path, ctx)
137            && qualified_path_root.eq(&other.qualified_path_root, ctx)
138    }
139}
140
141impl OrdWithEngines for QualifiedCallPath {
142    fn cmp(&self, other: &Self, ctx: &OrdWithEnginesContext) -> Ordering {
143        let QualifiedCallPath {
144            call_path: l_call_path,
145            qualified_path_root: l_qualified_path_root,
146        } = self;
147        let QualifiedCallPath {
148            call_path: r_call_path,
149            qualified_path_root: r_qualified_path_root,
150        } = other;
151        l_call_path
152            .cmp(r_call_path)
153            .then_with(|| l_qualified_path_root.cmp(r_qualified_path_root, ctx))
154    }
155}
156
157impl DisplayWithEngines for QualifiedCallPath {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
159        if let Some(qualified_path_root) = &self.qualified_path_root {
160            write!(
161                f,
162                "{}::{}",
163                engines.help_out(qualified_path_root),
164                &self.call_path
165            )
166        } else {
167            write!(f, "{}", &self.call_path)
168        }
169    }
170}
171
172impl DebugWithEngines for QualifiedCallPath {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
174        write!(f, "{}", engines.help_out(self))
175    }
176}
177
178#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
179pub enum CallPathType {
180    /// An unresolved path on the form `::X::Y::Z`. The path must be resolved relative to the
181    /// current package root module.
182    /// The path can be converted to a full path by prepending the package name, so if the path
183    /// `::X::Y::Z` occurs in package `A`, then the corresponding full path will be `A::X::Y::Z`.
184    RelativeToPackageRoot,
185    /// An unresolved path on the form `X::Y::Z`. The path must either be resolved relative to the
186    /// current module, in which case `X` is either a submodule or a name bound in the current
187    /// module, or as a full path, in which case `X` is the name of an external package.
188    /// If the path is resolved relative to the current module, and the current module has a module
189    /// path `A::B::C`, then the corresponding full path is `A::B::C::X::Y::Z`.
190    /// If the path is resolved as a full path, then the full path is `X::Y::Z`.
191    Ambiguous,
192    /// A full path on the form `X::Y::Z`. The first identifier `X` is the name of either the
193    /// current package or an external package.
194    /// After that comes a (possibly empty) series of names of submodules. Then comes the name of an
195    /// item (a type, a trait, a function, or something else declared in that module). Additionally,
196    /// there may be additional names such as the name of an enum variant or associated types.
197    Full,
198}
199
200/// In the expression `a::b::c()`, `a` and `b` are the prefixes and `c` is the suffix.
201/// `c` can be any type `T`, but in practice `c` is either an `Ident` or a `TypeInfo`.
202#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
203pub struct CallPath<T = Ident> {
204    pub prefixes: Vec<Ident>,
205    pub suffix: T,
206    pub callpath_type: CallPathType,
207}
208
209impl EqWithEngines for CallPath {}
210impl PartialEqWithEngines for CallPath {
211    fn eq(&self, other: &Self, _ctx: &PartialEqWithEnginesContext) -> bool {
212        self.prefixes == other.prefixes
213            && self.suffix == other.suffix
214            && self.callpath_type == other.callpath_type
215    }
216}
217
218impl<T: EqWithEngines> EqWithEngines for CallPath<T> {}
219impl<T: PartialEqWithEngines> PartialEqWithEngines for CallPath<T> {
220    fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool {
221        self.prefixes == other.prefixes
222            && self.suffix.eq(&other.suffix, ctx)
223            && self.callpath_type == other.callpath_type
224    }
225}
226
227impl<T: OrdWithEngines> OrdWithEngines for CallPath<T> {
228    fn cmp(&self, other: &Self, ctx: &OrdWithEnginesContext) -> Ordering {
229        self.prefixes
230            .cmp(&other.prefixes)
231            .then_with(|| self.suffix.cmp(&other.suffix, ctx))
232            .then_with(|| self.callpath_type.cmp(&other.callpath_type))
233    }
234}
235
236#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
237pub struct ResolvedCallPath<T, U = Ident> {
238    pub decl: T,
239    pub unresolved_call_path: CallPath<U>,
240}
241
242impl std::convert::From<Ident> for CallPath {
243    fn from(other: Ident) -> Self {
244        CallPath {
245            prefixes: vec![],
246            suffix: other,
247            callpath_type: CallPathType::Ambiguous,
248        }
249    }
250}
251
252impl<T> fmt::Display for CallPath<T>
253where
254    T: fmt::Display,
255{
256    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257        // TODO: Remove this workaround once https://github.com/FuelLabs/sway/issues/7304 is fixed
258        //       and uncomment the original code below.
259
260        if let Some((first_prefix, rest_prefixes)) = self.prefixes.split_first() {
261            let first_prefix = if !first_prefix.as_str().contains('-') {
262                first_prefix.as_str()
263            } else {
264                &first_prefix.as_str().replace('-', "_")
265            };
266            write!(f, "{first_prefix}::")?;
267            for prefix in rest_prefixes {
268                write!(f, "{}::", prefix.as_str())?;
269            }
270        }
271        write!(f, "{}", &self.suffix)
272
273        // for prefix in self.prefixes.iter() {
274        //     write!(f, "{}::", prefix.as_str())?;
275        // }
276        // write!(f, "{}", &self.suffix)
277    }
278}
279
280impl<T: DisplayWithEngines> DisplayWithEngines for CallPath<T> {
281    fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
282        // TODO: Remove this workaround once https://github.com/FuelLabs/sway/issues/7304 is fixed
283        //       and uncomment the original code below.
284
285        if let Some((first_prefix, rest_prefixes)) = self.prefixes.split_first() {
286            let first_prefix = if !first_prefix.as_str().contains('-') {
287                first_prefix.as_str()
288            } else {
289                &first_prefix.as_str().replace('-', "_")
290            };
291            write!(f, "{first_prefix}::")?;
292            for prefix in rest_prefixes {
293                write!(f, "{}::", prefix.as_str())?;
294            }
295        }
296        write!(f, "{}", engines.help_out(&self.suffix))
297
298        // for prefix in self.prefixes.iter() {
299        //     write!(f, "{}::", prefix.as_str())?;
300        // }
301        // write!(f, "{}", engines.help_out(&self.suffix))
302    }
303}
304
305impl<T: DisplayWithEngines> DebugWithEngines for CallPath<T> {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
307        for prefix in self.prefixes.iter() {
308            write!(f, "{}::", prefix.as_str())?;
309        }
310        write!(f, "{}", engines.help_out(&self.suffix))
311    }
312}
313
314impl<T: Spanned> Spanned for CallPath<T> {
315    fn span(&self) -> Span {
316        if self.prefixes.is_empty() {
317            self.suffix.span()
318        } else {
319            let suffix_span = self.suffix.span();
320            let mut prefixes_spans = self
321                .prefixes
322                .iter()
323                .map(|x| x.span())
324                // Depending on how the call path is constructed, we
325                // might have a situation that the parts do not belong
326                // to the same source and do not have the same source id.
327                // In that case, we will take only the suffix' span, as
328                // the span for the whole call path. Otherwise, we join
329                // the spans of all the parts.
330                .filter(|x| {
331                    Arc::ptr_eq(&x.src().text, &suffix_span.src().text)
332                        && x.source_id() == suffix_span.source_id()
333                })
334                .peekable();
335            if prefixes_spans.peek().is_some() {
336                Span::join(Span::join_all(prefixes_spans), &suffix_span)
337            } else {
338                suffix_span
339            }
340        }
341    }
342}
343
344/// This controls the type of display type for call path display string conversions.
345pub enum CallPathDisplayType {
346    /// Prints the regular call path as exists internally.
347    Regular,
348    /// Strips the current root package if it exists as prefix.
349    StripPackagePrefix,
350}
351
352impl CallPath {
353    pub fn fullpath(path: &[&str]) -> Self {
354        assert!(!path.is_empty());
355
356        CallPath {
357            prefixes: path
358                .iter()
359                .take(path.len() - 1)
360                .map(|&x| Ident::new_no_span(x.into()))
361                .collect(),
362            suffix: path.last().map(|&x| Ident::new_no_span(x.into())).unwrap(),
363            callpath_type: CallPathType::Full,
364        }
365    }
366
367    /// Shifts the last prefix into the suffix, and removes the old suffix.
368    /// Does nothing if prefixes are empty, or if the path is a full path and there is only a single prefix (which must be the package name, which is obligatory for full paths)
369    pub fn rshift(&self) -> CallPath {
370        if self.prefixes.is_empty()
371            || (matches!(self.callpath_type, CallPathType::Full) && self.prefixes.len() == 1)
372        {
373            self.clone()
374        } else {
375            CallPath {
376                prefixes: self.prefixes[0..self.prefixes.len() - 1].to_vec(),
377                suffix: self.prefixes.last().unwrap().clone(),
378                callpath_type: self.callpath_type,
379            }
380        }
381    }
382
383    /// Removes the first prefix. Does nothing if prefixes are empty.
384    pub fn lshift(&self) -> CallPath {
385        if self.prefixes.is_empty() {
386            self.clone()
387        } else {
388            let new_callpath_type = match self.callpath_type {
389                CallPathType::RelativeToPackageRoot | CallPathType::Ambiguous => {
390                    CallPathType::Ambiguous
391                }
392                CallPathType::Full => CallPathType::RelativeToPackageRoot,
393            };
394            CallPath {
395                prefixes: self.prefixes[1..self.prefixes.len()].to_vec(),
396                suffix: self.suffix.clone(),
397                callpath_type: new_callpath_type,
398            }
399        }
400    }
401
402    pub fn as_vec_string(&self) -> Vec<String> {
403        self.prefixes
404            .iter()
405            .map(|p| p.to_string())
406            .chain(std::iter::once(self.suffix.to_string()))
407            .collect::<Vec<_>>()
408    }
409
410    pub fn as_vec_ident(&self) -> Vec<Ident> {
411        self.as_vec_string()
412            .iter()
413            .map(|s| Ident::new_no_span(s.clone()))
414            .collect::<Vec<_>>()
415    }
416
417    /// Create a full [CallPath] from a given [Ident] and the [Namespace] in which the [Ident] is
418    /// declared.
419    ///
420    /// This function is intended to be used while typechecking the identifier declaration, i.e.,
421    /// before the identifier is added to the environment.
422    pub fn ident_to_fullpath(suffix: Ident, namespace: &Namespace) -> CallPath {
423        let mut res: Self = suffix.clone().into();
424        for mod_path in namespace.current_mod_path() {
425            res.prefixes.push(mod_path.clone())
426        }
427        res.callpath_type = CallPathType::Full;
428        res
429    }
430
431    /// Convert a given [CallPath] into a call path suitable for a `use` statement.
432    ///
433    /// For example, given a path `pkga::SOME_CONST` where `pkga` is an _internal_ library of a package named
434    /// `my_project`, the corresponding call path is `pkga::SOME_CONST`.
435    ///
436    /// Paths to _external_ libraries such `std::lib1::lib2::my_obj` are left unchanged.
437    pub fn to_import_path(&self, engines: &Engines, namespace: &Namespace) -> CallPath {
438        let converted = self.to_fullpath(engines, namespace);
439
440        if let Some(first) = converted.prefixes.first() {
441            if namespace.current_package_name() == first {
442                return converted.lshift();
443            }
444        }
445        converted
446    }
447
448    pub fn to_display_path(
449        &self,
450        display_type: CallPathDisplayType,
451        namespace: &Namespace,
452    ) -> CallPath {
453        let mut display_path = self.clone();
454
455        match display_type {
456            CallPathDisplayType::Regular => {}
457            CallPathDisplayType::StripPackagePrefix => {
458                if let Some(first) = self.prefixes.first() {
459                    if namespace.current_package_name() == first {
460                        display_path = display_path.lshift();
461                    }
462                }
463            }
464        };
465
466        display_path
467    }
468
469    /// Create a string form of the given [CallPath] and zero or more [TypeArgument]s.
470    /// The returned string is convenient for displaying full names, including generic arguments, in help messages.
471    /// E.g.:
472    /// - `some::module::SomeType`
473    /// - `some::module::SomeGenericType<T, u64>`
474    ///
475    /// Note that the trailing arguments are never separated by `::` from the suffix.
476    pub(crate) fn to_string_with_args(
477        &self,
478        engines: &Engines,
479        args: &[GenericArgument],
480    ) -> String {
481        let args = args
482            .iter()
483            .map(|type_arg| engines.help_out(type_arg).to_string())
484            .collect::<Vec<_>>()
485            .join(", ");
486
487        format!(
488            "{}{}",
489            // TODO: Replace with a context aware string representation of the path
490            //       once https://github.com/FuelLabs/sway/issues/6873 is fixed.
491            &self,
492            if args.is_empty() {
493                String::new()
494            } else {
495                format!("<{args}>")
496            }
497        )
498    }
499}
500
501impl<T: Clone> CallPath<T> {
502    /// Convert a given [CallPath] to a symbol to a full [CallPath] to a program point in which the
503    /// symbol can be resolved (assuming the given [CallPath] is a legal Sway path).
504    ///
505    /// The resulting [CallPath] is not guaranteed to be located in the package where the symbol is
506    /// declared. To obtain the path to the declaration, use [to_canonical_path].
507    ///
508    /// The [CallPath] is converted within the current module of the supplied namespace.
509    ///
510    /// For example, given a path `pkga::SOME_CONST` where `pkga` is an _internal_ module of a
511    /// package named `my_project`, the corresponding call path is
512    /// `my_project::pkga::SOME_CONST`. This does not imply that `SOME_CONST` is declared in the
513    /// `my_project::pkga`, but only that the name `SOME_CONST` is bound in `my_project::pkga`.
514    ///
515    /// Paths to _external_ libraries such `std::lib1::lib2::my_obj` are considered full already
516    /// and are left unchanged since `std` is a root of the package `std`.
517    pub fn to_fullpath(&self, engines: &Engines, namespace: &Namespace) -> CallPath<T> {
518        self.to_fullpath_from_mod_path(engines, namespace, namespace.current_mod_path())
519    }
520
521    /// Convert a given [CallPath] to a symbol to a full [CallPath] to a program point in which the
522    /// symbol can be resolved (assuming the given [CallPath] is a legal Sway path).
523    ///
524    /// The resulting [CallPath] is not guaranteed to be located in the package where the symbol is
525    /// declared. To obtain the path to the declaration, use [to_canonical_path].
526    ///
527    /// The [CallPath] is converted within the module given by `mod_path`, which must be a legal
528    /// path to a module.
529    ///
530    /// For example, given a path `pkga::SOME_CONST` where `pkga` is an _internal_ module of a
531    /// package named `my_project`, the corresponding call path is
532    /// `my_project::pkga::SOME_CONST`. This does not imply that `SOME_CONST` is declared in the
533    /// `my_project::pkga`, but only that the name `SOME_CONST` is bound in `my_project::pkga`.
534    ///
535    /// Paths to _external_ libraries such `std::lib1::lib2::my_obj` are considered full already
536    /// and are left unchanged since `std` is a root of the package `std`.
537    pub fn to_fullpath_from_mod_path(
538        &self,
539        engines: &Engines,
540        namespace: &Namespace,
541        mod_path: &Vec<Ident>,
542    ) -> CallPath<T> {
543        let mod_path_module = namespace.module_from_absolute_path(mod_path);
544
545        match self.callpath_type {
546            CallPathType::Full => self.clone(),
547            CallPathType::RelativeToPackageRoot => {
548                let mut prefixes = vec![mod_path[0].clone()];
549                for ident in self.prefixes.iter() {
550                    prefixes.push(ident.clone());
551                }
552                Self {
553                    prefixes,
554                    suffix: self.suffix.clone(),
555                    callpath_type: CallPathType::Full,
556                }
557            }
558            CallPathType::Ambiguous => {
559                if self.prefixes.is_empty() {
560                    // Given a path to a symbol that has no prefixes, discover the path to the symbol as a
561                    // combination of the package name in which the symbol is defined and the path to the
562                    // current submodule.
563                    CallPath {
564                        prefixes: mod_path.clone(),
565                        suffix: self.suffix.clone(),
566                        callpath_type: CallPathType::Full,
567                    }
568                } else if mod_path_module.is_some()
569                    && (mod_path_module.unwrap().has_submodule(&self.prefixes[0])
570                        || namespace.module_has_binding(engines, mod_path, &self.prefixes[0]))
571                {
572                    // The first identifier in the prefix is a submodule of the current
573                    // module.
574                    //
575                    // The path is a qualified path relative to the current module
576                    //
577                    // Complete the path by prepending the package name and the path to the current module.
578                    CallPath {
579                        prefixes: mod_path.iter().chain(&self.prefixes).cloned().collect(),
580                        suffix: self.suffix.clone(),
581                        callpath_type: CallPathType::Full,
582                    }
583                } else if namespace.package_exists(&self.prefixes[0])
584                    && namespace.module_is_external(&self.prefixes)
585                {
586                    // The first identifier refers to an external package. The path is already fully qualified.
587                    CallPath {
588                        prefixes: self.prefixes.clone(),
589                        suffix: self.suffix.clone(),
590                        callpath_type: CallPathType::Full,
591                    }
592                } else {
593                    // The first identifier in the prefix is neither a submodule of the current module nor the name of an external package.
594                    // This is probably an illegal path, so let it fail by assuming it is bound in the current module.
595                    CallPath {
596                        prefixes: mod_path.iter().chain(&self.prefixes).cloned().collect(),
597                        suffix: self.suffix.clone(),
598                        callpath_type: CallPathType::Full,
599                    }
600                }
601            }
602        }
603    }
604}
605
606impl CallPath {
607    /// Convert a given [CallPath] to a symbol to a full [CallPath] to where the symbol is declared
608    /// (assuming the given [CallPath] is a legal Sway path).
609    ///
610    /// The [CallPath] is converted within the current module of the supplied namespace.
611    ///
612    /// For example, given a path `pkga::SOME_CONST` where `pkga` is an _internal_ module of a
613    /// package named `my_project`, and `SOME_CONST` is bound in the module `my_project::pkga`, then
614    /// the corresponding call path is the full callpath to the declaration that `SOME_CONST` is
615    /// bound to. This does not imply that `SOME_CONST` is declared in the `my_project::pkga`, since
616    /// the binding may be the result of an import.
617    ///
618    /// Paths to _external_ libraries such `std::lib1::lib2::my_obj` are considered full already
619    /// and are left unchanged since `std` is a root of the package `std`.
620    pub fn to_canonical_path(&self, engines: &Engines, namespace: &Namespace) -> CallPath {
621        // Generate a full path to a module where the suffix can be resolved
622        let full_path = self.to_fullpath(engines, namespace);
623
624        match namespace.module_from_absolute_path(&full_path.prefixes) {
625            Some(module) => {
626                // Resolve the path suffix in the found module
627                match module.resolve_symbol(&Handler::default(), engines, &full_path.suffix) {
628                    Ok((decl, decl_path)) => {
629                        let name = decl.expect_typed().get_name(engines);
630                        let suffix = if name.as_str() != full_path.suffix.as_str() {
631                            name
632                        } else {
633                            full_path.suffix
634                        };
635                        // Replace the resolvable path with the declaration's path
636                        CallPath {
637                            prefixes: decl_path,
638                            suffix,
639                            callpath_type: full_path.callpath_type,
640                        }
641                    }
642                    Err(_) => {
643                        // The symbol does not resolve. The symbol isn't bound, so the best bet is
644                        // the full path.
645                        full_path
646                    }
647                }
648            }
649            None => {
650                // The resolvable module doesn't exist. The symbol probably doesn't exist, so
651                // the best bet is the full path.
652                full_path
653            }
654        }
655    }
656}