use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
use core::fmt;
use miden_assembly_syntax::{
ast::{
self, AttributeSet, Path,
types::{FunctionType, Type},
},
library::Library,
};
use miden_core::{Word, utils::DisplayHex};
#[cfg(feature = "arbitrary")]
use proptest::prelude::{Strategy, any};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{Dependency, PackageId};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
pub struct PackageManifest {
#[cfg_attr(
feature = "arbitrary",
proptest(
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))"
)
)]
pub(super) exports: BTreeMap<Arc<Path>, PackageExport>,
pub(super) dependencies: Vec<Dependency>,
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ManifestValidationError {
#[error("duplicate export path '{0}' in package manifest")]
DuplicateExport(Arc<Path>),
#[error("duplicate dependency '{0}' in package manifest")]
DuplicateDependency(PackageId),
}
impl PackageManifest {
pub fn new(
exports: impl IntoIterator<Item = PackageExport>,
) -> Result<Self, ManifestValidationError> {
let mut manifest = Self {
exports: Default::default(),
dependencies: Default::default(),
};
for export in exports {
manifest.add_export(export)?;
}
Ok(manifest)
}
pub fn from_library(library: &Library) -> Self {
use miden_assembly_syntax::library::LibraryExport;
use miden_core::mast::MastNodeExt;
Self::new(library.exports().map(|export| match export {
LibraryExport::Procedure(export) => {
let digest = library.mast_forest()[export.node].digest();
PackageExport::Procedure(ProcedureExport {
path: export.path.clone(),
digest,
signature: export.signature.clone(),
attributes: export.attributes.clone(),
})
},
LibraryExport::Constant(export) => PackageExport::Constant(ConstantExport {
path: export.path.clone(),
value: export.value.clone(),
}),
LibraryExport::Type(export) => PackageExport::Type(TypeExport {
path: export.path.clone(),
ty: export.ty.clone(),
}),
}))
.expect("library exports should have unique paths")
}
pub fn with_dependencies(
mut self,
dependencies: impl IntoIterator<Item = Dependency>,
) -> Result<Self, ManifestValidationError> {
for dependency in dependencies {
self.add_dependency(dependency)?;
}
Ok(self)
}
pub fn add_dependency(
&mut self,
dependency: Dependency,
) -> Result<(), ManifestValidationError> {
if self.dependencies.iter().any(|existing| existing.id() == dependency.id()) {
return Err(ManifestValidationError::DuplicateDependency(dependency.name));
}
self.dependencies.push(dependency);
Ok(())
}
pub fn num_dependencies(&self) -> usize {
self.dependencies.len()
}
pub fn dependencies(&self) -> impl Iterator<Item = &Dependency> {
self.dependencies.iter()
}
pub fn num_exports(&self) -> usize {
self.exports.len()
}
pub fn exports(&self) -> impl Iterator<Item = &PackageExport> {
self.exports.values()
}
pub fn get_export(&self, name: impl AsRef<Path>) -> Option<&PackageExport> {
self.exports.get(name.as_ref())
}
pub fn get_procedures_by_digest(
&self,
digest: &Word,
) -> impl Iterator<Item = &ProcedureExport> + '_ {
let digest = *digest;
self.exports.values().filter_map(move |export| match export {
PackageExport::Procedure(export) if export.digest == digest => Some(export),
PackageExport::Procedure(_) => None,
PackageExport::Constant(_) | PackageExport::Type(_) => None,
})
}
fn add_export(&mut self, export: PackageExport) -> Result<(), ManifestValidationError> {
let path = export.path();
if self.exports.insert(path.clone(), export).is_some() {
return Err(ManifestValidationError::DuplicateExport(path));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
pub enum PackageExport {
Procedure(ProcedureExport) = 1,
Constant(ConstantExport),
Type(TypeExport),
}
impl PackageExport {
pub fn path(&self) -> Arc<Path> {
match self {
Self::Procedure(export) => export.path.clone(),
Self::Constant(export) => export.path.clone(),
Self::Type(export) => export.path.clone(),
}
}
pub fn namespace(&self) -> &Path {
match self {
Self::Procedure(ProcedureExport { path, .. })
| Self::Constant(ConstantExport { path, .. })
| Self::Type(TypeExport { path, .. }) => path.parent().unwrap(),
}
}
pub fn name(&self) -> &str {
match self {
Self::Procedure(ProcedureExport { path, .. })
| Self::Constant(ConstantExport { path, .. })
| Self::Type(TypeExport { path, .. }) => path.last().unwrap(),
}
}
#[inline]
pub fn is_procedure(&self) -> bool {
matches!(self, Self::Procedure(_))
}
#[inline]
pub fn is_constant(&self) -> bool {
matches!(self, Self::Constant(_))
}
#[inline]
pub fn is_type(&self) -> bool {
matches!(self, Self::Type(_))
}
pub(crate) const fn tag(&self) -> u8 {
unsafe { *(self as *const Self).cast::<u8>() }
}
}
#[cfg(feature = "arbitrary")]
impl proptest::arbitrary::Arbitrary for PackageExport {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
prop_oneof![
any::<ProcedureExport>().prop_map(Self::Procedure),
any::<ConstantExport>().prop_map(Self::Constant),
any::<TypeExport>().prop_map(Self::Type),
]
.boxed()
}
type Strategy = proptest::prelude::BoxedStrategy<Self>;
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
pub struct ProcedureExport {
#[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
#[cfg_attr(
feature = "arbitrary",
proptest(strategy = "miden_assembly_syntax::arbitrary::path::bare_path_random_length(2)")
)]
pub path: Arc<Path>,
#[cfg_attr(feature = "arbitrary", proptest(value = "Word::default()"))]
pub digest: Word,
#[cfg_attr(feature = "arbitrary", proptest(value = "None"))]
#[cfg_attr(feature = "serde", serde(default))]
pub signature: Option<FunctionType>,
#[cfg_attr(feature = "arbitrary", proptest(value = "AttributeSet::default()"))]
#[cfg_attr(feature = "serde", serde(default))]
pub attributes: AttributeSet,
}
impl fmt::Debug for ProcedureExport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { path, digest, signature, attributes } = self;
f.debug_struct("PackageExport")
.field("path", &format_args!("{path}"))
.field("digest", &format_args!("{}", DisplayHex::new(&digest.as_bytes())))
.field("signature", signature)
.field("attributes", attributes)
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
pub struct ConstantExport {
#[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
#[cfg_attr(
feature = "arbitrary",
proptest(
strategy = "miden_assembly_syntax::arbitrary::path::constant_path_random_length(1)"
)
)]
pub path: Arc<Path>,
pub value: ast::ConstantValue,
}
impl fmt::Debug for ConstantExport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { path, value } = self;
f.debug_struct("ConstantExport")
.field("path", &format_args!("{path}"))
.field("value", value)
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
pub struct TypeExport {
#[cfg_attr(feature = "serde", serde(with = "miden_assembly_syntax::ast::path"))]
#[cfg_attr(
feature = "arbitrary",
proptest(
strategy = "miden_assembly_syntax::arbitrary::path::user_defined_type_path_random_length(1)"
)
)]
pub path: Arc<Path>,
#[cfg_attr(feature = "arbitrary", proptest(value = "Type::Felt"))]
pub ty: Type,
}
impl fmt::Debug for TypeExport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { path, ty } = self;
f.debug_struct("TypeExport")
.field("path", &format_args!("{path}"))
.field("ty", ty)
.finish()
}
}