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}