rust_utils_macros/
lib.rs

1use proc_macro::TokenStream;
2use syn::{
3    parse_macro_input, parse_quote, Attribute, DeriveInput, GenericParam, ImplItemFn, ItemEnum, ItemStruct, PathArguments, TraitItemFn, Type, TypePath
4};
5use quote::quote;
6use if_chain::if_chain;
7
8mod chainable;
9mod encapsulated;
10mod config;
11
12use config::*;
13use chainable::*;
14use encapsulated::*;
15
16// helper macro to return a compiler error message
17// if a ParseResult is an error
18macro_rules! ok_or_compile_err {
19    ($expr:expr) => {
20        match $expr {
21            Ok(val) => val,
22            Err(why) => return why.into_compile_error().into()
23        }
24    }
25}
26
27// helper macro to return a compiler error message
28// if an Option is None
29macro_rules! some_or_compile_err {
30    ($expr:expr, $reason:literal) => {
31        if let Some(val) = $expr {
32            val
33        }
34        else {
35            return crate::quote_compile_err!($reason);
36        }
37    };
38
39    ($expr:expr, $reason:expr) => {
40        if let Some(val) = $expr {
41            val
42        }
43        else {
44            return crate::quote_compile_err!($reason);
45        }
46    }
47}
48
49// helper macro to create a TokenStream with a compile error message
50macro_rules! quote_compile_err {
51    ($msg:literal) => {
52        quote::quote! {
53            compile_error!($msg)
54        }
55    };
56
57    ($msg:expr) => {
58        {
59            let msg = $msg;
60            quote::quote! {
61                compile_error!(#msg)
62            }
63        }
64    }
65}
66
67use ok_or_compile_err;
68use some_or_compile_err;
69use quote_compile_err;
70
71/// Convenience macro that generates builder-like chainable methods from setter or adder methods in an
72/// inherent implementation, trait definition, or from fields in a `struct`
73///
74/// # Examples
75///
76/// ## On an inherent method
77///
78/// ```
79/// struct Example {
80///     field_0: bool,
81///     opt_field: Option<usize>
82/// }
83///
84/// impl Example {
85///     fn new() -> Self {
86///         Example {
87///             field_0: false
88///         }
89///     }
90///
91///     #[chainable]
92///     fn set_field_0(&mut self, val: bool) {
93///         self.field_0 = val;
94///     }
95///
96///     // this will make the generated method take usize as an argument
97///     // instead of Option<usize>
98///     #[chainable(collapse_options)]
99///     fn set_opt_field(&mut self, opt_field: Option<usize>) {
100///         self.opt_field = opt_field;
101///     }
102/// }
103///
104/// let example = Example::new().field_0(true);
105/// println!("Value of field_0: {}", example.field_0);
106/// ```
107///
108/// ## In a trait definition
109///
110/// To use the macro in a trait definition, it must be a subtrait of [`Sized`]. This macro
111/// will also make a trait object unsafe
112///
113/// ```
114/// pub trait ExampleTrait: Sized {
115///     #[chainable]
116///     fn set_something(&mut self, val: u32);
117///
118///     #[chainable]
119///     fn set_something_else(&mut self, val: u32) {
120///         self.set_something(val);
121///     }
122/// }
123/// ```
124///
125///  ## In a struct definition
126///
127/// In a struct definition, the specified fields will have chainable methods generated for them
128/// with the same visibility as that field
129///
130/// ```
131/// #[derive(Default, Debug)]
132/// #[chainable]
133/// struct Example {
134///     // generated chainable method with documentation
135///     #[chainable(doc = "Documentation for `field_0`")]
136///     field_0: bool,
137///     field_1: usize,
138///     field_2: f64,
139/// 
140///     // this will make the generated method take usize as an argument
141///     // instead of Option<usize>
142///     #[chainable(collapse_option)]
143///     opt_field: Option<usize>
144/// }
145///
146/// let example = Example::default()
147///     .field_0(false)
148///     .field_1(100)
149///     .field_2(std::f64::consts::PI)
150///     .opt_field(1);
151///
152/// println!("{example:?}");
153/// ```
154///
155/// # Method Visibility
156/// 
157/// Methods generated by this macro have the same visibility as the annotated item
158/// (if the struct field or method is `pub`, so will the generated methods)
159/// 
160/// # Options for struct fields:
161///
162/// ## `collapse_option`
163///
164/// If the field is `Option<T>`, make the generated chainable method take `T` as its argument
165///
166/// ## `use_into_impl`
167///
168/// Make the generated chainable method take `impl Into<T>` and convert it to the field's type
169///
170/// ## `doc = "Your documentation here"`
171///
172/// Creates documentation for the generated chainable method
173///
174/// # Options for setter and adder methods
175///
176/// ## `collapse_options`
177///
178/// If any methods are `Option<T>`, collapse them to their inner types
179///
180/// ## `use_into_impl`
181///
182/// Make all the methods take `Into<T>` and convert them to their input types
183#[proc_macro_attribute]
184pub fn chainable(attr_args: TokenStream, item: TokenStream) -> TokenStream {
185    if let Ok(method) = syn::parse::<ImplItemFn>(item.clone()) {
186        chainable_inh_method(method, attr_args)
187    }
188    else if let Ok(method) = syn::parse::<TraitItemFn>(item.clone()) {
189        chainable_trait_method(method, attr_args)
190    }
191    else if let Ok(struct_def) = syn::parse::<ItemStruct>(item) {
192        if !attr_args.is_empty() {
193            quote_compile_err!("This attribute doesn't take any input!")
194        }
195        else {
196            chainable_struct_fields(struct_def)
197        }
198    }
199    else {
200        quote! {
201            compile_error!("This attribute can only be used on methods and structs!")
202        }
203    }
204        .into()
205}
206
207/// Java style encapsulation for struct fields
208///
209/// # Example
210/// ```
211/// #[encapsulated]
212/// pub struct Example {
213///     // make the getter method copy the value instead
214///     // returning a reference
215///     #[getter(copy_val)]
216///     #[setter]
217///     a: usize,
218///
219///     // return a mutable reference
220///     #[getter(mutable, doc = "Your documentation here")]
221///     // create a setter method with an Into impl
222///     #[setter(use_into_impl)]
223///     b: f64,
224///     c: f32,
225///
226///     #[setter]
227///     // it also possible to create chainable methods with this macro
228///     // (including with documentation)
229///     #[chainable(doc = "Documentation for `field_0`")]
230///     chainable: u32,
231///
232///     #[setter]
233///     #[chainable(collapse_option)]
234///     thing1: Option<u32>,
235///
236///     #[setter]
237///     #[chainable(collapse_option, use_into_impl)]
238///     thing2: Option<usize>
239/// }
240/// ```
241///
242/// # Method Visibility
243/// 
244/// Methods generated by this macro have the same visibility as the annotated struct
245/// (if the struct is `pub`, so will the generated methods)
246/// 
247/// # Helper attributes:
248/// 
249/// ## `#[setter]`
250/// 
251/// Create a setter method for the annotated field
252/// 
253/// ### Options:
254/// `use_into_impl`: Make the generated method take an [`Into`] implementation
255/// of the field type and convert it
256/// 
257/// `doc = "Your documentation here"`: Creates documentation for the generated setter method
258/// if `#[chainable]` is also specified for this field without documentation, the documentation from this
259/// attribute is used for the chainable method
260///
261/// ## `#[getter]`
262/// 
263/// Create a setter method for the annotated field
264/// 
265/// ### Options:
266/// `copy_val`: copy the field value instead of returning a reference. 
267/// This only works if the field's type implements [`Copy`]!
268/// 
269/// `mutable`: Create a getter method that returns a mutable
270/// reference to the field
271///
272/// `doc = "Your documentation here"`: Creates documentation for the generated setter method
273/// 
274/// ## `#[chainable]`
275/// 
276/// Works exactly like the [`macro@chainable`] macro on struct fields
277#[proc_macro_attribute]
278pub fn encapsulated(attr_args: TokenStream, item: TokenStream) -> TokenStream {
279    if !attr_args.is_empty() {
280        quote_compile_err!("This attribute doesn't take any input!")
281    }
282    else {
283        let struct_def = ok_or_compile_err!(syn::parse::<ItemStruct>(item));
284        encapsulated_struct(struct_def)
285    }
286        .into()
287}
288
289/// A macro that implements [`Default`] for a type if its inherent implementation has a `Self::new() -> Self` method
290#[proc_macro]
291pub fn new_default(input: TokenStream) -> TokenStream {
292    let in_type = parse_macro_input!(input as Type);
293    
294    let generics = if_chain! {
295        if let Type::Path(TypePath { path, .. }) = &in_type;
296        if let Some(type_segment) = path.segments.last();
297        if let PathArguments::AngleBracketed(type_args) = &type_segment.arguments;
298
299        then {
300            Some(type_args)
301        }
302        else { None }
303    };
304
305    quote! {
306        impl #generics core::default::Default for #in_type {
307            fn default() -> Self { Self::new() }
308        }
309    }
310        .into()
311}
312
313/// A derive macro that implements an inherent `Self::new()` method if
314/// [`Default`] is already implemented (can be derived)
315#[proc_macro_derive(New)]
316pub fn new_derive(input: TokenStream) -> TokenStream {
317    let input = parse_macro_input!(input as DeriveInput);
318    let name = input.ident;
319    let mut generics = input.generics;
320
321    for param in &mut generics.params {
322        if let GenericParam::Type(ref mut type_param) = *param {
323            type_param.bounds.push(parse_quote!(core::default::Default));
324        }
325    }
326
327    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
328
329    quote! {
330        impl #impl_generics #name #ty_generics #where_clause {
331            pub fn new() -> Self { Self::default() }
332        }
333    }
334        .into()
335}
336
337/// [`Config`]: rust_utils::config::Config
338/// [`Serialize`]: serde::Serialize
339/// [`Deserialize`]: serde::Deserialize
340///
341/// Convenience macro to quickly create an implementation of the [`Config`] trait.
342/// This also automatically implements the [`Serialize`] and [`Deserialize`] traits from
343/// the [`serde`] crate (requires the feature `serde_derive`)
344///
345/// # Example
346/// ```
347/// #[config(file_name = "example.toml", save_dir = "example")]
348/// pub struct ExampleConfig {
349///     string: String,
350///     number: u32,
351///     boolean: bool
352/// }
353///
354/// impl Default for ExampleConfig {
355///     fn default() -> Self {
356///         Self {
357///             string: "string".into(),
358///             number: 100,
359///             boolean: true
360///         }
361///     }
362/// }
363/// ```
364///
365/// # Options
366///
367/// ## `file_name = "<file name here>"`
368///
369/// The file name of the config
370///
371/// ## `save_dir = "<save directory>"`
372///
373/// The path of the config file's folder relative to the config root (normally `$HOME/.config/`)
374/// 
375/// ## `cfg_type(<config type>)` (optional)
376///
377/// The type of config the file will be saved as.
378///
379/// Valid options are `toml` and `ron`
380#[proc_macro_attribute]
381pub fn config(attr_args: TokenStream, item: TokenStream) -> TokenStream {
382    if let Ok(struct_def) = syn::parse::<ItemStruct>(item.clone()) {
383        gen_config_impl(&struct_def, &struct_def.ident, &struct_def.generics, attr_args)
384    }
385    else if let Ok(enum_def) = syn::parse::<ItemEnum>(item) {
386        gen_config_impl(&enum_def, &enum_def.ident, &enum_def.generics, attr_args)
387    }
388    else {
389        quote_compile_err!("This attribute can only be used on structs and enums!")
390    }
391        .into()
392}
393
394fn gen_doc_attrs<S: AsRef<str>>(doc_string: S) -> Vec<Attribute> {
395    let mut doc_attrs = Vec::new();
396    for line in doc_string.as_ref().lines() {
397        doc_attrs.push(
398            parse_quote! { #[doc = #line] }
399        );
400    }
401
402    doc_attrs
403}