Skip to main content

miden_assembly_syntax/ast/path/
join.rs

1use alloc::{boxed::Box, string::String, sync::Arc};
2
3use super::*;
4use crate::ast;
5
6/// This trait is used to implement joining of a path or path component to a [Path] or [PathBuf].
7///
8/// This is required as the semantics of joining a path to a path, versus joining a string to a path
9/// are not the same, but they are consistent for specific pairs of types.
10///
11/// This trait is public in order to use it as a constraint for [`Path::join`], but it is sealed to
12/// only allow it to be implemented on [Path] and [PathBuf].
13pub trait Join<T: ?Sized>: sealed::Joinable {
14    /// Joins `other` to `self`, producing a new [PathBuf] containing the joined path.
15    ///
16    /// Implementations must choose one of two strategies for joining, depending on what `T`
17    /// represents:
18    ///
19    /// 1. If `T` is a type that can represent a multi-component path, then you should prefer to
20    ///    construct a [Path] or [PathBuf] from `T`, and delegate to `<Path as Join<Path>>::join`.
21    ///    This approach is akin to converting `self` to a [PathBuf], and calling [`PathBuf::push`]
22    ///    on it.
23    /// 2. If `T` is a type that represents a symbol or single-component path, then you should
24    ///    prefer to convert the `T` to a `&str`/`String`/`Ident` and delegate to the corresponding
25    ///    implementation of `Join` for [Path]. This approach is akin to converting `self` to a
26    ///    [PathBuf] and calliing [`PathBuf::push_component`] on it.
27    fn join(&self, other: &T) -> PathBuf;
28}
29
30mod sealed {
31    #[doc(hidden)]
32    pub trait Joinable {}
33
34    impl Joinable for crate::ast::Path {}
35    impl Joinable for crate::ast::PathBuf {}
36}
37
38impl Join<Path> for Path {
39    fn join(&self, other: &Path) -> PathBuf {
40        if other.is_empty() {
41            return self.to_path_buf();
42        }
43
44        if self.is_empty() {
45            other.to_path_buf()
46        } else if other.is_absolute() || other.is_in_kernel() || other.is_in_exec() {
47            other.to_absolute().into_owned()
48        } else {
49            let mut buf = self.to_path_buf();
50            buf.push(other);
51
52            buf
53        }
54    }
55}
56
57impl Join<PathBuf> for Path {
58    #[inline(always)]
59    fn join(&self, other: &PathBuf) -> PathBuf {
60        <Path as Join<Path>>::join(self, other.as_path())
61    }
62}
63
64impl Join<str> for Path {
65    fn join(&self, other: &str) -> PathBuf {
66        let mut buf = self.to_path_buf();
67        buf.push_component(other);
68        buf
69    }
70}
71
72impl Join<String> for Path {
73    fn join(&self, other: &String) -> PathBuf {
74        <Path as Join<str>>::join(self, other)
75    }
76}
77
78impl Join<Box<str>> for Path {
79    fn join(&self, other: &Box<str>) -> PathBuf {
80        <Path as Join<str>>::join(self, other)
81    }
82}
83
84impl Join<Arc<str>> for Path {
85    fn join(&self, other: &Arc<str>) -> PathBuf {
86        <Path as Join<str>>::join(self, other)
87    }
88}
89
90impl Join<ast::Ident> for Path {
91    fn join(&self, other: &ast::Ident) -> PathBuf {
92        <Path as Join<str>>::join(self, other.as_str())
93    }
94}
95
96impl Join<ast::ProcedureName> for Path {
97    fn join(&self, other: &ast::ProcedureName) -> PathBuf {
98        <Path as Join<str>>::join(self, other.as_str())
99    }
100}
101
102impl Join<ast::QualifiedProcedureName> for Path {
103    fn join(&self, other: &ast::QualifiedProcedureName) -> PathBuf {
104        let mut buf = <Path as Join<Path>>::join(self, other.namespace());
105        buf.push_component(other.name());
106        buf
107    }
108}
109
110impl Join<Path> for PathBuf {
111    #[inline]
112    fn join(&self, other: &Path) -> PathBuf {
113        <Path as Join<Path>>::join(self.as_path(), other)
114    }
115}
116
117impl Join<PathBuf> for PathBuf {
118    #[inline(always)]
119    fn join(&self, other: &PathBuf) -> PathBuf {
120        <Path as Join<Path>>::join(self.as_path(), other.as_path())
121    }
122}
123
124impl Join<str> for PathBuf {
125    fn join(&self, other: &str) -> PathBuf {
126        <Path as Join<str>>::join(self.as_path(), other)
127    }
128}
129
130impl Join<String> for PathBuf {
131    fn join(&self, other: &String) -> PathBuf {
132        <Path as Join<str>>::join(self.as_path(), other)
133    }
134}
135
136impl Join<Box<str>> for PathBuf {
137    fn join(&self, other: &Box<str>) -> PathBuf {
138        <Path as Join<str>>::join(self.as_path(), other)
139    }
140}
141
142impl Join<Arc<str>> for PathBuf {
143    fn join(&self, other: &Arc<str>) -> PathBuf {
144        <Path as Join<str>>::join(self.as_path(), other)
145    }
146}
147
148impl Join<ast::Ident> for PathBuf {
149    fn join(&self, other: &ast::Ident) -> PathBuf {
150        <Path as Join<ast::Ident>>::join(self.as_path(), other)
151    }
152}
153
154impl Join<ast::ProcedureName> for PathBuf {
155    fn join(&self, other: &ast::ProcedureName) -> PathBuf {
156        <Path as Join<ast::ProcedureName>>::join(self.as_path(), other)
157    }
158}
159
160impl Join<ast::QualifiedProcedureName> for PathBuf {
161    fn join(&self, other: &ast::QualifiedProcedureName) -> PathBuf {
162        <Path as Join<ast::QualifiedProcedureName>>::join(self.as_path(), other)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use miden_core::assert_matches;
169
170    use super::*;
171
172    #[test]
173    fn test_join_path_to_path_plain() {
174        let p1 = Path::new("foo");
175        let p2 = Path::new("bar::baz");
176        let joined = Join::join(p1, p2);
177        assert_eq!(joined.as_path(), Path::new("foo::bar::baz"));
178        let mut components = joined.components();
179        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
180        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
181        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
182        assert_matches!(components.next(), None);
183    }
184
185    #[test]
186    fn test_join_absolute_path_to_path_plain() {
187        let p1 = Path::new("foo");
188        let p2 = Path::new("::bar::baz");
189        let joined = Join::join(p1, p2);
190        assert_eq!(joined.as_path(), Path::new("::bar::baz"));
191        let mut components = joined.components();
192        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
193        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
194        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
195        assert_matches!(components.next(), None);
196    }
197
198    #[test]
199    fn test_join_path_to_path_quoted() {
200        let p1 = Path::new("foo");
201        let p2 = Path::new("\"bar::baz\"::qux");
202        let joined = Join::join(p1, p2);
203        assert_eq!(joined.as_path(), Path::new("foo::\"bar::baz\"::qux"));
204        let mut components = joined.components();
205        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
206        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
207        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("qux"))));
208        assert_matches!(components.next(), None);
209    }
210
211    #[test]
212    fn test_join_path_to_absolute_path_quoted() {
213        let p1 = Path::new("::foo");
214        let p2 = Path::new("\"bar::baz\"::qux");
215        let joined = Join::join(p1, p2);
216        assert_eq!(joined.as_path(), Path::new("::foo::\"bar::baz\"::qux"));
217        let mut components = joined.components();
218        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
219        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
220        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
221        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("qux"))));
222        assert_matches!(components.next(), None);
223    }
224
225    #[test]
226    fn test_join_str_to_path_simple() {
227        let p1 = Path::new("foo");
228        let joined = Join::join(p1, "bar");
229        assert_eq!(joined.as_path(), Path::new("foo::bar"));
230        let mut components = joined.components();
231        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
232        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
233        assert_matches!(components.next(), None);
234    }
235
236    #[test]
237    fn test_join_str_to_path_multi_component_quoted() {
238        let p1 = Path::new("foo");
239        let joined = Join::join(p1, "\"bar::baz\"");
240        assert_eq!(joined.as_path(), Path::new("foo::\"bar::baz\""));
241        let mut components = joined.components();
242        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
243        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
244        assert_matches!(components.next(), None);
245    }
246
247    #[test]
248    fn test_join_str_to_path_multi_component_unquoted() {
249        let p1 = Path::new("foo");
250        let joined = Join::join(p1, "bar::baz");
251        assert_eq!(joined.as_path(), Path::new("foo::\"bar::baz\""));
252        let mut components = joined.components();
253        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
254        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
255        assert_matches!(components.next(), None);
256    }
257
258    #[test]
259    fn test_join_qualified_proc_name_to_path() {
260        let p1 = Path::new("foo");
261        let proc = ast::ProcedureName::new("qux").unwrap();
262        let p2 = ast::QualifiedProcedureName::new("bar::baz", proc);
263        let joined = Join::join(p1, &p2);
264        assert_eq!(joined.as_path(), Path::new("foo::bar::baz::qux"));
265        let mut components = joined.components();
266        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
267        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
268        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
269        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("qux"))));
270        assert_matches!(components.next(), None);
271    }
272}