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            match other.to_absolute() {
48                Ok(path) => path.into_owned(),
49                Err(_) => other.to_path_buf(),
50            }
51        } else {
52            let mut buf = self.to_path_buf();
53            buf.push(other);
54
55            buf
56        }
57    }
58}
59
60impl Join<PathBuf> for Path {
61    #[inline(always)]
62    fn join(&self, other: &PathBuf) -> PathBuf {
63        <Path as Join<Path>>::join(self, other.as_path())
64    }
65}
66
67impl Join<str> for Path {
68    fn join(&self, other: &str) -> PathBuf {
69        let mut buf = self.to_path_buf();
70        buf.push_component(other);
71        buf
72    }
73}
74
75impl Join<String> for Path {
76    fn join(&self, other: &String) -> PathBuf {
77        <Path as Join<str>>::join(self, other)
78    }
79}
80
81impl Join<Box<str>> for Path {
82    fn join(&self, other: &Box<str>) -> PathBuf {
83        <Path as Join<str>>::join(self, other)
84    }
85}
86
87impl Join<Arc<str>> for Path {
88    fn join(&self, other: &Arc<str>) -> PathBuf {
89        <Path as Join<str>>::join(self, other)
90    }
91}
92
93impl Join<ast::Ident> for Path {
94    fn join(&self, other: &ast::Ident) -> PathBuf {
95        <Path as Join<str>>::join(self, other.as_str())
96    }
97}
98
99impl Join<ast::ProcedureName> for Path {
100    fn join(&self, other: &ast::ProcedureName) -> PathBuf {
101        <Path as Join<str>>::join(self, other.as_str())
102    }
103}
104
105impl Join<ast::QualifiedProcedureName> for Path {
106    fn join(&self, other: &ast::QualifiedProcedureName) -> PathBuf {
107        let mut buf = <Path as Join<Path>>::join(self, other.namespace());
108        buf.push_component(other.name());
109        buf
110    }
111}
112
113impl Join<Path> for PathBuf {
114    #[inline]
115    fn join(&self, other: &Path) -> PathBuf {
116        <Path as Join<Path>>::join(self.as_path(), other)
117    }
118}
119
120impl Join<PathBuf> for PathBuf {
121    #[inline(always)]
122    fn join(&self, other: &PathBuf) -> PathBuf {
123        <Path as Join<Path>>::join(self.as_path(), other.as_path())
124    }
125}
126
127impl Join<str> for PathBuf {
128    fn join(&self, other: &str) -> PathBuf {
129        <Path as Join<str>>::join(self.as_path(), other)
130    }
131}
132
133impl Join<String> for PathBuf {
134    fn join(&self, other: &String) -> PathBuf {
135        <Path as Join<str>>::join(self.as_path(), other)
136    }
137}
138
139impl Join<Box<str>> for PathBuf {
140    fn join(&self, other: &Box<str>) -> PathBuf {
141        <Path as Join<str>>::join(self.as_path(), other)
142    }
143}
144
145impl Join<Arc<str>> for PathBuf {
146    fn join(&self, other: &Arc<str>) -> PathBuf {
147        <Path as Join<str>>::join(self.as_path(), other)
148    }
149}
150
151impl Join<ast::Ident> for PathBuf {
152    fn join(&self, other: &ast::Ident) -> PathBuf {
153        <Path as Join<ast::Ident>>::join(self.as_path(), other)
154    }
155}
156
157impl Join<ast::ProcedureName> for PathBuf {
158    fn join(&self, other: &ast::ProcedureName) -> PathBuf {
159        <Path as Join<ast::ProcedureName>>::join(self.as_path(), other)
160    }
161}
162
163impl Join<ast::QualifiedProcedureName> for PathBuf {
164    fn join(&self, other: &ast::QualifiedProcedureName) -> PathBuf {
165        <Path as Join<ast::QualifiedProcedureName>>::join(self.as_path(), other)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use core::assert_matches;
172
173    use super::*;
174
175    #[test]
176    fn test_join_path_to_path_plain() {
177        let p1 = Path::new("foo");
178        let p2 = Path::new("bar::baz");
179        let joined = Join::join(p1, p2);
180        assert_eq!(joined.as_path(), Path::new("foo::bar::baz"));
181        let mut components = joined.components();
182        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
183        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
184        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
185        assert_matches!(components.next(), None);
186    }
187
188    #[test]
189    fn test_join_absolute_path_to_path_plain() {
190        let p1 = Path::new("foo");
191        let p2 = Path::new("::bar::baz");
192        let joined = Join::join(p1, p2);
193        assert_eq!(joined.as_path(), Path::new("::bar::baz"));
194        let mut components = joined.components();
195        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
196        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
197        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
198        assert_matches!(components.next(), None);
199    }
200
201    #[test]
202    fn test_join_invalid_absolute_path_does_not_panic() {
203        let p1 = Path::new("foo");
204        let invalid = alloc::format!("::{}", "a".repeat(Path::MAX_COMPONENT_LENGTH + 1));
205        let p2 = Path::new(&invalid);
206
207        let joined = Join::join(p1, p2);
208
209        assert_eq!(joined.as_path(), p2);
210    }
211
212    #[test]
213    fn test_join_path_to_path_quoted() {
214        let p1 = Path::new("foo");
215        let p2 = Path::new("\"bar::baz\"::qux");
216        let joined = Join::join(p1, p2);
217        assert_eq!(joined.as_path(), Path::new("foo::\"bar::baz\"::qux"));
218        let mut components = joined.components();
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_path_to_absolute_path_quoted() {
227        let p1 = Path::new("::foo");
228        let p2 = Path::new("\"bar::baz\"::qux");
229        let joined = Join::join(p1, p2);
230        assert_eq!(joined.as_path(), Path::new("::foo::\"bar::baz\"::qux"));
231        let mut components = joined.components();
232        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
233        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
234        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
235        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("qux"))));
236        assert_matches!(components.next(), None);
237    }
238
239    #[test]
240    fn test_join_str_to_path_simple() {
241        let p1 = Path::new("foo");
242        let joined = Join::join(p1, "bar");
243        assert_eq!(joined.as_path(), Path::new("foo::bar"));
244        let mut components = joined.components();
245        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
246        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
247        assert_matches!(components.next(), None);
248    }
249
250    #[test]
251    fn test_join_str_to_path_multi_component_quoted() {
252        let p1 = Path::new("foo");
253        let joined = Join::join(p1, "\"bar::baz\"");
254        assert_eq!(joined.as_path(), Path::new("foo::\"bar::baz\""));
255        let mut components = joined.components();
256        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
257        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
258        assert_matches!(components.next(), None);
259    }
260
261    #[test]
262    fn test_join_str_to_path_multi_component_unquoted() {
263        let p1 = Path::new("foo");
264        let joined = Join::join(p1, "bar::baz");
265        assert_eq!(joined.as_path(), Path::new("foo::\"bar::baz\""));
266        let mut components = joined.components();
267        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
268        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar::baz\""))));
269        assert_matches!(components.next(), None);
270    }
271
272    #[test]
273    fn test_join_qualified_proc_name_to_path() {
274        let p1 = Path::new("foo");
275        let proc = ast::ProcedureName::new("qux").unwrap();
276        let p2 = ast::QualifiedProcedureName::new("bar::baz", proc);
277        let joined = Join::join(p1, &p2);
278        assert_eq!(joined.as_path(), Path::new("foo::bar::baz::qux"));
279        let mut components = joined.components();
280        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
281        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
282        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
283        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("qux"))));
284        assert_matches!(components.next(), None);
285    }
286}