named_block/
lib.rs

1// TODO
2// - eliminate the closure hole by recognizing nested calls and shadowed labels, then maintaining a whitelist?
3// - inline @up rule to reduce recursion depth
4
5#![cfg_attr(not(test), no_std)]
6
7// the tests need more recursion to parse all the code
8#![cfg_attr(test, recursion_limit = "1000")]
9
10// on nightly, we can re-export static_cond!
11#![cfg_attr(feature = "nightly", feature(macro_reexport))]
12
13#[cfg(all(test,                       // for testing ...
14          not(feature = "nightly")))] // ... on beta/stable ...
15#[macro_use]                          // ... we use the macros ...
16#[no_link]                            // ... but no code ...
17extern crate static_cond;             // ... from static-cond
18
19#[cfg(feature = "nightly")]    // on nightly ...
20#[macro_use]                   // ... we use the macros ...
21#[no_link]                     // ... but no code ...
22#[macro_reexport(static_cond)] // ... and re-export static-cond! ...
23extern crate static_cond;      // ... from static-cond
24
25/// Provides the "early exit from any block" control-flow primitive that was mentioned in [RFC 243][link].
26///
27/// If not using the "nightly" Cargo feature, you must depend on `static-cond` and put `#[macro_use] extern crate static_cond;` at the crate root.
28///
29/// See README.md for more details.
30///
31/// [link]: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#early-exit-from-any-block
32///
33/// Examples
34/// ========
35///
36/// ```
37/// # #[macro_use] extern crate named_block;
38/// # #[macro_use] extern crate static_cond;
39/// # fn main() {
40/// let x = block!('a: {
41///     break 'a 0;
42///     1
43/// });
44/// assert_eq!(x, 0);
45/// # }
46/// ```
47///
48/// ```
49/// # #[macro_use] extern crate named_block;
50/// # #[macro_use] extern crate static_cond;
51/// # fn main() {
52/// assert_eq!(
53///     42,
54///     block!('a: {
55///         enum Foo { Bar(i32) }
56///         let closure = #[block(ignore)] {
57///             move |Foo::Bar(x): Foo| -> i32 {
58///                 x + block!('a: {
59///                     break 'a 41;
60///                 })
61///             }
62///         };
63///     
64///         closure(Foo::Bar(1))
65///     })
66/// );
67/// # }
68/// ```
69#[macro_export]
70macro_rules! block {
71    // =======================================================================================
72    // PRIVATE RULES
73    // =======================================================================================
74    
75    // ======================================================
76    // UTILITY RULES
77    // ======================================================
78
79    // utility: coerce an AST fragment with interpolated TTs into an expression
80    // (see https://danielkeep.github.io/tlborm/book/blk-ast-coercion.html)
81    (@as_expr $e:expr) => { $e };
82    
83    // utility: deliberately cause a compile error with a CamelCaseMessage
84    (@error $err:ident) => {{
85        struct $err;
86        let _: () = $err;
87    }};
88
89    // ======================================================
90    // OUTPUT STAGE
91    // ======================================================
92    // This is called from the scanner when everything has
93    // been processed and we are ready to write out the
94    // final expansion.
95    
96    // final output from the top level of the macro
97    (@wrap $life:tt () $ret:ident ($($init:tt)*) $out:expr) => {
98        block!(@as_expr
99            {
100                let $ret $($init)*;
101                $life: loop {
102                    $ret = $out;
103                    break $life;
104                }
105                $ret
106            })
107    };
108    (@wrap $life:tt (loop) $ret:ident ($($init:tt)*) $out:expr) => {
109        block!(@as_expr
110            {
111                let $ret $($init)*;
112                $life: loop {
113                    $out;
114                }
115                $ret
116            })
117    };
118
119    // ======================================================
120    // SCANNER STAGE
121    // ======================================================
122    // This is the meat of the macro. It transfers code from
123    // the input (left of ->) to the output (right of ->)
124    // while transforming break statements or triggering
125    // errors as needed.
126
127    // The next four rules handle the end of the input code -- either the macro
128    // is done, or we need to pop the stack and keep walking. We can tell which
129    // it is by checking the context stack. If it's empty, we can go to output.
130    // Otherwise, we take the current output, surround it by the brace type,
131    // and move up the context stack.
132    
133    // no context: we're done!
134    (@scan {} $life:tt $ret:ident () -> ($($out:tt)*) (() $lp:tt $init:tt)) => {
135        block!(@wrap $life $lp $ret $init { $($out)* })
136    };
137    // pop stack and surround with {}
138    (@scan {} $life:tt $ret:ident () -> ($($out:tt)*) $stack:tt) => {
139        block!(@up $life $ret { $($out)* } $stack)
140    };
141    // pop stack and surround with ()
142    (@scan () $life:tt $ret:ident () -> ($($out:tt)*) $stack:tt) => {
143        block!(@up $life $ret ( $($out)* ) $stack)
144    };
145    // pop stack and surround with []
146    (@scan [] $life:tt $ret:ident () -> ($($out:tt)*) $stack:tt) => {
147        block!(@up $life $ret [ $($out)* ] $stack)
148    };
149    
150    // The next nine rules are triggered when the tree walker encounters a
151    // break/continue statement.
152
153    // bare "break" and "continue" statements are errors (TODO allow bare break?)
154    (@scan $paren:tt $life:tt $ret:ident (break) -> ($($out:tt)*) $stack:tt) => {
155        block!(@scan $paren $life $ret () -> ($($out)* block!(@error NoBareBreakInNamedBlock);) $stack)
156    };
157    (@scan $paren:tt $life:tt $ret:ident (break; $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
158        block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* block!(@error NoBareBreakInNamedBlock);) $stack)
159    };
160    (@scan $paren:tt $life:tt $ret:ident (continue) -> ($($out:tt)*) $stack:tt) => {
161        block!(@scan $paren $life $ret () -> ($($out)* block!(@error NoBareContinueInNamedBlock);) $stack)
162    };
163    (@scan $paren:tt $life:tt $ret:ident (continue; $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
164        block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* block!(@error NoBareContinueInNamedBlock);) $stack)
165    };
166    // "break LIFETIME;" (no EXPR)
167    (@scan $paren:tt $life1:tt $ret:ident (break $life2:tt; $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
168        block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* break $life2;) $stack)
169    };
170    // "break LIFETIME EXPR": compare the lifetimes, if they match then transform the statement, otherwise leave it alone
171    (@scan $paren:tt $life1:tt $ret:ident (break $life2:tt $e:expr; $($tail:tt)*) -> ($($out:tt)*) ($stack:tt $lp:tt $init:tt)) => {
172        static_cond! {
173            if $life1 == $life2 {
174                block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* { $ret = $e; break $life2; }) ($stack $lp ()))
175            } else {
176                block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* break $life2 $e;) ($stack $lp ()))
177            }
178        }
179    };
180    (@scan $paren:tt $life1:tt $ret:ident (break $life2:tt $e:expr) -> ($($out:tt)*) ($stack:tt $lp:tt $init:tt)) => {
181        static_cond! {
182            if $life1 == $life2 {
183                block!(@scan $paren $life1 $ret () -> ($($out)* { $ret = $e; break $life2 }) ($stack $lp ()))
184                    // TODO make sure this isn't adding too many semicolons
185            } else {
186                block!(@scan $paren $life1 $ret () -> ($($out)* break $life2 $e;) ($stack $lp ()))
187            }
188        }
189    };
190    // "continue LIFETIME": compare the lifetimes, if they match then error, otherwise leave it alone
191    // (this only applies to bare blocks)
192    (@scan $paren:tt $life1:tt $ret:ident (continue $life2:tt; $($tail:tt)*) -> ($($out:tt)*) ($stack:tt () $init:tt)) => {
193        static_cond! {
194            if $life1 == $life2 {
195                block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* block!(@error NoMatchedContinueInNamedBlock);) ($stack () $init))
196            } else {
197                block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* continue $life2;) ($stack () $init))
198            }
199        }
200    };
201    (@scan $paren:tt $life1:tt $ret:ident (continue $life2:tt) -> ($($out:tt)*) $stack:tt) => {
202        static_cond! {
203            if $life1 == $life2 {
204                block!(@scan $paren $life1 $ret () -> ($($out)* block!(@error NoMatchedContinueInNamedBlock);) $stack)
205            } else {
206                block!(@scan $paren $life1 $ret () -> ($($out)* continue $life2;) $stack)
207            }
208        }
209    };
210
211    // tree walker ignores #[block(ignore)] tts, closures, and items
212    
213    (@scan_item $paren:tt $life:tt $ret:ident ($ignore:item $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
214        block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* $ignore) $stack)
215    };
216    
217    // #[block(ignore)] attribute is ignored
218    (@scan $paren:tt $life:tt $ret:ident (#[block(ignore)] $ignore:tt $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
219        block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* $ignore) $stack)
220    };
221    // other attributes pass through
222    (@scan $paren:tt $life:tt $ret:ident (#[$attr:meta] $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
223        block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* #[$attr]) $stack)
224    };
225    // ignore items: use, extern, static, const, unsafe trait/impl/fn, fn, mod, type, enum, trait, impl, struct
226    (@scan $paren:tt $life:tt $ret:ident (pub $($tail:tt)*) -> $out:tt $stack:tt) => {
227        block!(@scan_item $paren $life $ret (pub $($tail)*) -> $out $stack)
228    };
229    (@scan $paren:tt $life:tt $ret:ident (use $($tail:tt)*) -> $out:tt $stack:tt) => {
230        block!(@scan_item $paren $life $ret (use $($tail)*) -> $out $stack)
231    };
232    (@scan $paren:tt $life:tt $ret:ident (extern $($tail:tt)*) -> $out:tt $stack:tt) => {
233        block!(@scan_item $paren $life $ret (extern $($tail)*) -> $out $stack)
234    };
235    (@scan $paren:tt $life:tt $ret:ident (mod $($tail:tt)*) -> $out:tt $stack:tt) => {
236        block!(@scan_item $paren $life $ret (mod $($tail)*) -> $out $stack)
237    };
238    (@scan $paren:tt $life:tt $ret:ident (static $($tail:tt)*) -> $out:tt $stack:tt) => {
239        block!(@scan_item $paren $life $ret (static $($tail)*) -> $out $stack)
240    };
241    (@scan $paren:tt $life:tt $ret:ident (const $($tail:tt)*) -> $out:tt $stack:tt) => {
242        block!(@scan_item $paren $life $ret (const $($tail)*) -> $out $stack)
243    };
244    (@scan $paren:tt $life:tt $ret:ident (trait $($tail:tt)*) -> $out:tt $stack:tt) => {
245        block!(@scan_item $paren $life $ret (trait $($tail)*) -> $out $stack)
246    };
247    (@scan $paren:tt $life:tt $ret:ident (unsafe trait $($tail:tt)*) -> $out:tt $stack:tt) => {
248        block!(@scan_item $paren $life $ret (unsafe trait $($tail)*) -> $out $stack)
249    };
250    (@scan $paren:tt $life:tt $ret:ident (impl $($tail:tt)*) -> $out:tt $stack:tt) => {
251        block!(@scan_item $paren $life $ret (impl $($tail)*) -> $out $stack)
252    };
253    (@scan $paren:tt $life:tt $ret:ident (unsafe impl $($tail:tt)*) -> $out:tt $stack:tt) => {
254        block!(@scan_item $paren $life $ret (unsafe impl $($tail)*) -> $out $stack)
255    };
256    (@scan $paren:tt $life:tt $ret:ident (fn $($tail:tt)*) -> $out:tt $stack:tt) => {
257        block!(@scan_item $paren $life $ret (fn $($tail)*) -> $out $stack)
258    };
259    (@scan $paren:tt $life:tt $ret:ident (unsafe fn $($tail:tt)*) -> $out:tt $stack:tt) => {
260        block!(@scan_item $paren $life $ret (unsafe fn $($tail)*) -> $out $stack)
261    };
262    (@scan $paren:tt $life:tt $ret:ident (type $($tail:tt)*) -> $out:tt $stack:tt) => {
263        block!(@scan_item $paren $life $ret (type $($tail)*) -> $out $stack)
264    };
265    (@scan $paren:tt $life:tt $ret:ident (enum $($tail:tt)*) -> $out:tt $stack:tt) => {
266        block!(@scan_item $paren $life $ret (enum $($tail)*) -> $out $stack)
267    };
268    (@scan $paren:tt $life:tt $ret:ident (struct $($tail:tt)*) -> $out:tt $stack:tt) => {
269        block!(@scan_item $paren $life $ret (struct $($tail)*) -> $out $stack)
270    };
271    
272    // tree walker descends into token trees
273    (@scan $paren:tt $life:tt $ret:ident ({ $($inner:tt)* } $($tail:tt)*) -> $out:tt ($stack:tt $lp:tt $init:tt)) => {
274        block!(@scan {} $life $ret ($($inner)*) -> ()
275               (($paren ($($tail)*) -> $out $stack) $lp $init))
276    };
277    (@scan $paren:tt $life:tt $ret:ident (( $($inner:tt)* ) $($tail:tt)*) -> $out:tt ($stack:tt $lp:tt $init:tt)) => {
278        block!(@scan () $life $ret ($($inner)*) -> ()
279               (($paren ($($tail)*) -> $out $stack) $lp $init))
280    };
281    (@scan $paren:tt $life:tt $ret:ident ([ $($inner:tt)* ] $($tail:tt)*) -> $out:tt ($stack:tt $lp:tt $init:tt)) => {
282        block!(@scan [] $life $ret ($($inner)*) -> ()
283               (($paren ($($tail)*) -> $out $stack) $lp $init))
284    };
285
286    // fall-through case for tree walker: transfer over a token
287    (@scan $paren:tt $life:tt $ret:ident ($head:tt $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
288        block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* $head) $stack)
289    };
290
291    // reformats arguments when popping a context off the tree walker stack
292    // TODO this could be folded into the @scan rules that call it, to reduce recursion depth
293    (@up $life:tt $ret:ident $thing:tt (($paren:tt $tail:tt -> ($($out:tt)*) $stack:tt) $lp:tt $init:tt)) => {
294        block!(@scan $paren $life $ret $tail -> ($($out)* $thing) ($stack $lp $init))
295    };
296
297    // entry point for bare block
298    ($life:tt: { $($body:tt)* }) => {
299        block!(@scan {} $life _ret ($($body)*) -> () (() () ()))
300        //      |    |  |     |    |              |  ||  |  |
301        //      |    |  |     |    |              |  ||  |  ^ initialization
302        //      |    |  |     |    |              |  ||  ^ loop type
303        //      |    |  |     |    |              |  |^ tree walker stack
304        //      |    |  |     |    |              |  ^ passed-through context
305        //      |    |  |     |    |              ^ transformed code
306        //      |    |  |     |    ^ code to be transformed
307        //      |    |  |     ^ block exit variable name (gensym)
308        //      |    |  ^ block label
309        //      |    ^ surrounding bracket type
310        //      ^ start the tree walker!
311    };
312
313    // entry point for loop
314    ($life:tt: loop { $($body:tt)* }) => {
315        block!(@scan {} $life _ret ($($body)*) -> () (() (loop) (= ())))
316    };
317}
318
319#[cfg(test)]
320mod tests {
321    #[test]
322    fn it_works() {
323        let flag = true;
324        let x = block!('a: {
325            if flag { break 'a "early exit"; }
326            "normal exit"
327        });
328        assert_eq!(x, "early exit");
329    }
330
331    #[test]
332    fn shadowing() {
333        let flag = false;
334        let x = block!('b: {
335            if flag { break 'b "early exit"; }
336            let _y = block!('c: {
337                if flag { break 'b "inner early exit"; };
338                String::from("inner normal exit")
339            });
340
341            #[block(ignore)]
342            {
343                #[allow(dead_code)]
344                fn f() -> i32 {
345                    block!('b: {
346                        break 'b 42;
347                    })
348                }
349            }
350            #[allow(dead_code)]
351            fn g() {
352                block!('b: {
353                    break 'b 42;
354                });
355
356                while false {
357                    break;
358                }
359                while false {
360                    continue;
361                }
362                'b: while false {
363                    continue 'b;
364                }
365            }
366
367            enum Foo { Bar(i32) }
368            let closure = move |Foo::Bar(x): Foo| -> i32 {
369                x + block!('d: {
370                    break 'd 42;
371                })
372            };
373            assert_eq!(closure(Foo::Bar(0)), 42);
374
375            "normal exit"
376        });
377        assert_eq!(x, "normal exit");
378
379        'e: for i in 1..5 {
380            assert!(i >= 1 && i < 5);
381            block!('d: {
382                //continue; //~ERROR NoBareContinueInNamedBlock
383                //continue 'd; //~ERROR NoMatchedContinueInNamedBlock
384                continue 'e;
385            });
386        }
387    }
388
389    #[test]
390    fn loops() {
391        assert_eq!(42, block!('a: loop { break 'a 42 }));
392        assert_eq!((), block!('a: {})); // make sure it works with no breaks
393
394        let mut v = vec![];
395        let mut i = 0;
396        block!('a: loop {
397            i += 1;
398            if i == 5 {
399                continue 'a;
400            } else if i == 10 {
401                break 'a;
402            }
403            v.push(i);
404        });
405        assert_eq!(&*v, &[1, 2, 3, 4, 6, 7, 8, 9]);
406    }
407}
408