sophia_iri/
_wrap_macro.rs

1#[macro_export]
2/// This macro is used to create a read-only wrapper around a type T,
3/// usually for the purpose of guaranteeing that the wrapped value verifies some condition.
4/// This macro takes care of defining all the usual traits for the wrapper type.
5///
6/// In its most general form, it is used like this:
7/// ```
8/// # #[macro_use] extern crate sophia_iri;
9/// # trait SomeTrait { fn check_some_property(&self) -> bool; }
10/// # #[derive(Debug)]
11/// # struct SomeError {}
12/// wrap! {
13///     #[derive(Clone, Debug)]
14///     MyWrapper<T: SomeTrait> :
15///
16///     // NB: the trait bound is required, as well as the trailing colon (':').
17///     // The derive clause, however, is optional (it will default to Clone, Copy, Debug).
18///
19///     // You can include members in the impl of the wrapper type.
20///     // At the very least, you must define a `new` constructor as the first member,
21///     // that will check the appropriate condition on the wrapped value,
22///     // and return a `Result`.
23///
24///     /// The `new` constructor should carry the documentation of the type;
25///     /// since the generated documentation for the type will point to it.
26///     pub fn new(inner: T) -> Result<Self, SomeError> {
27///         if inner.check_some_property() {
28///             Ok(Self(inner))
29///         } else {
30///             Err(SomeError {})
31///         }
32///     }
33/// }
34/// ```
35///
36/// A more specific form is when the trait that T must satisfy
37/// is [`std::borrow::Borrow<U>`], in which case more traits
38/// can be implemented by the wrapper. This is achieved with
39/// the following syntax:
40/// ```
41/// # #[macro_use] extern crate sophia_iri;
42/// # #[derive(Debug)]
43/// # struct SomeError {}
44/// wrap! { Foo borrowing str :
45///     /// As before
46///     pub fn new(inner: T) -> Result<Self, String> {
47///         if inner.borrow().contains("foo") {
48///             Ok(Self(inner))
49///         } else {
50///             Err(format!("{:?} is not a valid Foo", inner.borrow()))
51///         }
52///     }
53/// }
54/// ```
55///
56/// Two [examples](crate::wrap_macro_examples) are available,
57/// illustrating the members and trait implementation generated by this macro.
58///
59/// NB: the documentation of the wrapper will point to the documentation of the `new` method.
60macro_rules! wrap {
61
62    // general wrapper definition
63    (#[derive($($derive:ident),*)] $wid:ident<$tid:ident: $bound:path>: $new:item $($item:item)*) => {
64        #[derive($($derive),*)]
65
66        #[doc = concat!(
67            "See [`",
68            stringify!($wid),
69            "::new`].",
70        )]
71        pub struct $wid<$tid: $bound>($tid);
72
73        impl<$tid: $bound> $wid<$tid> {
74            $new
75            $($item)*
76
77            #[doc = concat!(
78                "Construct a `",
79                stringify!($wid),
80                "<T>` without checking that the inner value is valid. ",
81                "If it is not, it may result in undefined behaviour.",
82            )]
83            #[allow(dead_code)]
84            pub fn new_unchecked(inner: $tid) -> Self {
85                if cfg!(debug_assertions) {
86                    Self::new(inner).unwrap()
87                } else {
88                    Self(inner)
89                }
90            }
91
92            /// Returns the wrapped value, consuming `self`.
93            #[allow(dead_code)]
94            pub fn unwrap(self) -> $tid {
95                self.0
96            }
97
98            #[doc = concat!(
99                "Map a `",
100                stringify!($wid),
101                "<T>` to a `",
102                stringify!($wid),
103                "<U>` by applying a function to the wrapped value. ",
104                "It does not check that the value returned by the function is valid. ",
105                "If it is not, it may result in undefined behaviour.",
106            )]
107            #[allow(dead_code)]
108            pub fn map_unchecked<F, U>(self, f: F) -> $wid<U>
109            where
110                F: FnOnce(T) -> U,
111                U: $bound,
112            {
113                let inner = self.unwrap();
114                let new_inner: U = f(inner);
115                $wid(new_inner)
116            }
117        }
118
119        impl<T: $bound> std::ops::Deref for $wid<T> {
120            type Target = T;
121            fn deref(&self) -> &T {
122                &self.0
123            }
124        }
125
126        impl<T: $bound> std::convert::AsRef<T> for $wid<T> {
127            fn as_ref(&self) -> &T {
128                &self.0
129            }
130        }
131
132        impl<T: $bound> std::borrow::Borrow<T> for $wid<T> {
133            fn borrow(&self) -> &T {
134                &self.0
135            }
136        }
137
138        impl<T, U> std::cmp::PartialEq<$wid<T>> for $wid<U>
139        where
140            T: $bound,
141            U: $bound + std::cmp::PartialEq<T>,
142        {
143            fn eq(&self, rhs: &$wid<T>) -> bool {
144                self.0 == rhs.0
145            }
146        }
147
148        impl<T> std::cmp::Eq for $wid<T>
149        where
150            T: $bound + std::cmp::Eq,
151        {}
152
153        impl<T, U> std::cmp::PartialOrd<$wid<T>> for $wid<U>
154        where
155            T: $bound,
156            U: $bound + std::cmp::PartialOrd<T>,
157        {
158            fn partial_cmp(&self, rhs: &$wid<T>) -> std::option::Option<std::cmp::Ordering> {
159                std::cmp::PartialOrd::partial_cmp(&self.0, &rhs.0)
160            }
161        }
162
163        impl<T> std::cmp::Ord for $wid<T>
164        where
165            T: $bound + std::cmp::Eq + std::cmp::Ord,
166        {
167            fn cmp(&self, rhs: &$wid<T>) -> std::cmp::Ordering {
168                std::cmp::Ord::cmp(&self.0, &rhs.0)
169            }
170        }
171
172
173        impl<T> std::hash::Hash for $wid<T>
174        where
175            T: $bound + std::hash::Hash,
176        {
177            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
178                self.0.hash(state);
179            }
180        }
181    };
182
183    // general wrapper definition, with implicit derive clause
184    ($wid:ident<$tid:ident: $bound:path>: $new:item $($item:item)*) => {
185        $crate::wrap!( #[derive(Clone, Copy, Debug)] $wid<$tid: $bound>: $new $($item)* );
186
187    };
188
189    // very specific wrapper definition for Borrow<str>, relying on the less specific "borrowing" definition below
190    ($wid:ident borrowing str: $new:item $($item:item)*) => {
191        $crate::wrap!{
192            #[derive(Clone, Copy)]
193            $wid borrowing str:
194
195            $new
196
197            $($item)*
198
199            /// Gets a reference to the underlying &str.
200            pub fn as_str(&self) -> &str {
201                self.0.borrow()
202            }
203        }
204
205        impl<T: std::borrow::Borrow<str>> std::fmt::Debug for $wid<T> {
206            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
207                write!(f, "{}({:?})", stringify!($wid), self.0.borrow())
208            }
209        }
210    };
211
212    // specific wrapper definition with the "borrowing" keyword ;
213    // shortcut for Borrow<T>, and adds more trait implementation
214    (#[derive($($derive:ident),*)] $wid:ident borrowing $bid:ty: $new:item $($item:item)*) => {
215        $crate::wrap!( #[derive($($derive),*)] $wid<T: std::borrow::Borrow<$bid>>: $new $($item)* );
216
217        impl<T> $wid<T>
218        where
219            T: std::borrow::Borrow<$bid>,
220        {
221            #[doc = concat!(
222                "Convert from `&",
223                stringify!($wid),
224                "<T>` to `",
225                stringify!($wid),
226                "<&",
227                stringify!($bid),
228                ">`.",
229            )]
230            #[allow(dead_code)]
231            pub fn as_ref(&self) -> $wid<&$bid> {
232                $wid(self.0.borrow())
233            }
234        }
235
236        impl $wid<&'static $bid> {
237            #[doc = concat!(
238                "Construct a `",
239                stringify!($wid),
240                "<&'static ",
241                stringify!($bid),
242                ">` without checking that the inner value is valid. ",
243                "If it is not, it may result in undefined behaviour.",
244            )]
245            #[allow(dead_code)]
246            #[must_use] pub const fn new_unchecked_const(inner: &'static $bid) -> Self {
247                $wid(inner)
248            }
249        }
250
251        impl<T: std::borrow::Borrow<$bid>> std::convert::AsRef<$bid> for $wid<T> {
252            fn as_ref(&self) -> &$bid {
253                &self.0.borrow()
254            }
255        }
256
257        impl<T: std::borrow::Borrow<$bid>> std::borrow::Borrow<$bid> for $wid<T> {
258            fn borrow(&self) -> &$bid {
259                &self.0.borrow()
260            }
261        }
262
263        impl<T> std::cmp::PartialEq<$bid> for $wid<T>
264        where
265            T: std::borrow::Borrow<$bid>,
266            $bid: std::cmp::PartialEq,
267        {
268                fn eq(&self, other: &$bid) -> bool {
269                    self.0.borrow() == other
270                }
271        }
272
273        impl<T> std::cmp::PartialEq<$wid<T>> for $bid
274        where
275            T: std::borrow::Borrow<$bid>,
276            $bid: std::cmp::PartialEq,
277        {
278                fn eq(&self, other: &$wid<T>) -> bool {
279                    self == other.0.borrow()
280                }
281        }
282
283        impl<T> std::cmp::PartialOrd<$bid> for $wid<T>
284        where
285            T: std::borrow::Borrow<$bid>,
286            $bid: std::cmp::PartialOrd,
287        {
288            fn partial_cmp(&self, other: &$bid) -> std::option::Option<std::cmp::Ordering> {
289                self.0.borrow().partial_cmp(other)
290            }
291        }
292
293        impl<T> std::cmp::PartialOrd<$wid<T>> for $bid
294        where
295            T: std::borrow::Borrow<$bid>,
296            $bid: std::cmp::PartialOrd,
297        {
298            fn partial_cmp(&self, other: &$wid<T>) -> std::option::Option<std::cmp::Ordering> {
299                self.partial_cmp(other.0.borrow())
300            }
301        }
302    };
303
304    // same as the specific "borrowing" wrapper declaration above, with implicit derive clause
305    ($wid:ident borrowing $bid:ty: $new:item $($item:item)*) => {
306        $crate::wrap!( #[derive(Clone, Copy, Debug)] $wid borrowing $bid: $new $($item)* );
307    };
308}
309
310#[cfg(test)]
311pub mod test_simple_wrap {
312    pub trait Number {
313        fn even(&self) -> bool;
314    }
315    impl Number for i32 {
316        fn even(&self) -> bool {
317            *self % 2 == 0
318        }
319    }
320    impl Number for isize {
321        fn even(&self) -> bool {
322            *self % 2 == 0
323        }
324    }
325
326    wrap! { Even<T: Number>:
327        pub fn new(inner: T) -> Result<Self, ()> {
328            if inner.even() {
329                Ok(Even(inner))
330            } else {
331                Err(())
332            }
333        }
334    }
335
336    #[test]
337    fn constructor_succeeds() {
338        assert!(Even::new(42).is_ok());
339    }
340
341    #[test]
342    fn constructor_fails() {
343        assert!(Even::new(43).is_err());
344    }
345
346    // only check that this compiles
347    #[allow(dead_code)]
348    fn unwrap() {
349        let even = Even(42);
350        let _: isize = even.unwrap();
351    }
352
353    // only check that this compiles
354    #[allow(dead_code)]
355    fn deref() {
356        let even = Even(42);
357        let _: &isize = &even;
358    }
359
360    // only check that this compiles
361    #[allow(dead_code)]
362    fn as_ref() {
363        let even = Even(42);
364        let _: &isize = even.as_ref();
365    }
366
367    // only check that this compiles
368    #[allow(dead_code)]
369    fn borrow() {
370        use std::borrow::Borrow;
371        let even = Even(42);
372        let _: &isize = even.borrow();
373    }
374}
375
376#[cfg(test)]
377pub mod test_wrap_borrowing {
378    wrap! { Foo borrowing str :
379        /// The constructor of Foo
380        pub fn new(inner: T) -> Result<Self, ()> {
381            if inner.borrow().contains("foo") {
382                Ok(Foo(inner))
383            } else {
384                Err(())
385            }
386        }
387    }
388
389    #[test]
390    fn new_succeeds() {
391        assert!(Foo::new("this foo is good").is_ok());
392    }
393
394    #[test]
395    fn new_fails() {
396        assert!(Foo::new("this bar is bad").is_err());
397    }
398
399    #[test]
400    fn partial_eq() {
401        let f1a = Foo::new("foo1".to_string()).unwrap();
402        let f1b = Foo::new("foo1").unwrap();
403        let f2 = Foo::new("foo2").unwrap();
404        assert_eq!(&f1a, "foo1");
405        assert_eq!(&f1a, "foo1");
406        assert_eq!(&f2, "foo2");
407        assert_ne!(&f2, "foo1");
408        assert_eq!("foo1", &f1a);
409        assert_eq!("foo1", &f1a);
410        assert_eq!("foo2", &f2);
411        assert_ne!("foo1", &f2);
412        assert_eq!(f1a.as_str(), f1b.as_str());
413    }
414
415    // only check that this compiles
416    #[allow(dead_code)]
417    fn new_unchecked() {
418        let _: Foo<String> = Foo::new_unchecked(String::new());
419    }
420
421    // only check that this compiles
422    #[allow(dead_code)]
423    fn unwrap() {
424        let foo = Foo("foo".to_string());
425        let _: String = foo.unwrap();
426    }
427
428    // only check that this compiles
429    #[allow(dead_code)]
430    fn deref() {
431        let foo = Foo("this foo is good".to_string());
432        let _: &String = &foo;
433        let _: &str = &foo;
434    }
435
436    // only check that this compiles
437    #[allow(dead_code)]
438    fn as_ref_trait() {
439        let foo = Foo("this foo is good".to_string());
440        let _: &String = AsRef::as_ref(&foo);
441        let _: &str = AsRef::as_ref(&foo);
442    }
443
444    // only check that this compiles
445    #[allow(dead_code)]
446    fn borrow() {
447        use std::borrow::Borrow;
448        let foo = Foo("this foo is good".to_string());
449        let _: &String = foo.borrow();
450        let _: &str = foo.borrow();
451    }
452
453    // only check that this compiles
454    #[allow(dead_code)]
455    fn as_ref() {
456        let foo = Foo("this foo is good".to_string());
457        let _: Foo<&str> = foo.as_ref();
458    }
459}