Skip to main content

miden_project/ast/
dependency.rs

1use super::{parsing::SetSourceId, *};
2use crate::{Linkage, SourceId, Span, Uri, VersionRequirement};
3
4/// Represents information about a project dependency needed to resolve it to a Miden package
5#[derive(Debug, Clone)]
6#[cfg_attr(feature = "serde", derive(Serialize))]
7pub struct DependencySpec {
8    /// The name of the dependency package
9    #[cfg_attr(feature = "serde", serde(default, skip))]
10    pub name: Span<Arc<str>>,
11    /// The version requirement specified for this dependency
12    #[cfg_attr(
13        feature = "serde",
14        serde(rename = "version", alias = "digest", skip_serializing_if = "Option::is_none")
15    )]
16    pub version_or_digest: Option<VersionRequirement>,
17    /// Whether or not the version requirement is inherited from the containing workspace
18    #[cfg_attr(
19        feature = "serde",
20        serde(default, skip_serializing_if = "does_not_inherit_from_workspace")
21    )]
22    pub workspace: bool,
23    /// If present, specifies the path from which this dependency should be loaded
24    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
25    pub path: Option<Span<Uri>>,
26    /// If present, specifies the URI of the git repository to clone in order to load this
27    /// dependency.
28    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
29    pub git: Option<Span<Uri>>,
30    /// If present, specifies the branch of the git repository to checkout when loading this
31    /// dependency from the URI specified by `git`.
32    ///
33    /// NOTE: This field is only valid when specified along with `git`, and may not be used in
34    /// conjunction with `rev`.
35    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
36    pub branch: Option<Span<Arc<str>>>,
37    /// If present, specifies the revision of the git repository to checkout when loading this
38    /// dependency from the URI specified by `git`.
39    ///
40    /// NOTE: This field is only valid when specified along with `git`, and may not be used in
41    /// conjunction with `branch`.
42    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
43    pub rev: Option<Span<Arc<str>>>,
44    /// If present, specifies the desired linkage for this dependency during assembly
45    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
46    pub linkage: Option<Span<Linkage>>,
47}
48
49#[inline(always)]
50fn does_not_inherit_from_workspace(is_workspace_dependency: &bool) -> bool {
51    !(*is_workspace_dependency)
52}
53
54impl DependencySpec {
55    /// Returns the version constraint to apply to this dependency
56    pub fn version(&self) -> Option<&VersionRequirement> {
57        self.version_or_digest.as_ref()
58    }
59
60    /// Returns true if this dependency inherits its version requirement from a parent workspace
61    pub fn inherits_workspace_version(&self) -> bool {
62        self.workspace
63    }
64
65    /// Returns true if this dependency must be resolved using a host-provided resolver
66    pub fn is_host_resolved(&self) -> bool {
67        self.git.is_none() && self.path.is_none()
68    }
69
70    /// Returns true if this dependency specifies a local filesystem path
71    pub fn is_path(&self) -> bool {
72        self.path.is_some() && self.git.is_none()
73    }
74
75    /// Returns true if this dependency specifies a git repository
76    pub fn is_git(&self) -> bool {
77        self.git.is_some()
78    }
79}
80
81impl SetSourceId for DependencySpec {
82    fn set_source_id(&mut self, source_id: SourceId) {
83        self.name.set_source_id(source_id);
84        if let Some(version_or_digest) = self.version_or_digest.as_mut() {
85            version_or_digest.set_source_id(source_id);
86        }
87
88        if let Some(path) = self.path.as_mut() {
89            path.set_source_id(source_id);
90        }
91
92        if let Some(git) = self.git.as_mut() {
93            git.set_source_id(source_id);
94        }
95
96        if let Some(branch) = self.branch.as_mut() {
97            branch.set_source_id(source_id);
98        }
99
100        if let Some(rev) = self.rev.as_mut() {
101            rev.set_source_id(source_id);
102        }
103    }
104}
105
106#[cfg(feature = "serde")]
107pub use self::serialization::deserialize_dependency_map;
108
109#[cfg(feature = "serde")]
110mod serialization {
111    use alloc::sync::Arc;
112
113    use miden_assembly_syntax::debuginfo::Span;
114    use serde::{
115        Deserialize,
116        de::{IntoDeserializer, MapAccess, Visitor},
117    };
118
119    use super::DependencySpec;
120    use crate::Map;
121
122    #[derive(Deserialize)]
123    #[serde(deny_unknown_fields)]
124    struct DependencySpecTable {
125        #[serde(default, skip)]
126        name: Span<Arc<str>>,
127        #[serde(rename = "version", alias = "digest", skip_serializing_if = "Option::is_none")]
128        version_or_digest: Option<crate::VersionRequirement>,
129        #[serde(default, skip_serializing_if = "super::does_not_inherit_from_workspace")]
130        workspace: bool,
131        #[serde(default, skip_serializing_if = "Option::is_none")]
132        path: Option<Span<crate::Uri>>,
133        #[serde(default, skip_serializing_if = "Option::is_none")]
134        git: Option<Span<crate::Uri>>,
135        #[serde(default, skip_serializing_if = "Option::is_none")]
136        branch: Option<Span<Arc<str>>>,
137        #[serde(default, skip_serializing_if = "Option::is_none")]
138        rev: Option<Span<Arc<str>>>,
139        #[serde(default, skip_serializing_if = "Option::is_none")]
140        linkage: Option<Span<crate::Linkage>>,
141    }
142
143    impl From<DependencySpecTable> for DependencySpec {
144        fn from(table: DependencySpecTable) -> Self {
145            Self {
146                name: table.name,
147                version_or_digest: table.version_or_digest,
148                workspace: table.workspace,
149                path: table.path,
150                git: table.git,
151                branch: table.branch,
152                rev: table.rev,
153                linkage: table.linkage,
154            }
155        }
156    }
157
158    impl<'de> Deserialize<'de> for DependencySpec {
159        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
160        where
161            D: serde::Deserializer<'de>,
162        {
163            serde_untagged::UntaggedEnumVisitor::new()
164                .expecting("a dependency requirement string or dependency table")
165                .string(|value| {
166                    crate::VersionRequirement::deserialize(value.into_deserializer()).map(
167                        |version_or_digest| Self {
168                            name: Span::default(),
169                            version_or_digest: Some(version_or_digest),
170                            workspace: false,
171                            path: None,
172                            git: None,
173                            branch: None,
174                            rev: None,
175                            linkage: None,
176                        },
177                    )
178                })
179                .map(|map| map.deserialize::<DependencySpecTable>().map(Into::into))
180                .deserialize(deserializer)
181        }
182    }
183
184    struct DependencyMapVisitor;
185
186    impl<'de> Visitor<'de> for DependencyMapVisitor {
187        type Value = Map<Span<Arc<str>>, Span<DependencySpec>>;
188
189        fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
190            formatter.write_str("a dependency map")
191        }
192
193        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
194        where
195            M: MapAccess<'de>,
196        {
197            let mut map = Self::Value::default();
198
199            while let Some((key, mut value)) =
200                access.next_entry::<Span<Arc<str>>, Span<DependencySpec>>()?
201            {
202                value.name = key.clone();
203                map.insert(key, value);
204            }
205
206            Ok(map)
207        }
208    }
209
210    #[allow(clippy::type_complexity)]
211    pub fn deserialize_dependency_map<'de, D>(
212        deserializer: D,
213    ) -> Result<Map<Span<Arc<str>>, Span<DependencySpec>>, D::Error>
214    where
215        D: serde::Deserializer<'de>,
216    {
217        deserializer.deserialize_map(DependencyMapVisitor)
218    }
219}