Skip to main content

futures_signals_structs_derive/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3extern crate syn;
4#[macro_use]
5extern crate quote;
6
7use proc_macro::TokenStream;
8use syn::{Field, Ident, ItemStruct, Type, Visibility};
9
10/// Represents a field that needs to get converted to a Mutable and back.
11enum MutableStructField {
12    Basic {
13        name: Ident,
14        vis: Visibility,
15        ty: Type,
16    },
17    MutableStruct {
18        name: Ident,
19        vis: Visibility,
20        ty: Type,
21    },
22}
23
24impl From<&Field> for MutableStructField {
25    fn from(field: &Field) -> MutableStructField {
26        if MutableStructField::field_is_primitive(field) {
27            MutableStructField::Basic {
28                name: field.ident.clone().unwrap(),
29                vis: field.vis.clone(),
30                ty: field.ty.clone(),
31            }
32        } else {
33            MutableStructField::MutableStruct {
34                name: field.ident.clone().unwrap(),
35                vis: field.vis.clone(),
36                ty: field.ty.clone(),
37            }
38        }
39    }
40}
41
42impl MutableStructField {
43    /// Returns a struct definition of the mutable version of this field.
44    pub fn get_mutable_field_definition(&self) -> proc_macro2::TokenStream {
45        match self {
46            MutableStructField::Basic { vis, name, ty } => {
47                quote!(#vis #name: futures_signals::signal::Mutable<#ty>)
48            }
49            MutableStructField::MutableStruct {
50                vis,
51                name,
52                ty,
53            } => quote!(#vis #name: <#ty as futures_signals_structs_traits::AsMutableStruct>::MutableStructType),
54        }
55    }
56
57    /// Returns code that can generate a constructor from a non-mutable version of the struct.
58    pub fn get_constructor(&self, snapshot_name: Ident) -> proc_macro2::TokenStream {
59        match self {
60            MutableStructField::Basic { name, .. } => {
61                quote!(futures_signals::signal::Mutable::new(#snapshot_name.#name))
62            }
63            MutableStructField::MutableStruct { name, .. } => {
64                quote!(#snapshot_name.#name.as_mutable_struct())
65            }
66        }
67    }
68
69    /// Returns code that gets a static version of this field.
70    pub fn get_snapshot_generator(&self) -> proc_macro2::TokenStream {
71        match self {
72            MutableStructField::Basic { name, .. } => quote!(self.#name.get_cloned()),
73            MutableStructField::MutableStruct { name, .. } => quote!(self.#name.snapshot()),
74        }
75    }
76
77    /// Returns code that updates the mutable value from a non-mutable version of this struct.
78    pub fn get_update_setter(&self, snapshot_name: Ident) -> proc_macro2::TokenStream {
79        match self {
80            MutableStructField::Basic { name, .. } => quote!(self.#name.set(#snapshot_name.#name)),
81            MutableStructField::MutableStruct { name, .. } => {
82                quote!(self.#name.update(#snapshot_name.#name))
83            }
84        }
85    }
86
87    /// Returns the name of this field as an ident.
88    pub fn get_name(&self) -> &proc_macro2::Ident {
89        match self {
90            MutableStructField::Basic { name, .. } => name,
91            MutableStructField::MutableStruct { name, .. } => name,
92        }
93    }
94
95    /// Returns the value of the mutable_type annotation, if specified.
96    fn field_is_primitive(input: &Field) -> bool {
97        if let Type::Path(type_path) = &input.ty {
98            let last_component = type_path.path.segments.last().unwrap();
99            let name = last_component.ident.to_string();
100            name.chars().nth(0).unwrap().is_ascii_lowercase()
101        } else {
102            false
103        }
104    }
105}
106
107/// Derives a function called `as_mutable_struct()` that returns a version of the struct
108/// where all fields are Mutable objects.
109/// ```
110///     #[derive(AsMutableStruct)]
111///     struct PlayerScore {
112///         hits: u32,
113///         multiplier: f32,
114///     }
115/// 
116///     fn main() {
117///         let score = PlayerScore {
118///             hits: 4,
119///             multiplier: 0.4,
120///         };
121///         let mutable_score = score.as_mutable_struct();
122///         mutable_score.hits.set(6);
123///         mutable_score.multiplier.set(0.5);
124///     }
125/// ```
126/// By default this creates a new struct called MutablePlayerScore that can also be
127/// constructed directly as necessary.
128/// ```
129///     let mutable_score = MutablePlayerScore {
130///         hits: Mutable::new(5),
131///         multiplier: Mutable::new(1.4),
132///     };
133/// ```
134/// Either way you construct it, the mutable object can be 'snapshotted' into the
135/// original struct.
136/// ```
137///     assert_eq!(mutable_score.snapshot(), PlayerScore {
138///         hits: 5,
139///         multiplier: 1.4,
140///     });
141/// ```
142/// The mutable value can also be updated to match a new static struct.
143/// ```
144///     mutable_score.update(PlayerScore {
145///         hits: 50,
146///         multiplier: 15,
147///     });
148///     assert_eq!(mutable_score.snapshot(), PlayerScore {
149///         hits: 50,
150///         multiplier: 15,
151///     });
152/// ```
153/// Structs can depend on other structs when annotated with #[mutable_type]
154/// ```
155///     #[derive(AsMutableStruct)]
156///     struct GameScore {
157///         #[mutable_type = "MutablePlayerScore"] player_1: PlayerScore,
158///         #[mutable_type = "MutablePlayerScore"] player_2: PlayerScore,
159///     }
160/// ```
161#[proc_macro_derive(AsMutableStruct, attributes(MutableStructName))]
162pub fn as_mutable_struct(input: TokenStream) -> TokenStream {
163    // Parse the string representation
164    let ast: ItemStruct = syn::parse_macro_input!(input);
165
166    // Determine what to name the Mutable version of this struct. Tries to pull from the
167    // MutableStructName attribute, falls back to `MutableStructName` where StructName
168    // is the name of the derived struct.
169    let mutable_name = maybe_get_mutable_name(ast.clone())
170        .map(|name| format_ident!("{}", name))
171        .or(Some(format_ident!("Mutable{}", &ast.ident)))
172        .unwrap();
173
174    // Extract all fields as MutableStructField instances.
175    let fields = ast.fields.iter().map(MutableStructField::from).collect();
176
177    // Build the impl
178    let gen_mutable = make_mutable_variant(ast.clone(), &fields, &mutable_name);
179    let gen_as_signal_struct = impl_as_signal_struct(ast, &fields, &mutable_name);
180
181    // Return the generated impl
182    quote!(#gen_mutable #gen_as_signal_struct).into()
183}
184
185fn make_mutable_variant(
186    input: ItemStruct,
187    fields: &Vec<MutableStructField>,
188    mutable_name: &Ident,
189) -> proc_macro2::TokenStream {
190    let original_ident = input.ident;
191    let original_vis = input.vis;
192
193    let mutable_fields = fields
194        .iter()
195        .map(MutableStructField::get_mutable_field_definition)
196        .collect::<Vec<proc_macro2::TokenStream>>();
197
198    let snapshot_fields = fields
199        .iter()
200        .map(|field| {
201            let name = field.get_name();
202            let snapshot_generator = field.get_snapshot_generator();
203            quote!(#name: #snapshot_generator)
204        })
205        .collect::<Vec<proc_macro2::TokenStream>>();
206
207    let update_fields = fields
208        .iter()
209        .map(|field| field.get_update_setter(format_ident!("new_snapshot")))
210        .collect::<Vec<proc_macro2::TokenStream>>();
211
212    quote! {
213        #original_vis struct #mutable_name {
214            #(#mutable_fields),*
215        }
216
217        impl futures_signals_structs_traits::MutableStruct for #mutable_name {
218            type SnapshotType = #original_ident;
219
220            fn snapshot(&self) -> #original_ident {
221                #original_ident {
222                    #(#snapshot_fields),*
223                }
224            }
225
226            fn update(&self, new_snapshot: #original_ident) {
227                #(#update_fields);*;
228            }
229        }
230
231        impl Clone for #mutable_name {
232            fn clone(&self) -> #mutable_name {
233                self.snapshot().as_mutable_struct()
234            }
235        }
236    }
237}
238
239fn impl_as_signal_struct(
240    input: ItemStruct,
241    fields: &Vec<MutableStructField>,
242    mutable_name: &Ident,
243) -> proc_macro2::TokenStream {
244    let ident = input.ident;
245
246    let mutable_fields = fields
247        .iter()
248        .map(|field| {
249            let name = field.get_name();
250            let mutable_constructor = field.get_constructor(format_ident!("self"));
251            quote!(#name: #mutable_constructor)
252        })
253        .collect::<Vec<proc_macro2::TokenStream>>();
254
255    quote! {
256        impl futures_signals_structs_traits::AsMutableStruct for #ident {
257            type MutableStructType = #mutable_name;
258
259            fn as_mutable_struct(&self) -> #mutable_name {
260                #mutable_name {
261                    #(#mutable_fields),*
262                }
263            }
264        }
265    }
266}
267
268fn maybe_get_mutable_name(input: ItemStruct) -> Option<String> {
269    for attr in input.attrs {
270        if let syn::AttrStyle::Inner(_) = attr.style {
271            continue;
272        }
273        if !attr.path.is_ident("MutableStructName") {
274            continue;
275        }
276        if let Result::Ok(parsed_meta) = attr.parse_meta() {
277            if let syn::Meta::NameValue(name_value) = parsed_meta {
278                if let syn::Lit::Str(lit_str) = name_value.lit {
279                    return Some(lit_str.value());
280                } else {
281                    panic!("Found a MutableStructName that is not a string.")
282                }
283            } else {
284                panic!("Format MutableStructName as #[MutableStructName = \"MyMutableName\"]")
285            }
286        } else {
287            panic!("Found a malformed MutableStructName. Format MutableStructName as #[MutableStructName = \"Name\"]");
288        }
289    }
290    return Option::None;
291}