Skip to main content

rewrite_macros/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8extern crate proc_macro;
9
10mod cached;
11mod demomo;
12#[allow(unused)]
13mod prelude;
14mod syncify;
15mod token;
16mod token_stream_ext;
17
18/// Try to make code non-async by removing `async` and `.await` keywords.
19///
20/// You can add customized replace logic if the default is not sufficient.
21/// For example, use `#[syncify([<B: Future<Output=K>>] => [], [B] => [K])]`
22/// to remove `<B: Future<Output=K>>` and replace `B` with `K`. You can also
23/// use pattern matching, like`[BoxStream<__1>] => [Iterator<__1>]`.
24/// The placeholder names affect what they match:
25/// - `__1`: double underscore, without `g`: match a single token that is
26///   not a group (not `{ ... }`, `( ... )`).
27/// - `__1g`: double underscore, with `g`: match a single token that can
28///   also be a group.
29/// - `___1`: triple underscore, without `g`: match zero or more tokens,
30///   do not match groups.
31/// - `___1g`: triple underscore, with `g`: match zero or more tokens,
32///   including groups.
33///
34/// Use `debug` in proc macro attribute to turn on extra output about expanded
35/// code. You can also use `cargo expand`.
36#[proc_macro_attribute]
37pub fn syncify(
38    attr: proc_macro::TokenStream,
39    tokens: proc_macro::TokenStream,
40) -> proc_macro::TokenStream {
41    syncify::syncify(attr.into(), tokens.into()).into()
42}
43
44/// De-monomorphization. Rewrite functions using `impl` parameters like:
45///
46/// ```
47/// #[rewrite_macros::demomo]
48/// fn foo(x: impl AsRef<str>) -> String {
49///     let x = x.as_ref();
50///     x.replace("1", "2").replace("3", "4").replace("5", "6") // complex logic
51/// }
52/// ```
53///
54/// to:
55///
56/// ```ignore
57/// fn foo(x: impl AsRef<str>) -> String {
58///     fn inner(x: &str) -> String {
59///         x.replace("1", "2").replace("3", "4").replace("5", "6") // complex logic
60///     }
61///     inner(x.as_ref())
62/// }
63/// ```
64///
65/// so the complex logic (`inner`) is only compiled once and occurs once in the
66/// final binary.
67///
68/// Supports the following parameters:
69/// - `impl AsRef<T>`
70/// - `impl Into<T>`
71/// - `impl ToString<T>`.
72///
73/// For functions that take `self`, put `#[demomo]` on the `impl` block
74/// so `demomo` can figure out the type of `Self`:
75///
76/// ```
77/// use std::fs;
78/// use std::path::Path;
79///
80/// struct S(String);
81/// #[rewrite_macros::demomo]
82/// impl S {
83///     fn open(path: impl AsRef<Path>) -> Self {
84///         Self(fs::read_to_string(path.as_ref()).unwrap())
85///     }
86///     fn save_as(&self, path: impl AsRef<Path>) {
87///         let _ = fs::write(path.as_ref(), self.0.as_bytes());
88///     }
89///     fn edit(&mut self, content: impl ToString) {
90///         self.0 = content.to_string();
91///     }
92/// }
93/// ```
94///
95/// Use `#[demomo(debug)]` to enable debug output at compile time.
96///
97/// See also https://matklad.github.io/2021/09/04/fast-rust-builds.html#Compilation-Model-Monomorphization
98#[proc_macro_attribute]
99pub fn demomo(
100    attr: proc_macro::TokenStream,
101    tokens: proc_macro::TokenStream,
102) -> proc_macro::TokenStream {
103    demomo::demomo(attr.into(), tokens.into()).into()
104}
105
106/// Fill boilerplate of a cached field.
107/// The callsite needs to define `OnceCell<Arc<_>>` field. For example:
108///
109/// ```
110/// use std::io::Result;
111/// use std::path::PathBuf;
112/// use std::sync::Arc;
113///
114/// use once_cell::sync::OnceCell;
115///
116/// struct FileReader {
117///     path: PathBuf,
118///     // Define this field before using `#[cached_field]`!
119///     content: OnceCell<Arc<String>>,
120/// }
121///
122/// impl FileReader {
123///     pub fn new(path: PathBuf) -> Self {
124///         Self {
125///             path,
126///             content: Default::default(),
127///         }
128///     }
129///
130///     #[rewrite_macros::cached_field]
131///     pub fn content(&self) -> Result<Arc<String>> {
132///         let data = std::fs::read_to_string(&self.path)?;
133///         Ok(Arc::new(data))
134///     }
135/// }
136///
137/// let dir = tempfile::tempdir().unwrap();
138/// let path = dir.path().join("a.txt");
139/// let reader = FileReader::new(path.clone());
140///
141/// std::fs::write(&path, "abc").unwrap();
142/// assert_eq!(reader.content().unwrap().as_ref(), "abc");
143///
144/// // Calling `content()` will use the cache, not read from filesystem again.
145/// std::fs::write(&path, "def").unwrap();
146/// assert_eq!(reader.content().unwrap().as_ref(), "abc");
147/// ```
148///
149/// If the type is `Arc<RwLock<_>>`, then the cache can be invalidated:
150///
151/// ```
152/// # use std::io::Result;
153/// # use std::path::PathBuf;
154/// # use std::sync::Arc;
155///
156/// # use once_cell::sync::OnceCell;
157///
158/// # struct FileReader {
159/// #     path: PathBuf,
160/// #     content: OnceCell<Arc<RwLock<String>>>,
161/// # }
162///
163/// # impl FileReader {
164/// #     pub fn new(path: PathBuf) -> Self {
165/// #         Self {
166/// #             path,
167/// #             content: Default::default(),
168/// #         }
169/// #     }
170/// # }
171///
172/// use parking_lot::RwLock;
173///
174/// impl FileReader {
175///     #[rewrite_macros::cached_field]
176///     pub fn content(&self) -> Result<Arc<RwLock<String>>> {
177///         let data = std::fs::read_to_string(&self.path)?;
178///         Ok(Arc::new(RwLock::new(data)))
179///     }
180/// }
181///
182/// let dir = tempfile::tempdir().unwrap();
183/// let path = dir.path().join("a.txt");
184/// let reader = FileReader::new(path.clone());
185///
186/// std::fs::write(&path, "abc").unwrap();
187/// assert_eq!(reader.content().unwrap().read().as_str(), "abc");
188///
189/// // Cached, stale content.
190/// std::fs::write(&path, "def").unwrap();
191/// let content = reader.content().unwrap();
192/// assert_eq!(content.read().as_str(), "abc");
193///
194/// // Cache can be invalidated to get the new content.
195/// reader.invalidate_content().unwrap();
196/// assert_eq!(content.read().as_str(), "def");
197/// ```
198///
199/// To add post processing on the `Arc<RwLock>`, use `post_load`
200/// attribute:
201///
202/// ```
203/// # use std::io::Result;
204/// # use std::path::PathBuf;
205/// # use std::sync::Arc;
206///
207/// # use once_cell::sync::OnceCell;
208///
209/// # impl FileReader {
210/// #     pub fn new(path: PathBuf) -> Self {
211/// #         Self {
212/// #             path,
213/// #             content: Default::default(),
214/// #             backup: Default::default(),
215/// #         }
216/// #     }
217/// # }
218///
219/// struct FileReader {
220///     path: PathBuf,
221///     content: OnceCell<Arc<RwLock<String>>>,
222///     backup: RwLock<Option<Arc<RwLock<String>>>>,
223/// }
224///
225/// use parking_lot::RwLock;
226///
227/// impl FileReader {
228///     #[rewrite_macros::cached_field(post_load(self.backup_content))]
229///     pub fn content(&self) -> Result<Arc<RwLock<String>>> {
230///         let data = std::fs::read_to_string(&self.path)?;
231///         Ok(Arc::new(RwLock::new(data)))
232///     }
233///
234///     fn backup_content(&self, arc: &Arc<RwLock<String>>) -> Result<()> {
235///         *self.backup.write() = Some(Arc::clone(arc));
236///         Ok(())
237///     }
238/// }
239/// ```
240///
241/// Use `#[cached(debug)]` to enable debug output at compile time.
242#[proc_macro_attribute]
243pub fn cached_field(
244    attr: proc_macro::TokenStream,
245    tokens: proc_macro::TokenStream,
246) -> proc_macro::TokenStream {
247    cached::cached_field(attr.into(), tokens.into()).into()
248}