rust_cc_derive/
lib.rs

1#![forbid(unsafe_code)]
2
3use proc_macro_error::{abort_if_dirty, emit_error, proc_macro_error};
4use quote::quote;
5use syn::{Attribute, Data, Meta, MetaList, Token};
6use syn::punctuated::Punctuated;
7use synstructure::{AddBounds, decl_derive, Structure};
8
9const IGNORE: &str = "ignore";
10const UNSAFE_NO_DROP: &str = "unsafe_no_drop";
11const ALLOWED_ATTR_META_ITEMS: [&str; 2] = [IGNORE, UNSAFE_NO_DROP];
12
13decl_derive!([Trace, attributes(rust_cc)] => #[proc_macro_error] derive_trace_trait);
14
15fn derive_trace_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream {
16    // Check if the struct is annotated with #[rust_cc(unsafe_no_drop)]
17    let no_drop = s.ast().attrs
18    .iter()
19    .any(|attr| attr_contains(attr, UNSAFE_NO_DROP));
20
21    // Ignore every field and variant annotated with #[rust_cc(ignore)]
22    // Filter fields before variants to be able to emit all the errors in case of wrong attributes in ignored variants
23    s.filter(|bi| {
24        !bi.ast().attrs
25        .iter()
26        .any(|attr| attr_contains(attr, IGNORE))
27    });
28
29    // Filter variants only in case of enums
30    if let Data::Enum(_) = s.ast().data {
31        s.filter_variants(|vi| {
32            !vi.ast().attrs
33            .iter()
34            .any(|attr| attr_contains(attr, IGNORE))
35        });
36    }
37
38    // Abort if errors has been emitted
39    abort_if_dirty();
40
41    // Identifier for the ctx parameter of Trace::trace(...)
42    // Shouldn't clash with any other identifier
43    let ctx = quote::format_ident!("__rust_cc__Trace__ctx__");
44
45    // There's no .len() method, so this is the only way to know if there are any traced fields
46    let mut has_no_variants = true; // See inline_attr below
47
48    let body = s.each(|bi| {
49        has_no_variants = false;
50
51        let ty = &bi.ast().ty;
52        quote! {
53            <#ty as rust_cc::Trace>::trace(#bi, #ctx);
54        }
55    });
56
57    // Generate an #[inline(always)] if no field is being traced
58    let inline_attr = if has_no_variants {
59        quote! { #[inline(always)] }
60    } else {
61        quote! { #[inline] }
62    };
63
64    s.underscore_const(true);
65
66    s.add_bounds(AddBounds::Fields);
67    let trace_impl = s.gen_impl(quote! {
68        extern crate rust_cc;
69
70        gen unsafe impl rust_cc::Trace for @Self {
71            #inline_attr
72            #[allow(non_snake_case)]
73            fn trace(&self, #ctx: &mut rust_cc::Context<'_>) {
74                match *self { #body }
75            }
76        }
77    });
78
79    if no_drop {
80        return trace_impl;
81    }
82
83    s.add_bounds(AddBounds::None); // Don't generate bounds for Drop
84    let drop_impl = s.gen_impl(quote! {
85        extern crate core;
86
87        gen impl core::ops::Drop for @Self {
88            #[inline(always)]
89            fn drop(&mut self) {
90            }
91        }
92    });
93
94    quote! {
95        #trace_impl
96        #drop_impl
97    }
98}
99
100fn get_meta_items(attr: &Attribute) -> Option<&MetaList> {
101    if attr.path().is_ident("rust_cc") {
102        match &attr.meta {
103            Meta::List(meta) => Some(meta),
104            err => {
105                emit_error!(err, "Invalid attribute");
106                None
107            },
108        }
109    } else {
110        None
111    }
112}
113
114fn attr_contains(attr: &Attribute, ident: &str) -> bool {
115    let Some(meta_list) = get_meta_items(attr) else {
116        return false;
117    };
118
119    let nested = match meta_list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
120        Ok(nested) => nested,
121        Err(err) => {
122            emit_error!(meta_list, "Invalid attribute: {}", err);
123            return false;
124        },
125    };
126
127    for meta in nested {
128        match meta {
129            Meta::Path(path) if path.is_ident(ident) => {
130                return true;
131            },
132            Meta::Path(path) if ALLOWED_ATTR_META_ITEMS.iter().any(|id| path.is_ident(id)) => {
133                emit_error!(path, "Invalid attribute position");
134            },
135            Meta::Path(path) => {
136                emit_error!(path, "Unrecognized attribute");
137            },
138            err => {
139                emit_error!(err, "Invalid attribute");
140            },
141        }
142    }
143
144    false
145}
146
147decl_derive!([Finalize] => derive_finalize_trait);
148
149fn derive_finalize_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream {
150    s.underscore_const(true);
151    s.add_bounds(AddBounds::None); // Don't generate bounds for Finalize
152    s.gen_impl(quote! {
153        extern crate rust_cc;
154
155        gen impl rust_cc::Finalize for @Self {
156        }
157    })
158}