miden_mast_package/package/
manifest.rs1use 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#[derive(Debug, Default, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
27pub struct PackageManifest {
28 #[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 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 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 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 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 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 pub fn num_dependencies(&self) -> usize {
120 self.dependencies.len()
121 }
122
123 pub fn dependencies(&self) -> impl Iterator<Item = &Dependency> {
125 self.dependencies.iter()
126 }
127
128 pub fn num_exports(&self) -> usize {
130 self.exports.len()
131 }
132
133 pub fn exports(&self) -> impl Iterator<Item = &PackageExport> {
135 self.exports.values()
136 }
137
138 pub fn get_export(&self, name: impl AsRef<Path>) -> Option<&PackageExport> {
140 self.exports.get(name.as_ref())
141 }
142
143 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#[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 Procedure(ProcedureExport) = 1,
175 Constant(ConstantExport),
177 Type(TypeExport),
179}
180
181impl PackageExport {
182 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 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 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 #[inline]
215 pub fn is_procedure(&self) -> bool {
216 matches!(self, Self::Procedure(_))
217 }
218
219 #[inline]
221 pub fn is_constant(&self) -> bool {
222 matches!(self, Self::Constant(_))
223 }
224
225 #[inline]
227 pub fn is_type(&self) -> bool {
228 matches!(self, Self::Type(_))
229 }
230
231 pub(crate) const fn tag(&self) -> u8 {
232 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#[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 #[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 #[cfg_attr(feature = "arbitrary", proptest(value = "Word::default()"))]
275 pub digest: Word,
276 #[cfg_attr(feature = "arbitrary", proptest(value = "None"))]
278 #[cfg_attr(feature = "serde", serde(default))]
279 pub signature: Option<FunctionType>,
280 #[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#[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 #[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 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#[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 #[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 #[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}