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 let no_drop = s.ast().attrs
18 .iter()
19 .any(|attr| attr_contains(attr, UNSAFE_NO_DROP));
20
21 s.filter(|bi| {
24 !bi.ast().attrs
25 .iter()
26 .any(|attr| attr_contains(attr, IGNORE))
27 });
28
29 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_dirty();
40
41 let ctx = quote::format_ident!("__rust_cc__Trace__ctx__");
44
45 let mut has_no_variants = true; 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 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); 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); s.gen_impl(quote! {
153 extern crate rust_cc;
154
155 gen impl rust_cc::Finalize for @Self {
156 }
157 })
158}