Skip to main content

devela/code/util/
is.rs

1// devela/src/code/util/is.rs
2//
3//! Defines inline if macro [`is!`].
4//
5
6#[doc = crate::_tags!(code)]
7/// Conditional evaluation.
8#[doc = crate::_doc_meta!{
9    location("code/util", macro is),
10}]
11/// Combines:
12/// 1. `if`/`else` conditions
13/// 2. `if let` pattern matching
14/// 3. expression or statement-style branches
15/// 4. omitted unit branches
16///
17/// A branch followed by `;` is evaluated as a statement, discarding its value.
18///
19/// An omitted branch expands to an empty block.
20/// When only the `else` branch is provided, it must evaluate to `()`
21/// or diverge with `return`, `break`, `panic!`, etc.
22///
23/// # Examples
24///
25/// 1. Replacing `if`:
26/// ```
27/// use devela::is;
28///
29/// is![true, print!("true")];
30/// let s = is![1 > 0, true, false];
31/// assert_eq!(s, true);
32/// ```
33///
34/// 2. Statement-style branches:
35/// ```
36/// use devela::is;
37///
38/// let mut n = 0;
39/// is![true, n += 1;];
40/// is![false, n += 10;, n += 1;];
41/// assert_eq!(n, 2);
42/// ```
43///
44/// 3. Omitted branches:
45/// ```
46/// use devela::is;
47///
48/// let mut n = 0;
49/// is![false, , n += 1;]; // only else
50/// is![true, n += 1;,];   // empty else
51/// assert_eq!(n, 2);
52/// ```
53///
54/// 4. Replacing `if let`:
55/// ```
56/// use devela::is;
57///
58/// let num = Some(123);
59/// is![let Some(n) = num, println!("num:{n}"), {
60///     dbg![num];
61/// }];
62/// ```
63///
64/// 5. `if let` with statement and omitted branches:
65/// ```
66/// use devela::is;
67///
68/// let mut n = 0;
69/// is![let Some(v) = Some(2), n += v;];
70/// is![let Some(_) = None::<usize>, , n += 1;];
71/// assert_eq!(n, 3);
72/// ```
73///
74/// Nested:
75/// ```
76/// use devela::is;
77///
78/// let mut n = 2;
79/// let is_true = Some(true);
80/// is![let Some(b) = is_true, is![b, n += 3;]];
81/// assert_eq!(5, n);
82/// ```
83#[macro_export]
84#[cfg_attr(cargo_primary_package, doc(hidden))]
85macro_rules! is· {
86    /* if */
87    ($cond:expr, $then:expr) => {
88        $crate::is!(%if $cond, { $then }) };
89    ($cond:expr, $then:expr;) => {
90        $crate::is!(%if $cond,
91            { #[allow(clippy::diverging_sub_expression)] let _ = $then; }
92        )
93    };
94    ($cond:expr, $then:expr, $else:expr) => {
95        $crate::is!(%if $cond, { $then }, { $else }) };
96    ($cond:expr, $then:expr;, $else:expr) => {
97        $crate::is!(%if $cond,
98            { #[allow(clippy::diverging_sub_expression)] let _ = $then; },
99            { $else }
100        )
101    };
102    ($cond:expr, $then:expr, $else:expr;) => {
103        $crate::is!(%if $cond, { $then }, { $else; }) };
104    ($cond:expr, $then:expr;, $else:expr;) => {
105        $crate::is!(%if $cond,
106            { #[allow(clippy::diverging_sub_expression)] let _ = $then; },
107            { #[allow(clippy::diverging_sub_expression)] let _ = $else; }
108        )
109    };
110    ($cond:expr, $then:expr,) => {
111        $crate::is!(%if $cond, { $then }, {}) };
112    ($cond:expr, $then:expr; ,) => {
113        $crate::is!(%if $cond,
114            { #[allow(clippy::diverging_sub_expression)] let _ = $then; },
115            {}
116        )
117    };
118    ($cond:expr, , $else:expr) => {
119        $crate::is!(%if $cond, {}, { $else }) };
120    ($cond:expr, , $else:expr;) => {
121        $crate::is!(%if $cond,
122            {},
123            { #[allow(clippy::diverging_sub_expression)] let _ = $else; }
124        )
125    };
126
127    /* if let */
128    (let $pat:pat = $cond:expr, $then:expr) => {
129        $crate::is!(%let $pat = $cond, { $then }) };
130    (let $pat:pat = $cond:expr, $then:expr;) => {
131        $crate::is!(%let $pat = $cond,
132            { #[allow(clippy::diverging_sub_expression)] let _ = $then; }
133        )
134    };
135    (let $pat:pat = $cond:expr, $then:expr, $else:expr) => {
136        $crate::is!(%let $pat = $cond, { $then }, { $else }) };
137    (let $pat:pat = $cond:expr, $then:expr;, $else:expr) => {
138        $crate::is!(%let $pat = $cond,
139            { #[allow(clippy::diverging_sub_expression)] let _ = $then; },
140            { $else }
141        )
142    };
143    (let $pat:pat = $cond:expr, $then:expr, $else:expr;) => {
144        $crate::is!(%let $pat = $cond,
145            { $then },
146            { #[allow(clippy::diverging_sub_expression)] let _ = $else; }
147        )
148    };
149    (let $pat:pat = $cond:expr, $then:expr;, $else:expr;) => {
150        $crate::is!(%let $pat = $cond,
151            { #[allow(clippy::diverging_sub_expression)] let _ = $then; },
152            { #[allow(clippy::diverging_sub_expression)] let _ = $else; }
153        )
154    };
155    (let $pat:pat = $cond:expr, $then:expr,) => {
156        $crate::is!(%let $pat = $cond, { $then }, {}) };
157    (let $pat:pat = $cond:expr, $then:expr; ,) => {
158        $crate::is!(%let $pat = $cond,
159            { #[allow(clippy::diverging_sub_expression)] let _ = $then; },
160            {}
161        )
162    };
163    (let $pat:pat = $cond:expr, , $else:expr) => {
164        $crate::is!(%let $pat = $cond, {}, { $else }) };
165    (let $pat:pat = $cond:expr, , $else:expr;) => {
166        $crate::is!(%let $pat = $cond,
167            {},
168            { #[allow(clippy::diverging_sub_expression)] let _ = $else; }
169        )
170    };
171
172    /* internals */
173    (% if $cond:expr, $then:block) => {
174        #[allow(clippy::question_mark, reason = "to remain const-friendly")]
175        if $cond $then };
176    (% if $cond:expr, $then:block, $else:block) => {
177        if $cond $then else $else
178    };
179    (% let $pat:pat = $cond:expr, $then:block) => {
180        #[allow(clippy::question_mark, reason = "to remain const-friendly")]
181        if let $pat = $cond $then };
182    (% let $pat:pat = $cond:expr, $then:block, $else:block) => {
183        if let $pat = $cond $then else $else
184    };
185}
186#[doc(inline)]
187pub use is· as is;
188
189#[cfg(test)]
190mod _test {
191    use super::is;
192
193    #[test]
194    fn is_if_expr() {
195        assert_eq!('a', is![true, 'a', 'b']);
196        assert_eq!('b', is![false, 'a', 'b']);
197    }
198    #[test]
199    fn is_if_statement_then() {
200        let mut n = 0;
201        is![true, n += 1;];
202        is![false, n += 10;];
203        assert_eq!(1, n);
204    }
205    #[test]
206    fn is_if_statement_branches() {
207        let mut n = 0;
208        is![true, n += 1;, n += 10;];
209        assert_eq!(1, n);
210        is![false, n += 1;, n += 10;];
211        assert_eq!(11, n);
212    }
213    #[test]
214    fn is_if_empty_else() {
215        let mut n = 0;
216        is![true, n += 1;,];
217        is![false, n += 10;,];
218        assert_eq!(1, n);
219    }
220    #[test]
221    fn is_if_empty_then() {
222        let mut n = 0;
223        is![true, , n += 10;];
224        is![false, , n += 1;];
225        assert_eq!(1, n);
226    }
227    #[test]
228    fn is_if_empty_then_diverges() {
229        fn check(ok: bool) -> Result<(), &'static str> {
230            is![ok, , return Err("not ok");];
231            Ok(())
232        }
233        assert_eq!(Ok(()), check(true));
234        assert_eq!(Err("not ok"), check(false));
235    }
236    #[test]
237    fn is_let_expr() {
238        let somea = Some('a');
239        let noa: Option<char> = None;
240        assert_eq!('a', is![let Some(a) = somea, a, 'b']);
241        assert_eq!('b', is![let Some(a) = noa, a, 'b']);
242    }
243    #[test]
244    fn is_let_statement_then() {
245        let mut n = 0;
246        is![let Some(v) = Some(2), n += v;];
247        is![let Some(v) = None::<i32>, n += v;];
248        assert_eq!(2, n);
249    }
250    #[test]
251    fn is_let_statement_branches() {
252        let mut n = 0;
253        is![let Some(v) = Some(2), n += v;, n += 10;];
254        assert_eq!(2, n);
255        is![let Some(v) = None::<i32>, n += v;, n += 10;];
256        assert_eq!(12, n);
257    }
258    #[test]
259    fn is_let_empty_else() {
260        let mut n = 0;
261        is![let Some(v) = Some(3), n += v;,];
262        is![let Some(v) = None::<i32>, n += v;,];
263        assert_eq!(3, n);
264    }
265    #[test]
266    fn is_let_empty_then() {
267        let mut n = 0;
268        is![let Some(_) = Some(1), , n += 10;];
269        is![let Some(_) = None::<i32>, , n += 1;];
270        assert_eq!(1, n);
271    }
272    #[test]
273    fn is_nested_statement() {
274        let mut n = 2;
275        let is_true = Some(true);
276        is![let Some(b) = is_true, is![b, n += 3;]];
277        assert_eq!(5, n);
278    }
279}