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}