constructor_lite/
lib.rs

1// constructor-lite - Generate minimal constructors for structs
2// Copyright (C) 2023 d-k-bo
3// SPDX-License-Identifier: MIT
4
5//! This crate provides the `ConstructorLite` derive macro for generating
6//! minimal constructors for a struct from its fields.
7//!
8//! It is primarily designed for structs where deriving [`Default`] is not
9//! possible because some fields don't implement it.
10//!
11//! By default, an associated function `new()` is generated, which expects every
12//! field that is not `Option<T>` as an argument.
13//!
14//! - To add an optional field to expected arguments of the constructor function,
15//!   it can be marked with `#[constructor(required)]`.
16//! - To remove a non-optional field that implements `Default` from the constructor
17//!   function, it can be marked with `#[constructor(default)]`.
18//! - To change the name of the generated function, the struct can be marked with e. g.
19//!   `#[constructor(name = "function_name")]`.
20//! - By default, the generated function has the same visibility as the struct.
21//!   To override this behaviour, the struct can be marked with e. g.
22//!   `#[constructor(visibility = "pub(super)")]`.
23//!
24//! For more advanced uses you might prefer using
25//! [`derive-new`](https://lib.rs/crates/derive-new) or
26//! [`derive_builder`](https://lib.rs/crates/derive_builder) instead.
27//!
28//! # Example
29//!
30//! ```rust
31//! use constructor_lite::ConstructorLite;
32//!
33//! #[derive(Debug, PartialEq, ConstructorLite)]
34//! struct Movie {
35//!     title: String,
36//!     year: Option<u16>,
37//! }
38//!
39//! assert_eq!(
40//!     Movie::new("Star Wars".to_owned()),
41//!     Movie { title: "Star Wars".to_owned(), year: None },
42//! )
43//! ```
44
45use darling::{ast::Data, util::Flag, Error, FromDeriveInput, FromField};
46use proc_macro2::Span;
47use quote::quote;
48use syn::{parse_macro_input, GenericParam, Generics, Ident, Path, Type, Visibility};
49
50#[derive(Debug, FromField)]
51#[darling(attributes(constructor), and_then = "Self::not_both")]
52struct Field {
53    ident: Option<Ident>,
54    ty: Type,
55
56    required: Flag,
57    default: Flag,
58}
59impl Field {
60    fn not_both(self) -> darling::Result<Self> {
61        if self.required.is_present() && self.default.is_present() {
62            Err(
63                Error::custom("Field cannot use `required` and `default`at the same time.")
64                    .with_span(&self.default.span()),
65            )
66        } else {
67            Ok(self)
68        }
69    }
70}
71
72#[derive(Debug, FromDeriveInput)]
73#[darling(attributes(constructor), supports(struct_named))]
74struct ConstructorLite {
75    vis: Visibility,
76    ident: Ident,
77    generics: Generics,
78    data: Data<(), Field>,
79
80    visibility: Option<Visibility>,
81    name: Option<Ident>,
82}
83impl ConstructorLite {
84    fn constructor(&self) -> darling::Result<proc_macro::TokenStream> {
85        let Self {
86            vis,
87            ident,
88            generics,
89            data,
90            visibility,
91            name,
92        } = self;
93
94        let Data::Struct(fields) = data else {
95            return Err(Error::custom("ConstructorLite supports only structs."));
96        };
97
98        let mut arguments = Vec::new();
99        let mut required_field_idents = Vec::new();
100        let mut optional_field_idents = Vec::new();
101
102        for Field {
103            ident,
104            ty,
105            required,
106            default,
107        } in fields.iter()
108        {
109            if required.is_present() {
110                arguments.push(quote!(#ident: #ty));
111                required_field_idents.push(ident);
112                continue;
113            }
114            if default.is_present() {
115                optional_field_idents.push(ident);
116                continue;
117            }
118
119            if let Type::Path(ty) = &ty {
120                if path_is_option(&ty.path) {
121                    optional_field_idents.push(ident);
122                } else {
123                    arguments.push(quote!(#ident: #ty));
124                    required_field_idents.push(ident);
125                }
126            }
127        }
128
129        let mut generics_definitions = generics.clone();
130        generics_definitions.where_clause = None;
131        for param in &mut generics_definitions.params {
132            match param {
133                GenericParam::Lifetime(..) => {}
134                GenericParam::Type(param) => {
135                    param.default = None;
136                }
137                GenericParam::Const(param) => {
138                    param.default = None;
139                }
140            }
141        }
142
143        let mut generics = generics.clone();
144        for param in &mut generics.params {
145            match param {
146                GenericParam::Lifetime(param) => {
147                    param.bounds.clear();
148                }
149                GenericParam::Type(param) => {
150                    param.bounds.clear();
151                    param.default = None;
152                }
153                GenericParam::Const(param) => {
154                    param.default = None;
155                }
156            }
157        }
158        let where_clause = generics.where_clause.take();
159
160        let vis = visibility.as_ref().unwrap_or(vis);
161        let name: Ident = name
162            .clone()
163            .unwrap_or_else(|| Ident::new("new", Span::call_site()));
164
165        let constructor = quote!(
166            impl #generics_definitions #ident #generics #where_clause {
167                #vis fn #name ( #( #arguments ),* ) -> Self {
168                    Self {
169                        #(
170                            #required_field_idents,
171                        )*
172                        #(
173                            #optional_field_idents: Default::default(),
174                        )*
175                    }
176                }
177            }
178        );
179
180        Ok(constructor.into())
181    }
182}
183
184fn path_is_option(path: &Path) -> bool {
185    // Option<T>
186    if path.leading_colon.is_none()
187        && path.segments.len() == 1
188        && path.segments.first().unwrap().ident == "Option"
189    {
190        return true;
191    }
192
193    let mut segments = path.segments.iter();
194
195    // core::option::Option<T>
196    // ::core::option::Option<T>
197    // std::option::Option<T>
198    // ::std::option::Option<T>
199    segments
200        .next()
201        .map(|seg| seg.ident == "core" || seg.ident == "std")
202        .unwrap_or(false)
203        && segments
204            .next()
205            .map(|seg| seg.ident == "option")
206            .unwrap_or(false)
207        && segments
208            .next()
209            .map(|seg| seg.ident == "Option")
210            .unwrap_or(false)
211}
212
213/// Generate a constructor for the required fields of the struct.
214///
215/// See the [`constructor-lite`](crate) crate documentation for more details.
216#[proc_macro_derive(ConstructorLite, attributes(constructor))]
217pub fn constructor_lite_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
218    ConstructorLite::from_derive_input(&parse_macro_input!(input as syn::DeriveInput))
219        .and_then(|constructor| constructor.constructor())
220        .unwrap_or_else(|e| e.write_errors().into())
221}