prefix_stmts/lib.rs
1//! A utility macro to prefix each of several statements with the given tokens.
2//!
3//! Doing this with items is straightforward: the `item`
4//! [fragment specifier](https://doc.rust-lang.org/reference/macros-by-example.html#r-macro.decl.meta.specifier)
5//! in [`macro_rules!`](https://doc.rust-lang.org/reference/macros-by-example.html) works as expected.
6//! With statements however, things get tricky. Since the `stmt`
7//! [fragment specifier](https://doc.rust-lang.org/reference/macros-by-example.html#r-macro.decl.meta.specifier)
8//! also matches lone semicolons (which are no-op statements), and doesn't match trailing
9//! semicolons, the solution isn't trivial.
10//!
11//! After getting around the limitation, you'll be surprised to find the Rust compiler gets really
12//! confused with cryptic errors despite everything seeming right. After much trial and error, I
13//! found a procedural macro with special logic for
14//! [`Delimiter::None`](proc_macro::Delimiter::None) seems to work right.
15//!
16//! This macro is implemented as [`prefix_stmts!`]. It's a wrapper around the actual implementation,
17//! which first separates input statements through the `stmt`
18//! [fragment specifier](https://doc.rust-lang.org/reference/macros-by-example.html#r-macro.decl.meta.specifier),
19//! and then forwards them to a procedural macro
20//! ([`emit_prefixed_tokens_except_starts_with_semicolon`](prefix_stmts_proc_macro::emit_prefixed_tokens_except_starts_with_semicolon)),
21//! which does exactly what the name says (with the [`Delimiter::None`](proc_macro::Delimiter::None)
22//! trick).
23//!
24//! If you find a way to do this without a procedural macro, please do let me know!
25//!
26//! ```rust
27//! use prefix_stmts::prefix_stmts;
28//!
29//! fn void() {}
30//!
31//! let mut count: usize = 0;
32//!
33//! prefix_stmts! {
34//! [ count += 1; ]
35//!
36//! ;;;;;;;;;;;;;;;;; // Lone semicolons (noop, don't prefix)
37//!
38//! pub(crate) fn foo() {}
39//!
40//! let _: usize = 42;
41//!
42//! void();
43//!
44//! { foo();; void() };;;
45//!
46//! { ( { void() } );; foo() }
47//! }
48//!
49//! assert_eq!(count, 5);
50//! ```
51//!
52//! My whole motivation for making this was allowing several statements to be prefixed with
53//! [`cfg`](https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index)
54//! attributes without repeating the attribute for each statement.
55//!
56//! ```rust
57//! use prefix_stmts::prefix_stmts;
58//!
59//! prefix_stmts! {
60//! [ #[cfg( all() )] ]
61//!
62//! const FOO: bool = true;
63//! }
64//!
65//! prefix_stmts! {
66//! [ #[cfg( not( all() ) )] ]
67//!
68//! const FOO: bool = false;
69//! }
70//!
71//! assert_eq!(FOO, true);
72//! ```
73//!
74//! The Minimum Supported Rust Version (MSRV) for this crate is
75//! [Rust 1.45](https://blog.rust-lang.org/2020/07/16/Rust-1.45.0/), which added support for
76//! [statements emitted by procedural macros](https://blog.rust-lang.org/2020/07/16/Rust-1.45.0/#stabilizing-function-like-procedural-macros-in-expressions-patterns-and-statements).
77//! Do mind that Rust <= [1.47](https://blog.rust-lang.org/2020/10/08/Rust-1.47/) has issues with
78//! hygiene from the procedural macro, so using at least
79//! [Rust 1.48](https://blog.rust-lang.org/2020/11/19/Rust-1.48/) is recommended.
80
81#![no_std]
82
83#[cfg(doc)]
84extern crate proc_macro;
85
86#[doc(hidden)]
87pub mod __ {
88 pub use ::prefix_stmts_proc_macro::emit_prefixed_tokens_except_starts_with_semicolon;
89
90 pub use super::prefix_stmts;
91}
92
93/// Prefixes each statement with the given prefix tokens. First provide the tokens to prefix wrapped
94/// in `[ brackets ]`, then follow with the statements.
95///
96/// ```rust
97/// use prefix_stmts::prefix_stmts;
98///
99/// prefix_stmts! {
100/// [ #[cfg( all() )] ]
101///
102/// const FOO: bool = true;
103/// }
104///
105/// prefix_stmts! {
106/// [ #[cfg( not( all() ) )] ]
107///
108/// const FOO: bool = false;
109/// }
110///
111/// assert_eq!(FOO, true);
112/// ```
113///
114/// See the [crate documentation](crate) for more details.
115#[macro_export]
116macro_rules! prefix_stmts {
117 (
118 [ $($prefix:tt)* ]
119 $($statements:stmt)*
120 ) => {
121 $crate::__::emit_prefixed_tokens_except_starts_with_semicolon! (
122 [ $($prefix)* ]
123 $( ( $statements ) )*
124 );
125 };
126}
127
128#[cfg(test)]
129mod tests {
130 #[test]
131 fn prefixes_for_each_stmt() -> Result<(), ()> {
132 let mut count: usize = 0;
133
134 let mut raise_count = || {
135 count += 1;
136 };
137
138 macro_rules! dummy_macro {
139 () => {};
140 }
141
142 // https://doc.rust-lang.org/reference/statements.html
143 #[allow(unused)]
144 loop {
145 crate::prefix_stmts! (
146 [ raise_count(); ]
147
148 // Lone semicolons (noop, don't prefix)
149 ;;;;;;;;;;;;;;;;;
150
151 // Items (20)
152 #[cfg(all())]
153 #[cfg(all())]
154 pub(crate) mod foo {}
155
156 pub(crate) mod bar {};
157
158 extern crate core;
159
160 use core::option;;;
161
162 #[inline(always)]
163 fn qux<T: ?Sized>(v: &T) -> &T { { (v) } };
164
165 pub(crate) fn void() {}
166
167 type Alias = usize;;
168
169 struct Quz;
170
171 #[repr(C)]
172 struct Quux<T>(T);
173
174 enum Corge {
175 A = 60,
176 B,
177 }
178
179 union Grault {
180 a: u32,
181 b: f32,
182 };
183
184 const _: Alias = 2 + 2;;;
185
186 static DAKOTA: Alias = (3 + 3);
187
188 trait Garply: Sized {
189 fn foo(&self);
190 }
191
192 impl Garply for Quz {
193 fn foo(&self) {
194 void();
195 }
196 }
197
198 extern "C" {}
199
200 dummy_macro!();
201
202 dummy_macro![];
203
204 dummy_macro! {}
205
206 dummy_macro! {};
207
208 // Some more unnecessary semicolons for good measure
209 ;;;;;;;;;;
210
211 // Let statement (3)
212 let hi_dakota: Alias = DAKOTA;
213
214 let (a, b, c) = (1, 2, 3);
215
216 let _ = ((()));
217
218 // Expression statement (17)
219
220 hi_dakota + 1;
221
222 (qux(&hi_dakota));
223
224 { qux(&hi_dakota); qux(&hi_dakota) };
225
226 { qux(&hi_dakota); qux(&hi_dakota); }
227
228 { void() }
229
230 [void(),];
231
232 (void());
233
234 (void(),);
235
236 "The real victor of WW2 was the Soviet Union.";
237
238 Some::<()>;
239
240 'thingmabob: loop {
241 break;
242 }
243
244 loop {
245 break 42
246 };
247
248 unsafe {
249 void()
250 }
251
252 unsafe {
253 qux(&hi_dakota)
254 };
255
256 unsafe {
257 void();
258 }
259
260 23..0;
261
262 break
263 );
264 }
265
266 assert_eq!(count, 40);
267
268 Ok(())
269 }
270}