retained_macro/lib.rs
1mod retained_let;
2mod state;
3
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::quote_spanned;
7use retained_let::RetainedLetExpander;
8use state::{State, StateArg, StateDecl};
9use syn::{parse_macro_input, parse_quote, Ident, ItemFn};
10
11/// Create external storage for tagged local variables and bind to original let statement.
12///
13/// This macro cannot be used inside impl block.
14/// ```compile_fail
15/// impl Struct {
16/// #[retained(State)]
17/// pub fn method(&self) {
18/// ..
19/// }
20/// }
21/// ```
22///
23/// ## Usage
24/// The `retained` macro can only be used on top of bare function.
25/// It takes identifier and optionally generics paremeters to build state struct declaration.
26/// The macro will make a storage for local variables tagged with `#[retained]`.
27///
28/// Tagged let statement requires type and initializer like it is `static` or `const`.
29/// Corresponding fields in state struct are initialized on first access and bound to original let statment.
30///
31/// The following does not compile as it will move state's field to local variable.
32/// ```compile_fail
33/// #[retained(State)]
34/// fn my_fn() {
35/// #[retained]
36/// let retained_string: String = String::new("");
37/// }
38/// ```
39///
40/// To make this work, use ref pattern instead.
41/// ```no_run
42/// #[retained(State)]
43/// fn my_fn() {
44/// #[retained]
45/// let ref retained_string: String = String::new("");
46/// // Mutable access
47/// // let ref mut retained_string: String = String::new("");
48/// }
49/// ```
50#[proc_macro_attribute]
51pub fn retained(attr: TokenStream, item: TokenStream) -> TokenStream {
52 let mut f = parse_macro_input!(item as ItemFn);
53
54 let mut state = State {
55 vis: f.vis.clone(),
56 decl: parse_macro_input!(attr as StateDecl),
57 fields: Vec::new(),
58 };
59
60 let name = Ident::new("__inner", Span::mixed_site());
61 RetainedLetExpander::expand(name.clone(), 0, &mut state, &mut f.block);
62
63 let state_arg = StateArg {
64 name,
65 decl: &state.decl,
66 };
67 f.sig.inputs.push(parse_quote!(#state_arg));
68
69 TokenStream::from(quote_spanned! { Span::mixed_site() =>
70 #state
71 #f
72 })
73}