Skip to main content

miden_mast_package/package/
manifest.rs

1use alloc::{
2    collections::BTreeMap,
3    string::{String, ToString},
4    sync::Arc,
5    vec::Vec,
6};
7use core::fmt;
8
9use miden_assembly_syntax::ast::{
10    self, AttributeSet, Path,
11    types::{FunctionType, Type},
12};
13#[cfg(all(feature = "arbitrary", test))]
14use miden_core::serde::{Deserializable, Serializable};
15use miden_core::{Word, mast::MastNodeId, utils::DisplayHex};
16#[cfg(any(test, feature = "arbitrary"))]
17use proptest::prelude::{Strategy, any};
18use thiserror::Error;
19
20use crate::{Dependency, PackageId, debug_info::DebugSourceNodeId};
21
22// PACKAGE MANIFEST
23// ================================================================================================
24
25/// The manifest of a package, containing the set of package dependencies (libraries or packages)
26/// exported items (procedures, constants, types), and module surface information, if known.
27///
28/// Exports declared in the package manifest are keyed by their fully-qualified path.
29///
30/// Module surface entries describe the module tree independently from item exports. This lets
31/// downstream linkers validate `use module` and submodule traversal without treating modules as
32/// exported items.
33///
34/// Dependencies must each specify a unique package identifier, i.e. it is not allowed to have
35/// multiple dependencies on the same package identifier, even if they are different versions.
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(any(test, feature = "arbitrary"), derive(proptest_derive::Arbitrary))]
38#[cfg_attr(
39    all(feature = "arbitrary", test),
40    miden_test_serialization_macros::serialization_test
41)]
42pub struct PackageManifest {
43    /// The set of exports in this package.
44    #[cfg_attr(
45        any(test, feature = "arbitrary"),
46        proptest(
47            strategy = "proptest::collection::vec(any::<PackageExport>(), 1..10).prop_filter_map(\"package exports must have unique paths\", |exports| PackageManifest::new(exports).ok().map(|manifest| manifest.exports))"
48        )
49    )]
50    pub(super) exports: BTreeMap<Arc<Path>, PackageExport>,
51    /// The module surface declared by this package.
52    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "Default::default()"))]
53    pub(super) modules: BTreeMap<Arc<Path>, PackageModule>,
54    /// The libraries (packages) linked against by this package, which must be provided when
55    /// executing the program.
56    #[cfg_attr(
57        any(test, feature = "arbitrary"),
58        proptest(strategy = "arbitrary_dependencies()")
59    )]
60    pub(super) dependencies: Vec<Dependency>,
61    /// The (optional) entrypoint function for this package, if it is executable
62    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
63    pub(super) entrypoint: Option<Arc<Path>>,
64}
65
66#[derive(Debug, Error)]
67pub enum ManifestValidationError {
68    #[error("duplicate export path '{0}' in package manifest")]
69    DuplicateExport(Arc<Path>),
70    #[error("duplicate module path '{0}' in package manifest")]
71    DuplicateModule(Arc<Path>),
72    #[error("duplicate submodule '{name}' in module '{module}' in package manifest")]
73    DuplicateSubmodule { module: Arc<Path>, name: String },
74    #[error(
75        "package manifest declares export '{export}' in module '{module}', but no module surface was provided for that module"
76    )]
77    MissingExportModuleSurface { export: Arc<Path>, module: Arc<Path> },
78    #[error(
79        "package manifest declares submodule '{module}' from module '{parent}', but no module surface was provided for it"
80    )]
81    MissingDeclaredSubmoduleSurface {
82        parent: Arc<Path>,
83        name: String,
84        module: Arc<Path>,
85    },
86    #[error(
87        "package manifest contains module surface '{module}', but parent module '{parent}' does not declare submodule '{name}'"
88    )]
89    UndeclaredModuleSurface {
90        module: Arc<Path>,
91        parent: Arc<Path>,
92        name: String,
93    },
94    #[error("duplicate dependency '{0}' in package manifest")]
95    DuplicateDependency(PackageId),
96    #[error("multiple entrypoint procedures found: '{duplicate}' conflicts with '{original}'")]
97    DuplicateEntrypoint {
98        original: Arc<Path>,
99        duplicate: Arc<Path>,
100    },
101    #[error("invalid {expected} path '{path}': found export of type {actual}")]
102    UnexpectedExportType {
103        path: Arc<Path>,
104        expected: &'static str,
105        actual: &'static str,
106    },
107    #[error("found an executable entrypoint in a package declared with non-executable type")]
108    NonExecutableEntrypoint,
109    #[error("invalid entrypoint path '{path}': no export with that path was found in the manifest")]
110    MissingEntrypoint { path: Arc<Path> },
111    #[error(
112        "package manifest declares export for procedure '{path}', but no procedure root with its digest was found in the MAST"
113    )]
114    MissingProcedureMast { path: Arc<Path>, digest: Word },
115    #[error(
116        "invalid procedure export '{path}': the declared node id and digest do not correspond to a procedure root in the MAST"
117    )]
118    InvalidProcedureExport { path: Arc<Path> },
119    #[error("invalid export path '{path}': {error}")]
120    InvalidExportPath { path: Arc<Path>, error: ast::PathError },
121    #[error("invalid module path '{path}': {error}")]
122    InvalidModulePath { path: Arc<Path>, error: ast::PathError },
123    #[error("package must contain at least one exported procedure")]
124    NoProcedures,
125}
126
127impl PackageManifest {
128    /// Construct a new [PackageManifest] by providing the set of exports for the corresponding
129    /// package.
130    pub fn new(
131        exports: impl IntoIterator<Item = PackageExport>,
132    ) -> Result<Self, ManifestValidationError> {
133        let mut manifest = Self {
134            exports: Default::default(),
135            modules: Default::default(),
136            dependencies: Default::default(),
137            entrypoint: None,
138        };
139        let mut has_procedures = false;
140        for mut export in exports {
141            normalize_export(&mut export)?;
142            if let Some(proc) = export.as_procedure() {
143                has_procedures = true;
144                // The presence of `begin` in any exported module is automatically made the
145                // entrypoint for that package.
146                if proc.path.last().is_some_and(|name| name == ast::ProcedureName::MAIN_PROC_NAME) {
147                    if let Some(original) = manifest.entrypoint.clone() {
148                        return Err(ManifestValidationError::DuplicateEntrypoint {
149                            original,
150                            duplicate: proc.path.clone(),
151                        });
152                    }
153                    manifest.entrypoint = Some(proc.path.clone());
154                }
155            }
156            manifest.add_export(export)?;
157        }
158
159        if !has_procedures {
160            return Err(ManifestValidationError::NoProcedures);
161        }
162
163        Ok(manifest)
164    }
165
166    /// Specify the entrypoint procedure for this package.
167    ///
168    /// This will return an error if an entrypoint already exists, or if `entrypoint` is not
169    /// found in the set of exported procedures declared in this manifest.
170    pub fn with_entrypoint(
171        mut self,
172        entrypoint: Arc<Path>,
173    ) -> Result<Self, ManifestValidationError> {
174        self.set_entrypoint(entrypoint)?;
175
176        Ok(self)
177    }
178
179    /// Override the entrypoint procedure for this package.
180    ///
181    /// This will return an error if an entrypoint already exists, or if `entrypoint` is not
182    /// found in the set of exported procedures declared in this manifest.
183    pub(super) fn set_entrypoint(
184        &mut self,
185        entrypoint: Arc<Path>,
186    ) -> Result<(), ManifestValidationError> {
187        if let Some(original) = self.entrypoint.clone() {
188            if original == entrypoint {
189                Ok(())
190            } else {
191                Err(ManifestValidationError::DuplicateEntrypoint {
192                    original,
193                    duplicate: entrypoint,
194                })
195            }
196        } else if let Some(export) = self.get_export(&entrypoint) {
197            match export {
198                PackageExport::Procedure(proc) => {
199                    self.entrypoint = Some(proc.path.clone());
200                    Ok(())
201                },
202                other @ (PackageExport::Constant(_) | PackageExport::Type(_)) => {
203                    let actual = match other {
204                        PackageExport::Constant(_) => "constant",
205                        PackageExport::Type(_) => "type",
206                        _ => unreachable!(),
207                    };
208                    Err(ManifestValidationError::UnexpectedExportType {
209                        path: entrypoint,
210                        expected: "procedure",
211                        actual,
212                    })
213                },
214            }
215        } else {
216            Err(ManifestValidationError::MissingEntrypoint { path: entrypoint })
217        }
218    }
219
220    /// Extend this manifest with the provided dependencies
221    pub fn with_dependencies(
222        mut self,
223        dependencies: impl IntoIterator<Item = Dependency>,
224    ) -> Result<Self, ManifestValidationError> {
225        for dependency in dependencies {
226            self.add_dependency(dependency)?;
227        }
228
229        Ok(self)
230    }
231
232    /// Extend this manifest with module surface information.
233    pub fn with_modules(
234        mut self,
235        modules: impl IntoIterator<Item = PackageModule>,
236    ) -> Result<Self, ManifestValidationError> {
237        for module in modules {
238            self.add_module(module)?;
239        }
240
241        Ok(self)
242    }
243
244    /// Add module surface information to the manifest.
245    pub fn add_module(&mut self, mut module: PackageModule) -> Result<(), ManifestValidationError> {
246        normalize_module(&mut module)?;
247        let path = module.path.clone();
248        if self.modules.insert(path.clone(), module).is_some() {
249            return Err(ManifestValidationError::DuplicateModule(path));
250        }
251
252        Ok(())
253    }
254
255    /// Add a dependency to the manifest
256    pub fn add_dependency(
257        &mut self,
258        dependency: Dependency,
259    ) -> Result<(), ManifestValidationError> {
260        if self.dependencies.iter().any(|existing| existing.id() == dependency.id()) {
261            return Err(ManifestValidationError::DuplicateDependency(dependency.name));
262        }
263
264        self.dependencies.push(dependency);
265        Ok(())
266    }
267
268    /// Get the number of dependencies of this package
269    pub fn num_dependencies(&self) -> usize {
270        self.dependencies.len()
271    }
272
273    /// Get an iterator over the dependencies of this package
274    pub fn dependencies(&self) -> impl Iterator<Item = &Dependency> {
275        self.dependencies.iter()
276    }
277
278    /// Get the number of items exported from this package
279    pub fn num_exports(&self) -> usize {
280        self.exports.len()
281    }
282
283    /// Get an iterator over the exports in this package
284    pub fn exports(&self) -> impl Iterator<Item = &PackageExport> {
285        self.exports.values()
286    }
287
288    /// Get information about an export by it's qualified name
289    pub fn get_export(&self, name: impl AsRef<Path>) -> Option<&PackageExport> {
290        self.exports.get(name.as_ref())
291    }
292
293    /// Get the number of module surface entries in this manifest.
294    pub fn num_modules(&self) -> usize {
295        self.modules.len()
296    }
297
298    /// Get an iterator over the module surfaces in this manifest.
299    pub fn modules(&self) -> impl Iterator<Item = &PackageModule> {
300        self.modules.values()
301    }
302
303    /// Get information about a module surface by its qualified path.
304    pub fn get_module(&self, name: impl AsRef<Path>) -> Option<&PackageModule> {
305        self.modules.get(name.as_ref())
306    }
307
308    /// Get information about all exported procedures of this package with the given MAST root
309    /// digest
310    pub fn get_procedures_by_digest(
311        &self,
312        digest: &Word,
313    ) -> impl Iterator<Item = &ProcedureExport> + '_ {
314        let digest = *digest;
315        self.exports.values().filter_map(move |export| match export {
316            PackageExport::Procedure(export) if export.digest == digest => Some(export),
317            PackageExport::Procedure(_) => None,
318            PackageExport::Constant(_) | PackageExport::Type(_) => None,
319        })
320    }
321
322    /// Get the entrypoint specified in the package manifest, if one is specified
323    pub fn entrypoint(&self) -> Option<Arc<Path>> {
324        self.entrypoint.clone()
325    }
326
327    fn add_export(&mut self, export: PackageExport) -> Result<(), ManifestValidationError> {
328        let path = export.path();
329        if self.exports.insert(path.clone(), export).is_some() {
330            return Err(ManifestValidationError::DuplicateExport(path));
331        }
332
333        Ok(())
334    }
335}
336
337/// Represents a module surface declared by a package.
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct PackageModule {
340    /// The fully-qualified path of this module.
341    pub path: Arc<Path>,
342    /// The public submodules declared by this module.
343    pub submodules: Vec<PackageSubmodule>,
344}
345
346impl PackageModule {
347    pub fn new(path: Arc<Path>, submodules: impl IntoIterator<Item = PackageSubmodule>) -> Self {
348        Self {
349            path,
350            submodules: submodules.into_iter().collect(),
351        }
352    }
353
354    /// Get the module path.
355    #[inline]
356    pub fn path(&self) -> &Arc<Path> {
357        &self.path
358    }
359
360    /// Get the submodule declarations for this module.
361    #[inline]
362    pub fn submodules(&self) -> &[PackageSubmodule] {
363        &self.submodules
364    }
365}
366
367/// Represents a submodule declaration in a package module surface.
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct PackageSubmodule {
370    /// The name of the submodule.
371    pub name: ast::Ident,
372}
373
374impl PackageSubmodule {
375    pub fn new(name: ast::Ident) -> Self {
376        Self { name }
377    }
378}
379
380/// Represents a named item exported from a package.
381#[derive(Debug, Clone, PartialEq, Eq)]
382#[repr(u8)]
383#[cfg_attr(
384    all(feature = "arbitrary", test),
385    miden_test_serialization_macros::serialization_test
386)]
387pub enum PackageExport {
388    /// A procedure definition or alias with 'pub' visibility
389    Procedure(ProcedureExport) = 1,
390    /// A constant definition with 'pub' visibility
391    Constant(ConstantExport),
392    /// A type declaration with 'pub' visibility
393    Type(TypeExport),
394}
395
396impl PackageExport {
397    /// Get the path of this exported item
398    pub fn path(&self) -> Arc<Path> {
399        match self {
400            Self::Procedure(export) => export.path.clone(),
401            Self::Constant(export) => export.path.clone(),
402            Self::Type(export) => export.path.clone(),
403        }
404    }
405
406    /// Get the namespace of the exported item.
407    ///
408    /// For example, if `Self::path` returns the path `std::foo::NAME`, this returns `std::foo`.
409    pub fn namespace(&self) -> &Path {
410        match self {
411            Self::Procedure(ProcedureExport { path, .. })
412            | Self::Constant(ConstantExport { path, .. })
413            | Self::Type(TypeExport { path, .. }) => path.parent().unwrap(),
414        }
415    }
416
417    /// Get the name of the exported item without its namespace.
418    ///
419    /// For example, if `Self::path` returns the path `std::foo::NAME`, this returns just `NAME`.
420    pub fn name(&self) -> &str {
421        match self {
422            Self::Procedure(ProcedureExport { path, .. })
423            | Self::Constant(ConstantExport { path, .. })
424            | Self::Type(TypeExport { path, .. }) => path.last().unwrap(),
425        }
426    }
427
428    /// Returns true if this item is a procedure
429    #[inline]
430    pub fn is_procedure(&self) -> bool {
431        matches!(self, Self::Procedure(_))
432    }
433
434    /// Returns true if this item is a constant
435    #[inline]
436    pub fn is_constant(&self) -> bool {
437        matches!(self, Self::Constant(_))
438    }
439
440    /// Returns true if this item is a type declaration
441    #[inline]
442    pub fn is_type(&self) -> bool {
443        matches!(self, Self::Type(_))
444    }
445
446    /// Returns true if this item is a procedure
447    #[inline]
448    pub fn as_procedure(&self) -> Option<&ProcedureExport> {
449        match self {
450            Self::Procedure(export) => Some(export),
451            _ => None,
452        }
453    }
454
455    /// Returns true if this item is a constant
456    #[inline]
457    pub fn as_constant(&self) -> Option<&ConstantExport> {
458        match self {
459            Self::Constant(export) => Some(export),
460            _ => None,
461        }
462    }
463
464    /// Returns true if this item is a type declaration
465    #[inline]
466    pub fn as_type(&self) -> Option<&TypeExport> {
467        match self {
468            Self::Type(export) => Some(export),
469            _ => None,
470        }
471    }
472
473    pub(crate) const fn tag(&self) -> u8 {
474        // SAFETY: This is safe because we have given this enum a
475        // primitive representation with #[repr(u8)], with the first
476        // field of the underlying union-of-structs the discriminant
477        //
478        // See the section on "accessing the numeric value of the discriminant"
479        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
480        unsafe { *(self as *const Self).cast::<u8>() }
481    }
482}
483
484#[cfg(any(test, feature = "arbitrary"))]
485impl proptest::arbitrary::Arbitrary for PackageExport {
486    type Parameters = ();
487
488    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
489        use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
490
491        prop_oneof![
492            any::<ProcedureExport>().prop_map(Self::Procedure),
493            any::<ConstantExport>().prop_map(Self::Constant),
494            any::<TypeExport>().prop_map(Self::Type),
495        ]
496        .boxed()
497    }
498
499    type Strategy = proptest::prelude::BoxedStrategy<Self>;
500}
501
502/// A procedure exported by a package, along with its digest, signature, and attributes.
503#[derive(Clone, PartialEq, Eq)]
504#[cfg_attr(any(test, feature = "arbitrary"), derive(proptest_derive::Arbitrary))]
505#[cfg_attr(
506    all(feature = "arbitrary", test),
507    miden_test_serialization_macros::serialization_test
508)]
509pub struct ProcedureExport {
510    /// The fully-qualified path of the procedure exported by this package.
511    #[cfg_attr(
512        any(test, feature = "arbitrary"),
513        proptest(strategy = "miden_assembly_syntax::arbitrary::path::bare_path_random_length(2)")
514    )]
515    pub path: Arc<Path>,
516    /// The id of the MAST root node corresponding to this procedure
517    ///
518    /// This is used for provenance, i.e. tracing which specific node in the package MAST this
519    /// export corresponds to, when multiple exports may have the same digest (conversely, some
520    /// procedure roots in the MAST may not be associated with any exports).
521    ///
522    /// Provenance is important because multiple logically distinct procedures may compile to the
523    /// same MAST digest while retaining distinct export identities. The MAST uses executable node
524    /// fingerprints to collapse equivalent nodes in the forest. The only way to guarantee that you
525    /// will get the precise MAST node that corresponds to the specific procedure you've named is
526    /// to use the MAST node, rather than the digest.
527    ///
528    /// NOTE: While one might get the impression that `MastNodeId` is a unique identifier for each
529    /// procedure that gets assembled to the MAST, that isn't actually true. If multiple nodes have
530    /// the same executable fingerprint, they may be collapsed into a single node in the MAST and
531    /// have the same `MastNodeId`.
532    ///
533    /// If this field contains `None`, the digest is used to resolve a MAST node.
534    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
535    pub node: Option<MastNodeId>,
536    /// Source/debug occurrence corresponding to this exported procedure, when package debug info
537    /// is present.
538    ///
539    /// This disambiguates exports that collapse to the same executable [`MastNodeId`] but retain
540    /// distinct source/debug metadata in the package-owned source occurrence graph.
541    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
542    pub source_node: Option<DebugSourceNodeId>,
543    /// The digest of the procedure exported by this package.
544    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "Word::default()"))]
545    pub digest: Word,
546    /// The type signature of the exported procedure.
547    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
548    pub signature: Option<FunctionType>,
549    /// Attributes attached to the exported procedure.
550    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "AttributeSet::default()"))]
551    pub attributes: AttributeSet,
552}
553
554impl ProcedureExport {
555    pub fn new(
556        path: Arc<Path>,
557        node: Option<MastNodeId>,
558        digest: Word,
559        signature: Option<FunctionType>,
560    ) -> Self {
561        Self {
562            path,
563            node,
564            source_node: None,
565            digest,
566            signature,
567            attributes: Default::default(),
568        }
569    }
570
571    pub fn with_source_node(mut self, source_node: Option<DebugSourceNodeId>) -> Self {
572        self.source_node = source_node;
573        self
574    }
575}
576
577impl fmt::Debug for ProcedureExport {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        let Self {
580            path,
581            node,
582            source_node,
583            digest,
584            signature,
585            attributes,
586        } = self;
587        f.debug_struct("PackageExport")
588            .field("path", &format_args!("{path}"))
589            .field("node", node)
590            .field("source_node", source_node)
591            .field("digest", &format_args!("{}", DisplayHex::new(&digest.as_bytes())))
592            .field("signature", signature)
593            .field("attributes", attributes)
594            .finish()
595    }
596}
597
598/// A constant definition exported by a package
599#[derive(Clone, PartialEq, Eq)]
600#[cfg_attr(any(test, feature = "arbitrary"), derive(proptest_derive::Arbitrary))]
601#[cfg_attr(
602    all(feature = "arbitrary", test),
603    miden_test_serialization_macros::serialization_test
604)]
605pub struct ConstantExport {
606    /// The fully-qualified path of the constant exported by this package.
607    #[cfg_attr(
608        any(test, feature = "arbitrary"),
609        proptest(
610            strategy = "miden_assembly_syntax::arbitrary::path::constant_path_random_length(1)"
611        )
612    )]
613    pub path: Arc<Path>,
614    /// The value of the exported constant
615    ///
616    /// We export a [ast::ConstantValue] here, rather than raw felts, because it is how a constant
617    /// is used that determines its final concrete value, not the declaration itself. However,
618    /// [ast::ConstantValue] does represent a concrete value, just one that requires context to
619    /// fully evaluate.
620    pub value: ast::ConstantValue,
621}
622
623impl fmt::Debug for ConstantExport {
624    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625        let Self { path, value } = self;
626        f.debug_struct("ConstantExport")
627            .field("path", &format_args!("{path}"))
628            .field("value", value)
629            .finish()
630    }
631}
632
633/// A named type declaration exported by a package
634#[derive(Clone, PartialEq, Eq)]
635#[cfg_attr(any(test, feature = "arbitrary"), derive(proptest_derive::Arbitrary))]
636#[cfg_attr(
637    all(feature = "arbitrary", test),
638    miden_test_serialization_macros::serialization_test
639)]
640pub struct TypeExport {
641    /// The fully-qualified path of the type exported by this package.
642    #[cfg_attr(
643        any(test, feature = "arbitrary"),
644        proptest(
645            strategy = "miden_assembly_syntax::arbitrary::path::user_defined_type_path_random_length(1)"
646        )
647    )]
648    pub path: Arc<Path>,
649    /// The type that was declared
650    #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "Type::Felt"))]
651    pub ty: Type,
652}
653
654impl fmt::Debug for TypeExport {
655    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656        let Self { path, ty } = self;
657        f.debug_struct("TypeExport")
658            .field("path", &format_args!("{path}"))
659            .field("ty", ty)
660            .finish()
661    }
662}
663
664#[cfg(any(test, feature = "arbitrary"))]
665fn arbitrary_dependencies() -> impl Strategy<Value = Vec<Dependency>> {
666    proptest::collection::vec(any::<Dependency>(), 0..10).prop_filter(
667        "package dependencies must have unique ids",
668        |dependencies| {
669            use alloc::collections::BTreeSet;
670
671            let mut seen = BTreeSet::new();
672            dependencies.iter().all(|dependency| seen.insert(dependency.id().clone()))
673        },
674    )
675}
676
677fn normalize_export(export: &mut PackageExport) -> Result<(), ManifestValidationError> {
678    let canonical_path = canonicalize_export_path(export.path().as_ref())?;
679
680    match export {
681        PackageExport::Procedure(proc) => {
682            let _ = canonical_path
683                .procedure_name()
684                .map_err(|error| ManifestValidationError::InvalidExportPath {
685                    path: canonical_path.clone(),
686                    error,
687                })?
688                .ok_or_else(|| ManifestValidationError::InvalidExportPath {
689                    path: canonical_path.clone(),
690                    error: ast::PathError::Empty,
691                })?;
692            proc.path = canonical_path;
693        },
694        PackageExport::Constant(ConstantExport { path, .. })
695        | PackageExport::Type(TypeExport { path, .. }) => {
696            let leaf = canonical_path
697                .components()
698                .next_back()
699                .ok_or_else(|| ManifestValidationError::InvalidExportPath {
700                    path: canonical_path.clone(),
701                    error: ast::PathError::Empty,
702                })?
703                .map_err(|error| ManifestValidationError::InvalidExportPath {
704                    path: canonical_path.clone(),
705                    error,
706                })?;
707            let _ = ast::Ident::new(leaf.as_str()).map_err(|err| {
708                ManifestValidationError::InvalidExportPath {
709                    path: canonical_path.clone(),
710                    error: ast::PathError::InvalidComponent(err),
711                }
712            })?;
713            *path = canonical_path;
714        },
715    }
716
717    Ok(())
718}
719
720fn normalize_module(module: &mut PackageModule) -> Result<(), ManifestValidationError> {
721    use alloc::collections::BTreeSet;
722    let canonical_path = canonicalize_module_path(module.path.as_ref())?;
723    let mut declared = BTreeSet::new();
724
725    for submodule in module.submodules.iter() {
726        let name = submodule.name.as_str();
727        if !declared.insert(name.to_string()) {
728            return Err(ManifestValidationError::DuplicateSubmodule {
729                module: canonical_path,
730                name: name.to_string(),
731            });
732        }
733    }
734
735    module.path = canonical_path;
736    Ok(())
737}
738
739fn canonicalize_module_path(path: &Path) -> Result<Arc<Path>, ManifestValidationError> {
740    let canonical =
741        path.canonicalize()
742            .map_err(|error| ManifestValidationError::InvalidModulePath {
743                error,
744                path: path.to_path_buf().into(),
745            })?;
746    Ok(Arc::<Path>::from(canonical.into_boxed_path()))
747}
748
749fn canonicalize_export_path(path: &Path) -> Result<Arc<Path>, ManifestValidationError> {
750    let canonical =
751        path.canonicalize()
752            .map_err(|error| ManifestValidationError::InvalidExportPath {
753                error,
754                path: path.to_path_buf().into(),
755            })?;
756    Ok(Arc::<Path>::from(canonical.into_boxed_path()))
757}