midenc_hir/ir/symbols/
path.rs

1use alloc::{borrow::Cow, collections::VecDeque, format};
2use core::fmt;
3
4use midenc_session::diagnostics::{miette, Diagnostic};
5use smallvec::{smallvec, SmallVec};
6
7use super::SymbolUseRef;
8use crate::{define_attr_type, interner, FunctionIdent, SymbolName};
9
10#[derive(Debug, thiserror::Error, Diagnostic)]
11pub enum InvalidSymbolPathError {
12    #[error("invalid symbol path: cannot be empty")]
13    Empty,
14    #[error("invalid symbol path: invalid format")]
15    #[diagnostic(help(
16        "The grammar for symbols is `<namespace>:<package>[/<export>]*[@<version>]"
17    ))]
18    InvalidFormat,
19    #[error("invalid symbol path: missing package")]
20    #[diagnostic(help(
21        "A fully-qualified symbol must namespace packages, i.e. `<namespace>:<package>`, but \
22         you've only provided one of these"
23    ))]
24    MissingPackage,
25    #[error("invalid symbol path: only fully-qualified symbols can be versioned")]
26    UnexpectedVersion,
27    #[error("invalid symbol path: unexpected character '{token}' at byte {pos}")]
28    UnexpectedToken { token: char, pos: usize },
29    #[error("invalid symbol path: no leaf component was provided")]
30    MissingLeaf,
31    #[error("invalid symbol path: unexpected components found after leaf")]
32    UnexpectedTrailingComponents,
33    #[error("invalid symbol path: only one root component is allowed, and it must come first")]
34    UnexpectedRootPlacement,
35}
36
37#[derive(Clone, PartialEq, Eq)]
38pub struct SymbolPathAttr {
39    pub path: SymbolPath,
40    pub user: SymbolUseRef,
41}
42
43impl fmt::Display for SymbolPathAttr {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}", &self.path)
46    }
47}
48
49impl crate::formatter::PrettyPrint for SymbolPathAttr {
50    fn render(&self) -> crate::formatter::Document {
51        crate::formatter::display(self)
52    }
53}
54
55impl fmt::Debug for SymbolPathAttr {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("SymbolPathAttr")
58            .field("path", &self.path)
59            .field("user", &self.user.borrow())
60            .finish()
61    }
62}
63
64impl core::hash::Hash for SymbolPathAttr {
65    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
66        self.path.hash(state);
67        self.user.hash(state);
68    }
69}
70
71define_attr_type!(SymbolPathAttr);
72
73/// This type is a custom [Attribute] for [Symbol] references.
74///
75/// A [SymbolPath] is represented much like a filesystem path, i.e. as a vector of components.
76/// Each component refers to a distinct `Symbol` that must be resolvable, the details of which
77/// depends on what style of path is used.
78///
79/// Similar to filesystem paths, there are two types of paths supported:
80///
81/// * Unrooted (i.e. relative) paths. These are resolved from the nearest parent `SymbolTable`,
82///   and must terminate with `SymbolNameComponent::Leaf`.
83/// * Absolute paths. The resolution rules for these depends on what the top-level operation is
84///   as reachable from the containing operation, described in more detail below. These paths
85///   must begin with `SymbolNameComponent::Root`.
86///
87/// NOTE: There is no equivalent of the `.` or `..` nodes in a filesystem path in symbol paths,
88/// at least at the moment. Thus there is no way to refer to symbols some arbitrary number of
89/// parents above the current `SymbolTable`, they must be resolved to absolute paths by the
90/// frontend for now.
91///
92/// # Symbol Resolution
93///
94/// Relative paths, as mentioned above, are resolved from the nearest parent `SymbolTable`; if
95/// no `SymbolTable` is present, an error will be raised.
96///
97/// Absolute paths are relatively simple, but supports two use cases, based on the _top-level_
98/// operation reachable from the current operation, i.e. the operation at the top of the
99/// ancestor tree which has no parent:
100///
101/// 1. If the top-level operation is an anonymous `SymbolTable` (i.e. it is not also a `Symbol`),
102///    then that `SymbolTable` corresponds to the global (root) namespace, and symbols are
103///    resolved recursively from there.
104/// 2. If the top-level operation is a named `SymbolTable` (i.e. it is also a `Symbol`), then it
105///    is presumed that the top-level operation is defined in the global (root) namespace, even
106///    though we are unable to reach the global namespace directly. Thus, the symbol we're
107///    trying to resolve _must_ be a descendant of the top-level operation. This implies that
108///    the symbol path of the top-level operation must be a prefix of `path`.
109///
110/// We support the second style to allow for working with more localized chunks of IR, when no
111/// symbol references escape the top-level `SymbolTable`. This is mostly useful in testing
112/// scenarios.
113///
114/// Symbol resolution of absolute paths will fail if:
115///
116/// * The top-level operation is not a `SymbolTable`
117/// * The top-level operation is a `Symbol` whose path is not a prefix of `path`
118/// * We are unable to resolve any component of the path, starting from the top-level
119/// * Any intermediate symbol in the path refers to a `Symbol` which is not also a `SymbolTable`
120#[derive(Clone)]
121pub struct SymbolPath {
122    /// The underlying components of the symbol name (alternatively called the symbol path).
123    pub path: SmallVec<[SymbolNameComponent; 3]>,
124}
125
126impl FromIterator<SymbolNameComponent> for SymbolPath {
127    fn from_iter<I>(iter: I) -> Self
128    where
129        I: IntoIterator<Item = SymbolNameComponent>,
130    {
131        Self {
132            path: SmallVec::from_iter(iter),
133        }
134    }
135}
136
137impl SymbolPath {
138    pub fn new<I>(components: I) -> Result<Self, InvalidSymbolPathError>
139    where
140        I: IntoIterator<Item = SymbolNameComponent>,
141    {
142        let mut path = SmallVec::default();
143
144        let mut components = components.into_iter();
145
146        match components.next() {
147            None => return Err(InvalidSymbolPathError::Empty),
148            Some(component @ (SymbolNameComponent::Root | SymbolNameComponent::Component(_))) => {
149                path.push(component);
150            }
151            Some(component @ SymbolNameComponent::Leaf(_)) => {
152                if components.next().is_some() {
153                    return Err(InvalidSymbolPathError::UnexpectedTrailingComponents);
154                }
155                path.push(component);
156                return Ok(Self { path });
157            }
158        };
159
160        while let Some(component) = components.next() {
161            match component {
162                SymbolNameComponent::Root => {
163                    return Err(InvalidSymbolPathError::UnexpectedRootPlacement);
164                }
165                component @ SymbolNameComponent::Component(_) => {
166                    path.push(component);
167                }
168                component @ SymbolNameComponent::Leaf(_) => {
169                    path.push(component);
170                    if components.next().is_some() {
171                        return Err(InvalidSymbolPathError::UnexpectedTrailingComponents);
172                    }
173                }
174            }
175        }
176
177        Ok(Self { path })
178    }
179
180    /// Converts a [FunctionIdent] representing a fully-qualified Miden Assembly procedure path,
181    /// to it's equivalent [SymbolPath] representation.
182    ///
183    /// # Example
184    ///
185    /// ```rust
186    /// use midenc_hir::{SymbolPath, SymbolNameComponent, FunctionIdent};
187    ///
188    /// let id = FunctionIdent {
189    ///     module: "intrinsics::mem".into(),
190    ///     function: "load_felt_unchecked".into(),
191    /// };
192    /// assert_eq!(
193    ///     SymbolPath::from_masm_function_id(id),
194    ///     SymbolPath::from_iter([
195    ///         SymbolNameComponent::Root,
196    ///         SymbolNameComponent::Component("intrinsics".into()),
197    ///         SymbolNameComponent::Component("mem".into()),
198    ///         SymbolNameComponent::Leaf("load_felt_unchecked".into()),
199    ///     ])
200    /// );
201    /// ```
202    pub fn from_masm_function_id(id: FunctionIdent) -> Self {
203        let mut path = Self::from_masm_module_id(id.module.as_str());
204        path.path.push(SymbolNameComponent::Leaf(id.function.as_symbol()));
205        path
206    }
207
208    /// Converts a [str] representing a fully-qualified Miden Assembly module path, to it's
209    /// equivalent [SymbolPath] representation.
210    ///
211    /// # Example
212    ///
213    /// ```rust
214    /// use midenc_hir::{SymbolPath, SymbolNameComponent};
215    ///
216    /// assert_eq!(
217    ///     SymbolPath::from_masm_module_id("intrinsics::mem"),
218    ///     SymbolPath::from_iter([
219    ///         SymbolNameComponent::Root,
220    ///         SymbolNameComponent::Component("intrinsics".into()),
221    ///         SymbolNameComponent::Component("mem".into()),
222    ///     ])
223    /// );
224    /// ```
225    pub fn from_masm_module_id(id: &str) -> Self {
226        let parts = id.split("::");
227        Self::from_iter(
228            core::iter::once(SymbolNameComponent::Root)
229                .chain(parts.map(SymbolName::intern).map(SymbolNameComponent::Component)),
230        )
231    }
232
233    /// Returns the leaf component of the symbol path
234    pub fn name(&self) -> SymbolName {
235        match self.path.last().expect("expected non-empty symbol path") {
236            SymbolNameComponent::Leaf(name) => *name,
237            component => panic!("invalid symbol path: expected leaf node, got: {component:?}"),
238        }
239    }
240
241    /// Set the value of the leaf component of the path, or append it if not yet present
242    pub fn set_name(&mut self, name: SymbolName) {
243        match self.path.last_mut() {
244            Some(SymbolNameComponent::Leaf(ref mut prev_name)) => {
245                *prev_name = name;
246            }
247            _ => {
248                self.path.push(SymbolNameComponent::Leaf(name));
249            }
250        }
251    }
252
253    /// Returns the first non-root component of the symbol path, if the path is absolute
254    pub fn namespace(&self) -> Option<SymbolName> {
255        if self.is_absolute() {
256            match self.path[1] {
257                SymbolNameComponent::Component(ns) => Some(ns),
258                SymbolNameComponent::Leaf(_) => None,
259                SymbolNameComponent::Root => unreachable!(
260                    "malformed symbol path: root components may only occur at the start of a path"
261                ),
262            }
263        } else {
264            None
265        }
266    }
267
268    /// Derive a Miden Assembly `LibraryPath` from this symbol path
269    pub fn to_library_path(&self) -> midenc_session::LibraryPath {
270        use midenc_session::{
271            miden_assembly::{ast::Ident, SourceSpan, Span},
272            LibraryNamespace, LibraryPath,
273        };
274
275        let mut components = self.path.iter();
276        let mut parts = SmallVec::<[_; 3]>::default();
277        if self.is_absolute() {
278            let _ = components.next();
279        }
280        let ns = match components.next() {
281            None => {
282                return LibraryPath::new_from_components(LibraryNamespace::Anon, parts);
283            }
284            Some(component) => LibraryNamespace::from_ident_unchecked(Ident::from_raw_parts(
285                Span::new(SourceSpan::default(), component.as_symbol_name().as_str().into()),
286            )),
287        };
288
289        for component in components {
290            let id = Ident::from_raw_parts(Span::new(
291                SourceSpan::default(),
292                component.as_symbol_name().as_str().into(),
293            ));
294            parts.push(id);
295        }
296
297        LibraryPath::new_from_components(ns, parts)
298    }
299
300    /// Returns true if this symbol name is fully-qualified
301    pub fn is_absolute(&self) -> bool {
302        matches!(&self.path[0], SymbolNameComponent::Root)
303    }
304
305    /// Returns true if this symbol name is nested
306    pub fn has_parent(&self) -> bool {
307        if self.is_absolute() {
308            self.path.len() > 2
309        } else {
310            self.path.len() > 1
311        }
312    }
313
314    /// Returns true if `self` is a prefix of `other`, i.e. `other` is a further qualified symbol
315    /// reference.
316    ///
317    /// NOTE: If `self` and `other` are equal, `self` is considered a prefix. The caller should
318    /// check if the two references are identical if they wish to distinguish the two cases.
319    pub fn is_prefix_of(&self, other: &Self) -> bool {
320        other.is_prefixed_by(&self.path)
321    }
322
323    /// Returns true if `prefix` is a prefix of `self`, i.e. `self` is a further qualified symbol
324    /// reference.
325    ///
326    /// NOTE: If `self` and `prefix` are equal, `prefix` is considered a valid prefix. The caller
327    /// should check if the two references are identical if they wish to distinguish the two cases.
328    pub fn is_prefixed_by(&self, prefix: &[SymbolNameComponent]) -> bool {
329        let mut a = prefix.iter();
330        let mut b = self.path.iter();
331
332        let mut index = 0;
333        loop {
334            match (a.next(), b.next()) {
335                (Some(part_a), Some(part_b)) if part_a == part_b => {
336                    index += 1;
337                }
338                (None, Some(_)) => break index > 0,
339                _ => break false,
340            }
341        }
342    }
343
344    /// Returns an iterator over the path components of this symbol name
345    pub fn components(&self) -> impl ExactSizeIterator<Item = SymbolNameComponent> + '_ {
346        self.path.iter().copied()
347    }
348
349    /// Get the parent of this path, i.e. all but the last component
350    pub fn parent(&self) -> Option<SymbolPath> {
351        match self.path.split_last()? {
352            (SymbolNameComponent::Root, []) => None,
353            (_, rest) => Some(SymbolPath {
354                path: SmallVec::from_slice(rest),
355            }),
356        }
357    }
358
359    /// Get the portion of this path without the `Leaf` component, if present.
360    pub fn without_leaf(&self) -> Cow<'_, SymbolPath> {
361        match self.path.split_last() {
362            Some((SymbolNameComponent::Leaf(_), rest)) => Cow::Owned(SymbolPath {
363                path: SmallVec::from_slice(rest),
364            }),
365            _ => Cow::Borrowed(self),
366        }
367    }
368}
369
370/// Print symbol path according to Wasm Component Model rules, i.e.:
371///
372/// ```text,ignore
373/// PATH ::= NAMESPACE ":" PACKAGE PACKAGE_PATH? VERSION?
374///
375/// NAMESPACE ::= SYMBOL
376/// PACKAGE ::= SYMBOL (":" SYMBOL)*
377/// PACKAGE_PATH ::= ("/" SYMBOL)+
378/// VERSION ::= "@" VERSION_STRING
379/// ```
380///
381/// The first component of an absolute path (ignoring the `Root` node) is expected to be the package
382/// name, i.e. the `NAMESPACE ":" PACKAGE` part as a single symbol.
383///
384/// The first component of a relative path is expected to be either `Component` or `Leaf`
385impl fmt::Display for SymbolPath {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        use core::fmt::Write;
388
389        let mut components = self.path.iter();
390
391        if self.is_absolute() {
392            let _ = components.next();
393        }
394
395        match components.next() {
396            Some(component) => f.write_str(component.as_symbol_name().as_str())?,
397            None => return Ok(()),
398        }
399        for component in components {
400            f.write_char('/')?;
401            f.write_str(component.as_symbol_name().as_str())?;
402        }
403        Ok(())
404    }
405}
406
407impl fmt::Debug for SymbolPath {
408    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
409        f.debug_struct("SymbolPath")
410            .field_with("path", |f| f.debug_list().entries(self.path.iter()).finish())
411            .finish()
412    }
413}
414impl crate::formatter::PrettyPrint for SymbolPath {
415    fn render(&self) -> crate::formatter::Document {
416        use crate::formatter::*;
417        display(self)
418    }
419}
420impl Eq for SymbolPath {}
421impl PartialEq for SymbolPath {
422    fn eq(&self, other: &Self) -> bool {
423        self.path == other.path
424    }
425}
426impl PartialOrd for SymbolPath {
427    #[inline]
428    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
429        Some(self.cmp(other))
430    }
431}
432impl Ord for SymbolPath {
433    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
434        self.path.cmp(&other.path)
435    }
436}
437impl core::hash::Hash for SymbolPath {
438    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
439        self.path.hash(state);
440    }
441}
442
443/// A component of a namespaced [SymbolName].
444///
445/// A component refers to one of the following:
446///
447/// * The root/global namespace anchor, i.e. indicates that other components are to be resolved
448///   relative to the root (possibly anonymous) symbol table.
449/// * The name of a symbol table nested within another symbol table or root namespace
450/// * The name of a symbol (which must always be the leaf component of a path)
451#[derive(Copy, Clone, PartialEq, Eq, Hash)]
452pub enum SymbolNameComponent {
453    /// A component that signals the path is relative to the root symbol table
454    Root,
455    /// A component of the symbol name path
456    Component(SymbolName),
457    /// The name of the symbol in its local symbol table
458    Leaf(SymbolName),
459}
460
461impl SymbolNameComponent {
462    pub fn as_symbol_name(&self) -> SymbolName {
463        match self {
464            Self::Root => interner::symbols::Empty,
465            Self::Component(name) | Self::Leaf(name) => *name,
466        }
467    }
468
469    #[inline]
470    pub fn is_root(&self) -> bool {
471        matches!(self, Self::Root)
472    }
473
474    #[inline]
475    pub fn is_leaf(&self) -> bool {
476        matches!(self, Self::Leaf(_))
477    }
478}
479
480impl fmt::Debug for SymbolNameComponent {
481    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
482        match self {
483            Self::Root => f.write_str("Root"),
484            Self::Component(name) => {
485                f.debug_tuple("Component").field_with(|f| f.write_str(name.as_str())).finish()
486            }
487            Self::Leaf(name) => {
488                f.debug_tuple("Leaf").field_with(|f| f.write_str(name.as_str())).finish()
489            }
490        }
491    }
492}
493
494impl Ord for SymbolNameComponent {
495    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
496        use core::cmp::Ordering;
497
498        if self == other {
499            return Ordering::Equal;
500        }
501
502        match (self, other) {
503            (Self::Root, _) => Ordering::Less,
504            (_, Self::Root) => Ordering::Greater,
505            (Self::Component(x), Self::Component(y)) => x.cmp(y),
506            (Self::Component(_), _) => Ordering::Less,
507            (_, Self::Component(_)) => Ordering::Greater,
508            (Self::Leaf(x), Self::Leaf(y)) => x.cmp(y),
509        }
510    }
511}
512
513impl PartialOrd for SymbolNameComponent {
514    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
515        Some(self.cmp(other))
516    }
517}
518
519/// An iterator over [SymbolNameComponent] derived from a path symbol and leaf symbol.
520pub struct SymbolNameComponents {
521    parts: VecDeque<&'static str>,
522    name: SymbolName,
523    absolute: bool,
524    done: bool,
525}
526
527impl SymbolNameComponents {
528    /// Construct a new [SymbolNameComponents] iterator from a Wasm Component Model symbol.
529    ///
530    /// The syntax for such symbols are described by the following EBNF-style grammar:
531    ///
532    /// ```text,ignore
533    /// SYMBOL ::= ID
534    /// QUALIFIED_SYMBOL ::= NAMESPACE ("/" ID)* ("@" VERSION)?
535    /// NAMESPACE ::= (ID ":")+ ID
536    /// ID ::= ID_CHAR+
537    /// ID_CHAR ::= 'A'..'Z'
538    ///           | 'a'..'z'
539    ///           | '0'..'z'
540    ///           | '-'
541    /// ```text,ignore
542    ///
543    /// This corresponds to identifiers of the form:
544    ///
545    /// * `foo` (referencing `foo` in the current scope)
546    /// * `miden:base/foo` (importing `foo` from the `miden:base` package)
547    /// * `miden:base/foo/bar` (importing `bar` from the `foo` interface of `miden:base`)
548    /// * `miden:base/foo/bar@1.0.0` (same as above, but specifying an exact package version)
549    ///
550    /// The following are not permitted:
551    ///
552    /// * `foo@1.0.0` (cannot reference a different version of the current package)
553    /// * `miden/foo` (packages must be namespaced, i.e. `<namespace>:<package>`)
554    pub fn from_component_model_symbol(symbol: SymbolName) -> Result<Self, crate::Report> {
555        use core::{iter::Peekable, str::CharIndices};
556
557        let mut parts = VecDeque::default();
558        if symbol == interner::symbols::Empty {
559            let done = symbol == interner::symbols::Empty;
560            return Ok(Self {
561                parts,
562                name: symbol,
563                done,
564                absolute: false,
565            });
566        }
567
568        #[inline(always)]
569        fn is_valid_id_char(c: char) -> bool {
570            c.is_ascii_alphanumeric() || c == '-'
571        }
572
573        fn lex_id<'a>(
574            s: &'a str,
575            start: usize,
576            lexer: &mut Peekable<CharIndices<'a>>,
577        ) -> Option<(usize, &'a str)> {
578            let mut end = start;
579            while let Some((i, c)) = lexer.next_if(|(_, c)| is_valid_id_char(*c)) {
580                end = i + c.len_utf8();
581            }
582            if end == start {
583                return None;
584            }
585            Some((end, unsafe { core::str::from_utf8_unchecked(&s.as_bytes()[start..end]) }))
586        }
587
588        let input = symbol.as_str();
589        let mut chars = input.char_indices().peekable();
590        let mut pos = 0;
591
592        // Parse the package name
593        let mut absolute = false;
594        let package_end = loop {
595            let (new_pos, _) = lex_id(input, pos, &mut chars).ok_or_else(|| {
596                crate::Report::msg(format!(
597                    "invalid component model symbol: '{symbol}' contains invalid characters"
598                ))
599            })?;
600            pos = new_pos;
601
602            if let Some((new_pos, c)) = chars.next_if(|(_, c)| *c == ':') {
603                pos = new_pos + c.len_utf8();
604                absolute = true;
605            } else {
606                break pos;
607            }
608        };
609
610        // Check if this is just a local symbol or package name
611        if chars.peek().is_none() {
612            let symbol =
613                unsafe { core::str::from_utf8_unchecked(&input.as_bytes()[pos..package_end]) };
614            return Ok(Self {
615                parts,
616                name: SymbolName::intern(symbol),
617                done: false,
618                absolute,
619            });
620        }
621
622        // Push the package name to `parts`
623        let package_name =
624            unsafe { core::str::from_utf8_unchecked(&input.as_bytes()[pos..package_end]) };
625        parts.push_back(package_name);
626
627        // The next character may be either a version (if absolute), or "/"
628        //
629        // Advance the lexer as appropriate
630        match chars.next_if(|(_, c)| *c == '/') {
631            None => {
632                // If the next char is not '@', the format is invalid
633                // If the char is '@', but the path is not absolute, the format is invalid
634                if chars.next_if(|(_, c)| *c == '@').is_some() {
635                    if !absolute {
636                        return Err(crate::Report::msg(
637                            "invalid component model symbol: unqualified symbols cannot be \
638                             versioned",
639                        ));
640                    }
641                    // TODO(pauls): Add support for version component
642                    //
643                    // For now we drop it
644                    parts.clear();
645                    return Ok(Self {
646                        parts,
647                        name: SymbolName::intern(package_name),
648                        done: false,
649                        absolute,
650                    });
651                } else {
652                    return Err(crate::Report::msg(format!(
653                        "invalid component model symbol: unexpected character in '{symbol}' \
654                         starting at byte {pos}"
655                    )));
656                }
657            }
658            Some((new_pos, c)) => {
659                pos = new_pos + c.len_utf8();
660            }
661        }
662
663        // Parse `ID ("/" ID)*+` until we reach end of input, or `"@"`
664        loop {
665            let (new_pos, id) = lex_id(input, pos, &mut chars).ok_or_else(|| {
666                crate::Report::msg(format!(
667                    "invalid component model symbol: '{symbol}' contains invalid characters"
668                ))
669            })?;
670            pos = new_pos;
671
672            if let Some((new_pos, c)) = chars.next_if(|(_, c)| *c == '/') {
673                pos = new_pos + c.len_utf8();
674                parts.push_back(id);
675            } else {
676                break;
677            }
678        }
679
680        // If the next char is '@', we have a version
681        //
682        // TODO(pauls): Add support for version component
683        //
684        // For now, ignore it
685        if chars.next_if(|(_, c)| *c == '@').is_some() {
686            let name = SymbolName::intern(parts.pop_back().unwrap());
687            return Ok(Self {
688                parts,
689                name,
690                done: false,
691                absolute,
692            });
693        }
694
695        // We should be at the end now, or the format is invalid
696        if chars.peek().is_none() {
697            let name = SymbolName::intern(parts.pop_back().unwrap());
698            Ok(Self {
699                parts,
700                name,
701                done: false,
702                absolute,
703            })
704        } else {
705            Err(crate::Report::msg(format!(
706                "invalid component model symbol: '{symbol}' contains invalid character starting \
707                 at byte {pos}"
708            )))
709        }
710    }
711
712    /// Convert this iterator into a single [Symbol] consisting of all components.
713    ///
714    /// Returns `None` if the input is empty.
715    pub fn into_symbol_name(self) -> Option<SymbolName> {
716        let attr = self.into_symbol_path()?;
717
718        Some(SymbolName::intern(attr))
719    }
720
721    /// Convert this iterator into a [SymbolPath].
722    ///
723    ///
724    /// Returns `None` if the input is empty.
725    pub fn into_symbol_path(self) -> Option<SymbolPath> {
726        if self.name == interner::symbols::Empty {
727            return None;
728        }
729
730        if self.parts.is_empty() {
731            return Some(SymbolPath {
732                path: smallvec![SymbolNameComponent::Leaf(self.name)],
733            });
734        }
735
736        // Pre-allocate the storage for the internal SymbolPath path
737        let mut path = SmallVec::<[_; 3]>::with_capacity(self.parts.len() + 1);
738
739        // Handle the first path component which tells us whether or not the path is rooted
740        let mut parts = self.parts.into_iter();
741        if let Some(part) = parts.next() {
742            if part == "::" {
743                path.push(SymbolNameComponent::Root);
744            } else {
745                path.push(SymbolNameComponent::Component(SymbolName::intern(part)));
746            }
747        }
748
749        // Append the remaining parts as intermediate path components
750        path.extend(parts.map(SymbolName::intern).map(SymbolNameComponent::Component));
751
752        // Finish up with the leaf symbol
753        path.push(SymbolNameComponent::Leaf(self.name));
754
755        Some(SymbolPath { path })
756    }
757}
758
759impl core::iter::FusedIterator for SymbolNameComponents {}
760impl Iterator for SymbolNameComponents {
761    type Item = SymbolNameComponent;
762
763    fn next(&mut self) -> Option<Self::Item> {
764        if self.done {
765            return None;
766        }
767        if self.absolute {
768            self.absolute = false;
769            return Some(SymbolNameComponent::Root);
770        }
771        if let Some(part) = self.parts.pop_front() {
772            return Some(SymbolNameComponent::Component(part.into()));
773        }
774        self.done = true;
775        Some(SymbolNameComponent::Leaf(self.name))
776    }
777}
778impl ExactSizeIterator for SymbolNameComponents {
779    fn len(&self) -> usize {
780        let is_empty = self.name == interner::symbols::Empty;
781        if is_empty {
782            assert_eq!(self.parts.len(), 0, "malformed symbol name components");
783            0
784        } else {
785            self.parts.len() + 1
786        }
787    }
788}