generic_new/
lib.rs

1//! <div align="center">
2//!
3//! [![crates-io](https://img.shields.io/crates/v/generic-new.svg)](https://crates.io/crates/generic-new)
4//! [![docs-rs](https://docs.rs/generic-new/badge.svg)](https://docs.rs/generic-new)
5//! [![github](https://img.shields.io/static/v1?label=&message=github&color=grey&logo=github)](https://github.com/aatifsyed/generic-new)
6//!
7//! </div>
8//!
9//! A derive macro which generates an ergonomic constructor with shortcuts for certain types.
10//!
11//! ```rust
12//! # use std::path::PathBuf;
13//! use generic_new::GenericNew;
14//!
15//! #[derive(GenericNew)]
16//! struct Foo {
17//!     s: String,      // -> impl AsRef<str>
18//!     v: Vec<usize>,  // -> impl IntoIterator<Item = usize>
19//!     i: Vec<String>, // -> impl IntoIterator<Item = impl AsRef<str>>
20//!     p: PathBuf,     // -> impl AsRef<Path>
21//!     #[generic_new(ignore)]
22//!     o: String,      // Turn off magic conversion for some fields
23//!     #[generic_new(ty = impl Into<usize>, converter = |u|Into::into(u))]
24//!     u: usize,       // Custom converters are supported
25//! }
26//!
27//! # fn _make_foo() {
28//! Foo::new(
29//!     "hello",
30//!     [1, 2, 3],
31//!     ["a", "b", "c"],
32//!     "path/to/foo",
33//!     String::from("world"),
34//!     1u16,
35//! );
36//!
37//! # }
38//! ```
39
40use field::{make_field_configs, FieldConfig};
41use proc_macro::TokenStream;
42use proc_macro_error::{abort, proc_macro_error};
43use quote::quote;
44use syn::{parse_macro_input, DeriveInput};
45mod attributes;
46mod config;
47mod field;
48
49#[proc_macro_error]
50#[proc_macro_derive(GenericNew, attributes(generic_new))]
51pub fn derive_generic_new(input: TokenStream) -> TokenStream {
52    pretty_env_logger::try_init().ok();
53    let derive_input = parse_macro_input!(input as DeriveInput);
54    let user_ident = derive_input.ident.clone();
55
56    match derive_input.data {
57        syn::Data::Struct(ref user_struct) => {
58            let field_infos = make_field_configs(user_struct);
59            let inputs = field_infos.iter().map(FieldConfig::input);
60            let transforms = field_infos.iter().map(FieldConfig::transform);
61            let outputs = field_infos.iter().map(FieldConfig::output);
62
63            let constructor = match user_struct.fields {
64                syn::Fields::Named(_) => quote!(Self {#(#outputs,)*}),
65                syn::Fields::Unnamed(_) => quote!(Self(#(#outputs,)*)),
66                syn::Fields::Unit => abort!(derive_input, "Unit fields are not supported"),
67            };
68
69            let appended = quote! {
70                impl #user_ident {
71                    pub fn new(
72                        #(#inputs,)*
73                    ) -> Self {
74                        #(#transforms;)*
75                        #constructor
76                    }
77                }
78            };
79            appended.into()
80        }
81        syn::Data::Enum(_) => abort!(derive_input, "Enums are not yet supported"),
82        syn::Data::Union(_) => abort!(derive_input, "Unions are not supported"),
83    }
84}
85#[cfg(test)]
86mod tests {
87    #[test]
88    fn ui() {
89        let t = trybuild::TestCases::new();
90        t.pass("trybuild/pass/*.rs");
91        t.compile_fail("trybuild/fail/*.rs");
92    }
93}