derived_deref/
lib.rs

1//! A crate for deriving the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html)
2//! and [`DerefMut`](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) 
3//! traits from the standard library onto structs with at least one field. 
4//! Fields with references are passed directly.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use derived_deref::{Deref, DerefMut};
10//!
11//! #[derive(Deref, DerefMut)]
12//! struct StringWithCount {
13//!     // Annotation of `#[target]` is required when there are two+ fields.
14//!     #[target] inner: String,
15//!     count: usize,
16//! }
17//! 
18//!
19//! // When there is only one field, annotation is optional instead.
20//!
21//! #[derive(Deref, DerefMut)]
22//! struct StringWrapper(String);
23//!
24//! #[derive(Deref, DerefMut)]
25//! struct CountWrapper(#[target] usize);
26//! ```
27
28extern crate proc_macro;
29
30use proc_macro::TokenStream;
31use syn::{parse_macro_input, ItemStruct, Ident, Generics, Field, Fields, Index, Type, punctuated::Punctuated, token::Comma};
32use quote::quote;
33use proc_macro2::TokenStream as TokenStream2;
34
35/// Derives the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html)
36/// trait, passing the field directly if a reference type.
37#[proc_macro_derive(Deref, attributes(target))]
38pub fn derive_deref(input: TokenStream) -> TokenStream {
39    // Creates the ItemStruct...
40    let item_struct = parse_macro_input!(input as ItemStruct);
41    let name = item_struct.ident;
42    let generics = item_struct.generics;
43
44    // ...to then get the desired field, one marked by `#[target]`.
45    // However, if there's only one field, being marked is no longer required.
46    match extract_field_parameters(item_struct.fields, "Deref") {
47        Ok((field_name, field_type, is_mut_reference)) => impl_deref(name, generics, field_name, Some(field_type), is_mut_reference),
48        Err(error) => error,
49    }
50}
51
52/// Derives the [`DerefMut`](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) 
53/// trait, passing the field directly if a reference type. This will fail to
54/// compile if the chosen field is an immutable reference type.
55#[proc_macro_derive(DerefMut, attributes(target))]
56// Deriving for `DerefMut` is the same as with `Deref` with the exception that
57// `Target` does not need to be defined.
58pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
59    let item_struct = parse_macro_input!(input as ItemStruct);
60    let name = item_struct.ident;
61    let generics = item_struct.generics;
62
63    match extract_field_parameters(item_struct.fields, "DerefMut") {
64        Ok((field_name, _, is_mut_reference)) => impl_deref(name, generics, field_name, None, is_mut_reference),
65        Err(error) => error,
66    }
67}
68
69// Acquires the only field or the marked field coupled with its index.
70fn get_field(fields: Punctuated<Field, Comma>) -> Result<(usize, Field), TokenStream> {
71    let attribute_name = "target";
72    let error = || quote! { compile_error!("`#[target]` is required for one field"); }.into();
73    
74    let has_one_field = fields.len() == 1;
75    let mut fields_iter = fields.into_iter().fuse().enumerate();
76    
77    if has_one_field {
78        // An infallible call to take the first field.
79        fields_iter.next().ok_or_else(error)
80    } else {
81        // Below filters for the fields marked correctly with `#[target]`.
82        let mut fields_iter = fields_iter.filter(|(_, field)| {
83            field.attrs.iter().any(|attribute| {
84                attribute.meta
85                    .require_path_only()
86                    .is_ok_and(|path| path.is_ident(attribute_name))
87            })
88        });
89
90        // Takes the next element, only keeping the `Some` if the next take
91        // is a `None`. This ensures there's only one field marked `#[target]`.
92        fields_iter.next().filter(|_| {
93            fields_iter
94                .next()
95                .is_none()
96        })
97        .ok_or_else(error)
98    }
99}
100
101fn extract_field_parameters(fields: Fields, trait_name: &str) -> Result<(TokenStream2, Type, Option<bool>), TokenStream> {
102    match fields {
103        Fields::Named(fields) => {
104            let (_, field) = get_field(fields.named)?;
105            let field_name = field.ident.unwrap();
106            let (field_type, is_mut_reference) = match field.ty {
107                Type::Reference(reference_type) => (*reference_type.elem, Some(reference_type.mutability.is_some())),
108                field_type => (field_type, None),
109            };
110
111            Ok((quote! { #field_name }, field_type, is_mut_reference))
112        },
113        Fields::Unnamed(fields) => {
114            let (field_index, field) = get_field(fields.unnamed)?;
115            let field_index = Index::from(field_index);
116            let (field_type, is_mut_reference) = match field.ty {
117                Type::Reference(reference_type) => (*reference_type.elem, Some(reference_type.mutability.is_some())),
118                field_type => (field_type, None),
119            };
120
121            Ok((quote! { #field_index }, field_type, is_mut_reference))
122        },
123        Fields::Unit => {
124            let error = &format!("unable to implement `{}` trait for struct of no fields", trait_name)[..];
125
126            Err(quote! { compile_error!(#error); }.into())
127        }
128    }
129}
130
131fn impl_deref(
132    struct_name: Ident,
133    struct_generics: Generics,
134    field_name: TokenStream2,
135    // Only whenever there is no need for `field_type` does it mean `Deref` is 
136    // being implemented with its mutable counterpart.
137    field_type: Option<Type>,
138    // For if the field is a reference: `Some` if it is and `None` otherwise.
139    // The boolean is `true` when it is mutable and `false` otherwise.
140    is_mut_reference: Option<bool>,
141) -> TokenStream 
142{
143    let (impl_generics, type_generics, where_clause) = struct_generics.split_for_impl();
144
145    match field_type {
146        Some(field_type) => {
147            // If not a reference, "&" is passed. If it is, nothing is instead. 
148            let reference = is_mut_reference.map_or_else(|| Some(quote!(&)), |_| None);
149            
150            quote! {
151                impl #impl_generics core::ops::Deref for #struct_name #type_generics #where_clause {
152                    type Target = #field_type;
153
154                    fn deref(&self) -> &Self::Target {
155                        #reference self.#field_name
156                    }
157                }
158            }
159        },
160        None => {
161            let reference = match is_mut_reference {
162                Some(true) => None,
163                Some(false) => return quote! { compile_error!("`#[target]` is unable to be of an immutable reference"); }.into(),
164                None => Some(quote!(&mut)),
165            };
166            
167            quote! {
168                impl #impl_generics core::ops::DerefMut for #struct_name #type_generics #where_clause {
169                    fn deref_mut(&mut self) -> &mut Self::Target {
170                        #reference self.#field_name
171                    }
172                }
173            }
174        },
175    }
176    .into()
177}