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