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