futures_signals_structs_derive/
lib.rs1extern 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
10enum 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 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 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 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 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 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 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#[proc_macro_derive(AsMutableStruct, attributes(MutableStructName))]
162pub fn as_mutable_struct(input: TokenStream) -> TokenStream {
163 let ast: ItemStruct = syn::parse_macro_input!(input);
165
166 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 let fields = ast.fields.iter().map(MutableStructField::from).collect();
176
177 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 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}