Skip to main content

miden_mast_package/package/
manifest.rs

1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2use core::fmt;
3
4use miden_assembly_syntax::{
5    ast::{
6        self, AttributeSet, Path,
7        types::{FunctionType, Type},
8    },
9    library::Library,
10};
11use miden_core::{Word, utils::DisplayHex};
12#[cfg(feature = "arbitrary")]
13use proptest::prelude::{Strategy, any};
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18use crate::{Dependency, PackageId};
19
20// PACKAGE MANIFEST
21// ================================================================================================
22
23/// The manifest of a package, containing the set of package dependencies (libraries or packages)
24/// and exported items (procedures, constants, types), if known.
25#[derive(Debug, Default, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
27pub struct PackageManifest {
28    /// The set of exports in this package.
29    #[cfg_attr(
30        feature = "arbitrary",
31        proptest(
32            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))"
33        )
34    )]
35    pub(super) exports: BTreeMap<Arc<Path>, PackageExport>,
36    /// The libraries (packages) linked against by this package, which must be provided when
37    /// executing the program.
38    pub(super) dependencies: Vec<Dependency>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Error)]
42pub enum ManifestValidationError {
43    #[error("duplicate export path '{0}' in package manifest")]
44    DuplicateExport(Arc<Path>),
45    #[error("duplicate dependency '{0}' in package manifest")]
46    DuplicateDependency(PackageId),
47}
48
49impl PackageManifest {
50    /// Construct a new [PackageManifest] by providing the set of exports for the corresponding
51    /// package.
52    pub fn new(
53        exports: impl IntoIterator<Item = PackageExport>,
54    ) -> Result<Self, ManifestValidationError> {
55        let mut manifest = Self {
56            exports: Default::default(),
57            dependencies: Default::default(),
58        };
59        for export in exports {
60            manifest.add_export(export)?;
61        }
62
63        Ok(manifest)
64    }
65
66    /// Construct a new [PackageManifest] by deriving export information from the given [Library].
67    pub fn from_library(library: &Library) -> Self {
68        use miden_assembly_syntax::library::LibraryExport;
69        use miden_core::mast::MastNodeExt;
70
71        Self::new(library.exports().map(|export| match export {
72            LibraryExport::Procedure(export) => {
73                let digest = library.mast_forest()[export.node].digest();
74                PackageExport::Procedure(ProcedureExport {
75                    path: export.path.clone(),
76                    digest,
77                    signature: export.signature.clone(),
78                    attributes: export.attributes.clone(),
79                })
80            },
81            LibraryExport::Constant(export) => PackageExport::Constant(ConstantExport {
82                path: export.path.clone(),
83                value: export.value.clone(),
84            }),
85            LibraryExport::Type(export) => PackageExport::Type(TypeExport {
86                path: export.path.clone(),
87                ty: export.ty.clone(),
88            }),
89        }))
90        .expect("library exports should have unique paths")
91    }
92
93    /// Extend this manifest with the provided dependencies
94    pub fn with_dependencies(
95        mut self,
96        dependencies: impl IntoIterator<Item = Dependency>,
97    ) -> Result<Self, ManifestValidationError> {
98        for dependency in dependencies {
99            self.add_dependency(dependency)?;
100        }
101
102        Ok(self)
103    }
104
105    /// Add a dependency to the manifest
106    pub fn add_dependency(
107        &mut self,
108        dependency: Dependency,
109    ) -> Result<(), ManifestValidationError> {
110        if self.dependencies.iter().any(|existing| existing.id() == dependency.id()) {
111            return Err(ManifestValidationError::DuplicateDependency(dependency.name));
112        }
113
114        self.dependencies.push(dependency);
115        Ok(())
116    }
117
118    /// Get the number of dependencies of this package
119    pub fn num_dependencies(&self) -> usize {
120        self.dependencies.len()
121    }
122
123    /// Get an iterator over the dependencies of this package
124    pub fn dependencies(&self) -> impl Iterator<Item = &Dependency> {
125        self.dependencies.iter()
126    }
127
128    /// Get the number of items exported from this package
129    pub fn num_exports(&self) -> usize {
130        self.exports.len()
131    }
132
133    /// Get an iterator over the exports in this package
134    pub fn exports(&self) -> impl Iterator<Item = &PackageExport> {
135        self.exports.values()
136    }
137
138    /// Get information about an export by it's qualified name
139    pub fn get_export(&self, name: impl AsRef<Path>) -> Option<&PackageExport> {
140        self.exports.get(name.as_ref())
141    }
142
143    /// Get information about all exported procedures of this package with the given MAST root
144    /// digest
145    pub fn get_procedures_by_digest(
146        &self,
147        digest: &Word,
148    ) -> impl Iterator<Item = &ProcedureExport> + '_ {
149        let digest = *digest;
150        self.exports.values().filter_map(move |export| match export {
151            PackageExport::Procedure(export) if export.digest == digest => Some(export),
152            PackageExport::Procedure(_) => None,
153            PackageExport::Constant(_) | PackageExport::Type(_) => None,
154        })
155    }
156
157    fn add_export(&mut self, export: PackageExport) -> Result<(), ManifestValidationError> {
158        let path = export.path();
159        if self.exports.insert(path.clone(), export).is_some() {
160            return Err(ManifestValidationError::DuplicateExport(path));
161        }
162
163        Ok(())
164    }
165}
166
167/// Represents a named item exported from a package.
168#[derive(Debug, Clone, PartialEq, Eq)]
169#[repr(u8)]
170#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
171#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
172pub enum PackageExport {
173    /// A procedure definition or alias with 'pub' visibility
174    Procedure(ProcedureExport) = 1,
175    /// A constant definition with 'pub' visibility
176    Constant(ConstantExport),
177    /// A type declaration with 'pub' visibility
178    Type(TypeExport),
179}
180
181impl PackageExport {
182    /// Get the path of this exported item
183    pub fn path(&self) -> Arc<Path> {
184        match self {
185            Self::Procedure(export) => export.path.clone(),
186            Self::Constant(export) => export.path.clone(),
187            Self::Type(export) => export.path.clone(),
188        }
189    }
190
191    /// Get the namespace of the exported item.
192    ///
193    /// For example, if `Self::path` returns the path `std::foo::NAME`, this returns `std::foo`.
194    pub fn namespace(&self) -> &Path {
195        match self {
196            Self::Procedure(ProcedureExport { path, .. })
197            | Self::Constant(ConstantExport { path, .. })
198            | Self::Type(TypeExport { path, .. }) => path.parent().unwrap(),
199        }
200    }
201
202    /// Get the name of the exported item without its namespace.
203    ///
204    /// For example, if `Self::path` returns the path `std::foo::NAME`, this returns just `NAME`.
205    pub fn name(&self) -> &str {
206        match self {
207            Self::Procedure(ProcedureExport { path, .. })
208            | Self::Constant(ConstantExport { path, .. })
209            | Self::Type(TypeExport { path, .. }) => path.last().unwrap(),
210        }
211    }
212
213    /// Returns true if this item is a procedure
214    #[inline]
215    pub fn is_procedure(&self) -> bool {
216        matches!(self, Self::Procedure(_))
217    }
218
219    /// Returns true if this item is a constant
220    #[inline]
221    pub fn is_constant(&self) -> bool {
222        matches!(self, Self::Constant(_))
223    }
224
225    /// Returns true if this item is a type declaration
226    #[inline]
227    pub fn is_type(&self) -> bool {
228        matches!(self, Self::Type(_))
229    }
230
231    pub(crate) const fn tag(&self) -> u8 {
232        // SAFETY: This is safe because we have given this enum a
233        // primitive representation with #[repr(u8)], with the first
234        // field of the underlying union-of-structs the discriminant
235        //
236        // See the section on "accessing the numeric value of the discriminant"
237        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
238        unsafe { *(self as *const Self).cast::<u8>() }
239    }
240}
241
242#[cfg(feature = "arbitrary")]
243impl proptest::arbitrary::Arbitrary for PackageExport {
244    type Parameters = ();
245
246    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
247        use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
248
249        prop_oneof![
250            any::<ProcedureExport>().prop_map(Self::Procedure),
251            any::<ConstantExport>().prop_map(Self::Constant),
252            any::<TypeExport>().prop_map(Self::Type),
253        ]
254        .boxed()
255    }
256
257    type Strategy = proptest::prelude::BoxedStrategy<Self>;
258}
259
260/// A procedure exported by a package, along with its digest, signature, and attributes.
261#[derive(Clone, PartialEq, Eq)]
262#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
263#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
264#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
265pub struct ProcedureExport {
266    /// The fully-qualified path of the procedure exported by this package.
267    #[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
268    #[cfg_attr(
269        feature = "arbitrary",
270        proptest(strategy = "miden_assembly_syntax::arbitrary::path::bare_path_random_length(2)")
271    )]
272    pub path: Arc<Path>,
273    /// The digest of the procedure exported by this package.
274    #[cfg_attr(feature = "arbitrary", proptest(value = "Word::default()"))]
275    pub digest: Word,
276    /// The type signature of the exported procedure.
277    #[cfg_attr(feature = "arbitrary", proptest(value = "None"))]
278    #[cfg_attr(feature = "serde", serde(default))]
279    pub signature: Option<FunctionType>,
280    /// Attributes attached to the exported procedure.
281    #[cfg_attr(feature = "arbitrary", proptest(value = "AttributeSet::default()"))]
282    #[cfg_attr(feature = "serde", serde(default))]
283    pub attributes: AttributeSet,
284}
285
286impl fmt::Debug for ProcedureExport {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        let Self { path, digest, signature, attributes } = self;
289        f.debug_struct("PackageExport")
290            .field("path", &format_args!("{path}"))
291            .field("digest", &format_args!("{}", DisplayHex::new(&digest.as_bytes())))
292            .field("signature", signature)
293            .field("attributes", attributes)
294            .finish()
295    }
296}
297
298/// A constant definition exported by a package
299#[derive(Clone, PartialEq, Eq)]
300#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
303pub struct ConstantExport {
304    /// The fully-qualified path of the constant exported by this package.
305    #[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
306    #[cfg_attr(
307        feature = "arbitrary",
308        proptest(
309            strategy = "miden_assembly_syntax::arbitrary::path::constant_path_random_length(1)"
310        )
311    )]
312    pub path: Arc<Path>,
313    /// The value of the exported constant
314    ///
315    /// We export a [ast::ConstantValue] here, rather than raw felts, because it is how a constant
316    /// is used that determines its final concrete value, not the declaration itself. However,
317    /// [ast::ConstantValue] does represent a concrete value, just one that requires context to
318    /// fully evaluate.
319    pub value: ast::ConstantValue,
320}
321
322impl fmt::Debug for ConstantExport {
323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324        let Self { path, value } = self;
325        f.debug_struct("ConstantExport")
326            .field("path", &format_args!("{path}"))
327            .field("value", value)
328            .finish()
329    }
330}
331
332/// A named type declaration exported by a package
333#[derive(Clone, PartialEq, Eq)]
334#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
335#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
336#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
337pub struct TypeExport {
338    /// The fully-qualified path of the type exported by this package.
339    #[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
340    #[cfg_attr(
341        feature = "arbitrary",
342        proptest(
343            strategy = "miden_assembly_syntax::arbitrary::path::user_defined_type_path_random_length(1)"
344        )
345    )]
346    pub path: Arc<Path>,
347    /// The type that was declared
348    #[cfg_attr(feature = "arbitrary", proptest(value = "Type::Felt"))]
349    pub ty: Type,
350}
351
352impl fmt::Debug for TypeExport {
353    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354        let Self { path, ty } = self;
355        f.debug_struct("TypeExport")
356            .field("path", &format_args!("{path}"))
357            .field("ty", ty)
358            .finish()
359    }
360}