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#[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 #[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 #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "Default::default()"))]
55 pub(super) modules: BTreeMap<Arc<Path>, PackageModule>,
56 pub(super) dependencies: Vec<Dependency>,
59 #[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 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 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 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 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 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 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 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 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 pub fn num_dependencies(&self) -> usize {
268 self.dependencies.len()
269 }
270
271 pub fn dependencies(&self) -> impl Iterator<Item = &Dependency> {
273 self.dependencies.iter()
274 }
275
276 pub fn num_exports(&self) -> usize {
278 self.exports.len()
279 }
280
281 pub fn exports(&self) -> impl Iterator<Item = &PackageExport> {
283 self.exports.values()
284 }
285
286 pub fn get_export(&self, name: impl AsRef<Path>) -> Option<&PackageExport> {
288 self.exports.get(name.as_ref())
289 }
290
291 pub fn num_modules(&self) -> usize {
293 self.modules.len()
294 }
295
296 pub fn modules(&self) -> impl Iterator<Item = &PackageModule> {
298 self.modules.values()
299 }
300
301 pub fn get_module(&self, name: impl AsRef<Path>) -> Option<&PackageModule> {
303 self.modules.get(name.as_ref())
304 }
305
306 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 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#[derive(Debug, Clone, PartialEq, Eq)]
337#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
338pub struct PackageModule {
339 #[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
341 pub path: Arc<Path>,
342 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 #[inline]
356 pub fn path(&self) -> &Arc<Path> {
357 &self.path
358 }
359
360 #[inline]
362 pub fn submodules(&self) -> &[PackageSubmodule] {
363 &self.submodules
364 }
365}
366
367#[derive(Debug, Clone, PartialEq, Eq)]
369#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
370pub struct PackageSubmodule {
371 pub name: ast::Ident,
373}
374
375impl PackageSubmodule {
376 pub fn new(name: ast::Ident) -> Self {
377 Self { name }
378 }
379}
380
381#[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 Procedure(ProcedureExport) = 1,
392 Constant(ConstantExport),
394 Type(TypeExport),
396}
397
398impl PackageExport {
399 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 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 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 #[inline]
432 pub fn is_procedure(&self) -> bool {
433 matches!(self, Self::Procedure(_))
434 }
435
436 #[inline]
438 pub fn is_constant(&self) -> bool {
439 matches!(self, Self::Constant(_))
440 }
441
442 #[inline]
444 pub fn is_type(&self) -> bool {
445 matches!(self, Self::Type(_))
446 }
447
448 #[inline]
450 pub fn as_procedure(&self) -> Option<&ProcedureExport> {
451 match self {
452 Self::Procedure(export) => Some(export),
453 _ => None,
454 }
455 }
456
457 #[inline]
459 pub fn as_constant(&self) -> Option<&ConstantExport> {
460 match self {
461 Self::Constant(export) => Some(export),
462 _ => None,
463 }
464 }
465
466 #[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 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#[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 #[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 #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
539 #[cfg_attr(feature = "serde", serde(default))]
540 pub node: Option<MastNodeId>,
541 #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
547 #[cfg_attr(feature = "serde", serde(default))]
548 pub source_node: Option<DebugSourceNodeId>,
549 #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "Word::default()"))]
551 pub digest: Word,
552 #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))]
554 #[cfg_attr(feature = "serde", serde(default))]
555 pub signature: Option<FunctionType>,
556 #[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#[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 #[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 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#[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 #[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 #[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}