frunk_core/
macros.rs

1//! Macros all collected into a single module so that the order of `mod`
2//! statements in `lib.rs` does not matter.
3
4/// Returns an `HList` based on the values passed in.
5///
6/// Helps to avoid having to write nested `HCons`.
7///
8/// # Examples
9///
10/// ```
11/// # use frunk_core::hlist;
12/// # fn main() {
13/// let h = hlist![13.5f32, "hello", Some(41)];
14/// let (h1, (h2, h3)) = h.into_tuple2();
15/// assert_eq!(h1, 13.5f32);
16/// assert_eq!(h2, "hello");
17/// assert_eq!(h3, Some(41));
18///
19/// // Also works when you have trailing commas
20/// let h4 = hlist!["yo",];
21/// let h5 = hlist![13.5f32, "hello", Some(41),];
22/// assert_eq!(h4, hlist!["yo"]);
23/// assert_eq!(h5, hlist![13.5f32, "hello", Some(41)]);
24///
25/// // Use "...tail" to append an existing list at the end
26/// let h6 = hlist![12, ...h5];
27/// assert_eq!(h6, hlist![12, 13.5f32, "hello", Some(41)]);
28/// # }
29/// ```
30#[macro_export]
31macro_rules! hlist {
32    () => { $crate::hlist::HNil };
33    (...$rest:expr) => { $rest };
34    ($a:expr) => { $crate::hlist![$a,] };
35    ($a:expr, $($tok:tt)*) => {
36        $crate::hlist::HCons {
37            head: $a,
38            tail: $crate::hlist![$($tok)*],
39        }
40    };
41}
42
43/// Macro for pattern-matching on HLists.
44///
45/// Taken from <https://github.com/tbu-/rust-rfcs/blob/master/text/0873-type-macros.md>
46///
47/// # Examples
48///
49/// ```
50/// # use frunk_core::{hlist, hlist_pat};
51/// # fn main() {
52/// let h = hlist![13.5f32, "hello", Some(41)];
53/// let hlist_pat![a1, a2, a3] = h;
54/// assert_eq!(a1, 13.5f32);
55/// assert_eq!(a2, "hello");
56/// assert_eq!(a3, Some(41));
57///
58/// // Use "...tail" to match the rest of the list
59/// let hlist_pat![b_head, ...b_tail] = h;
60/// assert_eq!(b_head, 13.5f32);
61/// assert_eq!(b_tail, hlist!["hello", Some(41)]);
62///
63/// // You can also use "..." to just ignore the rest.
64/// let hlist_pat![c, ...] = h;
65/// assert_eq!(c, 13.5f32);
66/// # }
67/// ```
68#[macro_export]
69macro_rules! hlist_pat {
70    () => { $crate::hlist::HNil };
71    (...) => { _ };
72    (...$rest:pat) => { $rest };
73    (_) => { $crate::hlist_pat![_,] };
74    ($a:pat) => { $crate::hlist_pat![$a,] };
75    (_, $($tok:tt)*) => {
76        $crate::hlist::HCons {
77            tail: $crate::hlist_pat![$($tok)*],
78            ..
79        }
80    };
81    ($a:pat, $($tok:tt)*) => {
82        $crate::hlist::HCons {
83            head: $a,
84            tail: $crate::hlist_pat![$($tok)*],
85        }
86    };
87}
88
89/// Returns a type signature for an HList of the provided types
90///
91/// This is a type macro (introduced in Rust 1.13) that makes it easier
92/// to write nested type signatures.
93///
94/// # Examples
95///
96/// ```
97/// # use frunk_core::{hlist, HList};
98/// # fn main() {
99/// let h: HList!(f32, &str, Option<i32>) = hlist![13.5f32, "hello", Some(41)];
100///
101/// // Use "...Tail" to append another HList type at the end.
102/// let h: HList!(f32, ...HList!(&str, Option<i32>)) = hlist![13.5f32, "hello", Some(41)];
103/// # }
104/// ```
105#[macro_export]
106macro_rules! HList {
107    () => { $crate::hlist::HNil };
108    (...$Rest:ty) => { $Rest };
109    ($A:ty) => { $crate::HList![$A,] };
110    ($A:ty, $($tok:tt)*) => {
111        $crate::hlist::HCons<$A, $crate::HList![$($tok)*]>
112    };
113}
114
115/// Returns a type signature for a Coproduct of the provided types
116///
117/// This is a type macro (introduced in Rust 1.13) that makes it easier
118/// to write nested type signatures.
119///
120/// # Examples
121///
122/// ```
123/// # fn main() {
124/// use frunk_core::Coprod;
125///
126/// type I32Bool = Coprod!(i32, bool);
127/// let co1 = I32Bool::inject(3);
128///
129/// // Use ...Tail to append another coproduct at the end.
130/// let co2 = <Coprod!(&str, String, ...I32Bool)>::inject(3);
131/// # }
132/// ```
133#[macro_export]
134macro_rules! Coprod {
135    () => { $crate::coproduct::CNil };
136    (...$Rest:ty) => { $Rest };
137    ($A:ty) => { $crate::Coprod![$A,] };
138    ($A:ty, $($tok:tt)*) => {
139        $crate::coproduct::Coproduct<$A, $crate::Coprod![$($tok)*]>
140    };
141}
142
143/// Used for creating a Field
144///
145/// There are 3 forms of this macro:
146///
147/// * Create an instance of the `Field` struct with a tuple name type
148///   and any given value. The runtime-retrievable static name
149///   field will be set to the the concatenation of the types passed in the
150///   tuple type used as the first argument.
151///
152/// # Examples
153///
154/// ```
155/// use frunk::labelled::chars::*;
156/// use frunk_core::field;
157/// # fn main() {
158/// let labelled = field![(n,a,m,e), "joe"];
159/// assert_eq!(labelled.name, "name");
160/// assert_eq!(labelled.value, "joe")
161/// # }
162/// ```
163///
164/// * Create an instance of the `Field` struct with a custom, non-tuple
165///   name type and a value. The runtime-retrievable static name field
166///   will be set to the stringified version of the type provided.
167///
168/// ```
169/// # fn main() {
170/// use frunk_core::field;
171/// enum first_name {}
172/// let labelled = field![first_name, "Joe"];
173/// assert_eq!(labelled.name, "first_name");
174/// assert_eq!(labelled.value, "Joe");
175/// # }
176/// ```
177///
178/// * Create an instance of the `Field` struct with any name type and value,
179///   _and_ a custom name, passed as the last argument in the macro
180///
181/// ```
182/// use frunk::labelled::chars::*;
183/// use frunk_core::field;
184/// # fn main() {
185/// // useful aliasing of our type-level string
186/// type age = (a, g, e);
187/// let labelled = field![age, 30, "Age"];
188/// assert_eq!(labelled.name, "Age");
189/// assert_eq!(labelled.value, 30);
190/// # }
191/// ```
192#[macro_export]
193macro_rules! field {
194    // No name provided and type is a tuple
195    (($($repeated: ty),*), $value: expr) => {
196        $crate::field!( ($($repeated),*), $value, concat!( $(stringify!($repeated)),* ) )
197    };
198    // No name provided and type is a tuple, but with trailing commas
199    (($($repeated: ty,)*), $value: expr) => {
200        $crate::field!( ($($repeated),*), $value )
201    };
202    // We are provided any type, with no stable name
203    ($name_type: ty, $value: expr) => {
204        $crate::field!( $name_type, $value, stringify!($name_type) )
205    };
206    // We are provided any type, with a stable name
207    ($name_type: ty, $value: expr, $name: expr) => {
208        $crate::labelled::field_with_name::<$name_type,_>($name, $value)
209    }
210}
211
212/// Returns a polymorphic function for use with mapping/folding heterogeneous
213/// types.
214///
215/// This macro is intended for use with simple scenarios, and doesn't handle
216/// trait implementation bounds or where clauses (it might in the future when
217/// procedural macros land). If it doesn't work for you, simply implement
218/// Func on your own.
219///
220/// # Examples
221///
222/// ```
223/// # fn main() {
224/// use frunk_core::{Coprod, poly_fn};
225/// type I32F32Str<'a> = Coprod!(i32, f32, &'a str);
226///
227/// let co1 = I32F32Str::inject("lollerskates");
228/// let folded = co1.fold(poly_fn!(
229///   ['a] |x: &'a str| -> i8 { 1 },
230///   |x: i32| -> i8 { 2 },
231///   |f: f32| -> i8 { 3 },
232/// ));
233///
234/// assert_eq!(folded, 1);
235/// # }
236#[macro_export]
237macro_rules! poly_fn {
238    // encountered first func w/ type params
239    ([$($tparams: tt),*] |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block , $($rest: tt)*)
240    => { $crate::poly_fn!(
241       p~ [$($tparams, )*] |$arg: $arg_typ| -> $ret_typ $body, ~p  f~ ~f $($rest)*
242    )};
243    // encountered first func w/ type params, trailing comma on tparams
244    ([$($tparams: tt, )*] |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block , $($rest: tt)*)
245    => { $crate::poly_fn!(
246       p~ [$($tparams, )*] |$arg: $arg_typ| -> $ret_typ $body, ~p  f~ ~f $($rest)*
247    )};
248    // encountered first func w/o type params
249    (|$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block, $($rest: tt)*)
250    => { $crate::poly_fn!(
251       p~ ~p  f~ |$arg: $arg_typ| -> $ret_typ $body, ~f $($rest)*
252    )};
253
254    // encountered non-first func w/ type params
255    (p~ $([$($pars: tt, )*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty $p_body: block , )* ~p f~ $(|$f_args: ident : $f_arg_typ: ty| -> $f_ret_typ: ty $f_body: block , )* ~f [$($tparams: tt),*] |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block , $($rest: tt)*)
256    => { $crate::poly_fn!(
257       p~ [$($tparams, )*] |$arg: $arg_typ| -> $ret_typ $body, $( [$($pars, )*] |$p_args: $p_arg_typ| -> $p_ret_typ $p_body, )* ~p  f~ $(|$f_args: $f_arg_typ| -> $f_ret_typ $f_body, )* ~f $($rest)*
258    )};
259    // encountered non-first func w/ type params, trailing comma in tparams
260    (p~ $([$($pars: tt, )*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty { $p_body: block }, )* ~p f~ $(|$f_args: ident : $f_arg_typ: ty| -> $f_ret_typ: ty $f_body: block, )* ~f [$($tparams: tt, )*] |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block, $($rest: tt)*)
261    => { $crate::poly_fn!(
262       p~ [$($tparams, )*] |$arg: $arg_typ| -> $ret_typ $body, $( [$($pars, )*] |$p_args: $p_arg_typ| -> $p_ret_typ $p_body, )* ~p  f~ $(|$f_args: $f_arg_typ| -> $f_ret_typ $f_body, )* ~f $($rest)*
263    )};
264    // encountered non-first func w/o type params
265    (p~ $([$($pars: tt, )*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty $p_body: block, )* ~p f~ $(|$f_args: ident : $f_arg_typ: ty| -> $f_ret_typ: ty $f_body: block, )* ~f |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block, $($rest: tt)*)
266    => { $crate::poly_fn!(
267       p~ $( [$($pars, )*] |$p_args: $p_arg_typ| -> $p_ret_typ $p_body, )* ~p  f~ |$arg: $arg_typ| -> $ret_typ $body, $(|$f_args: $f_arg_typ| -> $f_ret_typ $f_body, )* ~f $($rest)*
268    )};
269
270    // last w/ type params, for when there is no trailing comma on the funcs...
271    (p~ $([$($pars: tt, )*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty $p_body: block, )* ~p f~ $(|$f_args: ident : $f_arg_typ: ty| -> $f_ret_typ: ty $f_body: block, )* ~f [$($tparams: tt),*] |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block)
272    => { $crate::poly_fn!(
273       p~ [$($tparams, )*] |$arg: $arg_typ| -> $ret_typ $body, $( [$($pars, )*] |$p_args: $p_arg_typ| -> $p_ret_typ $p_body, )* ~p  f~ $(|$f_args: $f_arg_typ| -> $f_ret_typ $f_body, )* ~f
274    )};
275    // last w/ type params, for when there is a trailing comma in tparams, but no trailing comma on the funcs..
276    (p~ $([$($pars: tt, )*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty $p_body: block, )* ~p f~ $(|$f_args: ident : $f_arg_typ: ty| -> $f_ret_typ: ty $f_body: block, )* ~f [$($tparams: tt, )*] |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block)
277    => { $crate::poly_fn!(
278       p~ [$($tparams, )*] |$arg: $arg_typ| -> $ret_typ $body, $( [$($pars, )*] |$p_args: $p_arg_typ| -> $p_ret_typ $p_body, )* ~p  f~ $(|$f_args: $f_arg_typ| -> $f_ret_typ $f_body, )* ~f
279    )};
280    // last w/o type params, for when there is no trailing comma on the funcs...
281    (p~ $([$($pars: tt)*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty $p_body: block, )* ~p f~ $(|$f_args: ident : $f_arg_typ: ty| -> $f_ret_typ: ty $f_body: block, )* ~f |$arg: ident : $arg_typ: ty| -> $ret_typ: ty $body: block)
282    => { $crate::poly_fn!(
283       p~ $( [$($pars, )*] |$p_args: $p_arg_typ| -> $p_ret_typ $p_body, )* ~p  f~ |$arg: $arg_typ| -> $ret_typ $body, $(|$f_args: $f_arg_typ| -> $f_ret_typ $f_body, )* ~f
284    )};
285
286    // unroll
287    (p~ $([$($pars: tt, )*] |$p_args: ident : $p_arg_typ: ty| -> $p_ret_typ: ty $p_body: block, )* ~p f~ $(|$args: ident : $arg_typ: ty| -> $ret_typ: ty $body: block, )* ~f) => {{
288        struct F;
289        $(
290            impl<$($pars,)*> $crate::traits::Func<$p_arg_typ> for F {
291                type Output = $p_ret_typ;
292
293                fn call($p_args: $p_arg_typ) -> Self::Output { $p_body }
294            }
295        )*
296        $(
297            impl $crate::traits::Func<$arg_typ> for F {
298                type Output = $ret_typ;
299
300                fn call($args: $arg_typ) -> Self::Output { $body }
301            }
302        )*
303        $crate::traits::Poly(F)
304    }}
305}
306
307#[cfg(test)]
308mod tests {
309    #[allow(clippy::diverging_sub_expression)]
310    #[test]
311    fn trailing_commas() {
312        use crate::test_structs::unit_copy::{A, B};
313
314        let hlist_pat![]: HList![] = hlist![];
315        let hlist_pat![A]: HList![A] = hlist![A];
316        let hlist_pat![A,]: HList![A,] = hlist![A,];
317        let hlist_pat![A, B]: HList![A, B] = hlist![A, B];
318        let hlist_pat![A, B,]: HList![A, B,] = hlist![A, B,];
319
320        let falsum = || false;
321        if falsum() {
322            let _: Coprod![] = panic!();
323        }
324        if falsum() {
325            let _: Coprod![A] = panic!();
326        }
327        if falsum() {
328            let _: Coprod![A,] = panic!();
329        }
330        if falsum() {
331            let _: Coprod![A, B] = panic!();
332        }
333        if falsum() {
334            let _: Coprod![A, B,] = panic!();
335        }
336    }
337
338    #[test]
339    fn ellipsis_tail() {
340        use crate::coproduct::Coproduct;
341        use crate::test_structs::unit_copy::{A, B, C};
342
343        // hlist: accepted locations, and consistency between macros
344        let hlist_pat![...hlist_pat![C]]: HList![...HList![C]] = { hlist![...hlist![C]] };
345        let hlist_pat![A, ...hlist_pat![C]]: HList![A, ...HList![C]] = { hlist![A, ...hlist![C]] };
346        let hlist_pat![A, B, ...hlist_pat![C]]: HList![A, B, ...HList![C]] =
347            { hlist![A, B, ...hlist![C]] };
348
349        // hlist: ellipsis semantics
350        //   (by pairing an ellipsis call with a non-ellipsis call)
351        let hlist_pat![A, B, C] = hlist![A, ...hlist![B, C]];
352        let hlist_pat![A, ...hlist_pat![B, C]] = hlist![A, B, C];
353
354        // coprod: accepted locations and semantics
355        let choice: Coprod![A, B, C] = Coproduct::inject(A);
356        let _: Coprod![...Coprod![A, B, C]] = choice;
357        let _: Coprod![A, ...Coprod![B, C]] = choice;
358        let _: Coprod![A, B, ...Coprod![C]] = choice;
359    }
360
361    #[test]
362    fn ellipsis_ignore() {
363        use crate::test_structs::unit_copy::{A, B, C, D, E};
364
365        // '...' accepted locations
366        let hlist_pat![...] = hlist![A, B, C, D, E];
367        let hlist_pat![A, ...] = hlist![A, B, C, D, E];
368        let hlist_pat![A, B, ...] = hlist![A, B, C, D, E];
369    }
370
371    #[test]
372    fn poly_fn_macro_test() {
373        let h = hlist![9000, "joe", 41f32, "schmoe", 50];
374        let h2 = h.map(poly_fn!(
375            |x: i32| -> bool { x > 100 },
376            |_x: f32| -> &'static str { "dummy" },
377            ['a] |x: &'a str| -> usize { x.len() }
378        ));
379        assert_eq!(h2, hlist![true, 3, "dummy", 6, false]);
380    }
381
382    #[test]
383    fn poly_fn_macro_coproduct_test() {
384        type I32F32StrBool<'a> = Coprod!(i32, f32, &'a str);
385
386        let co1 = I32F32StrBool::inject("lollerskates");
387        let folded = co1.fold(poly_fn!(
388            ['a] |_x: &'a str| -> i8 { 1 },
389            |_x: i32| -> i8 { 2 },
390            |_f: f32| -> i8 { 3 },
391        ));
392        assert_eq!(folded, 1);
393    }
394
395    #[test]
396    fn poly_fn_macro_trailing_commas_test() {
397        let h = hlist![9000, "joe", 41f32, "schmoe", 50];
398        let h2 = h.map(poly_fn!(
399            |x: i32| -> bool { x > 100 },
400            |_x: f32| -> &'static str { "dummy" },
401            ['a,] |x: &'a str| -> usize { x.len() },
402        ));
403        assert_eq!(h2, hlist![true, 3, "dummy", 6, false]);
404    }
405
406    #[test]
407    fn poly_fn_macro_multiline_bodies_test() {
408        let h = hlist![9000, 1, -1];
409        let h2 = h.map(poly_fn!(|x: i32| -> bool {
410            let a = if x > 100 { 1 } else { -1 };
411            a > 0
412        },));
413        assert_eq!(h2, hlist![true, false, false]);
414    }
415
416    #[test]
417    #[deny(clippy::unneeded_field_pattern)]
418    fn unneeded_field_pattern() {
419        let hlist_pat![_, _] = hlist![1, 2];
420        let hlist_pat![foo, _, baz] = hlist!["foo", "bar", "baz"];
421        assert_eq!(foo, "foo");
422        assert_eq!(baz, "baz");
423    }
424}