1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use quote::quote;
use syn::parse::Error;
use syn::parse_quote;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Fields, Index, Member};

extern crate proc_macro;

#[allow(non_snake_case)]
#[proc_macro_derive(DropTake, attributes(drop_take))]
pub fn DropTake(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    syn::parse(input)
        .and_then(impl_drop_take_semantics)
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

fn impl_drop_take_semantics(input: DeriveInput) -> syn::parse::Result<proc_macro2::TokenStream> {
    let name = input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    match input.data {
        Data::Enum(data) => Err(Error::new(
            data.enum_token.span(),
            "DropTake only works for structs",
        )),
        Data::Union(data) => Err(Error::new(
            data.union_token.span(),
            "DropTake only works for structs",
        )),
        Data::Struct(data) => {
            let take = extract_drop_take_fields(data.fields);
            Ok(quote! {
                #[automatically_derived]
                impl #impl_generics ::core::ops::Drop for #name #ty_generics #where_clause {
                    #[allow(warnings)]
                    fn drop(&mut self) {
                        extern crate drop_take;
                        <Self as drop_take::DropTake>::drop_take({ #take })
                    }
                }
            })
        }
    }
}

fn extract_drop_take_fields(fields: Fields) -> proc_macro2::TokenStream {
    let fields = match fields {
        Fields::Unit => Default::default(),
        Fields::Named(fields) => fields.named,
        Fields::Unnamed(fields) => fields.unnamed,
    };
    let members = fields
        .into_iter()
        .enumerate()
        .filter(|(_i, field)| {
            field
                .attrs
                .iter()
                .any(|attr| attr.path == parse_quote!(drop_take))
        })
        .map(|(i, field)| {
            if let Some(name) = field.ident {
                Member::Named(name)
            } else {
                Member::Unnamed(Index {
                    index: i as u32,
                    span: field.ty.span(),
                })
            }
        });

    quote!(unsafe { (#(drop_take::Take::take(&mut self. #members)),*) })
}