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}