Skip to content

Commit 810a9ad

Browse files
committed
feat: synstructure derive
Signed-off-by: Yaroslav Bolyukin <[email protected]>
1 parent b95d96b commit 810a9ad

File tree

4 files changed

+91
-79
lines changed

4 files changed

+91
-79
lines changed

gcmodule_derive/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ proc-macro = true
1313
[dependencies]
1414
quote = "1"
1515
syn = { version = "1", features = ["derive"] }
16+
proc-macro2 = "1.0.32"
17+
synstructure = "0.12"
1618

1719
[dev-dependencies]
18-
gcmodule = { path = ".." }
20+
gcmodule = { path = ".." }

gcmodule_derive/src/lib.rs

Lines changed: 56 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -11,91 +11,76 @@
1111
//! a: String,
1212
//! b: Option<T>,
1313
//!
14-
//! #[trace(skip)] // ignore this field for Trace.
14+
//! #[skip_trace] // ignore this field for Trace.
1515
//! c: MyType,
1616
//! }
1717
//!
1818
//! struct MyType;
1919
//! ```
2020
extern crate proc_macro;
2121

22-
use proc_macro::TokenStream;
2322
use quote::quote;
24-
use quote::ToTokens;
25-
use syn::Data;
23+
use syn::Attribute;
24+
use synstructure::{decl_derive, AddBounds, BindStyle, Structure};
2625

27-
#[proc_macro_derive(Trace, attributes(trace))]
28-
pub fn gcmodule_trace_derive(input: TokenStream) -> TokenStream {
29-
let input = syn::parse_macro_input!(input as syn::DeriveInput);
30-
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
31-
let ident = input.ident;
32-
let mut trace_fn_body = Vec::new();
33-
let mut is_type_tracked_fn_body = Vec::new();
34-
if !input.attrs.into_iter().any(is_skipped) {
35-
match input.data {
36-
Data::Struct(data) => {
37-
for (i, field) in data.fields.into_iter().enumerate() {
38-
if field.attrs.into_iter().any(is_skipped) {
39-
continue;
40-
}
41-
let trace_field = match field.ident {
42-
Some(i) => quote! {
43-
if gcmodule::DEBUG_ENABLED {
44-
eprintln!("[gc] Trace({}): visit .{}", stringify!(#ident), stringify!(#i));
45-
}
46-
self.#i.trace(tracer);
47-
},
48-
None => {
49-
let i = syn::Index::from(i);
50-
quote! {
51-
if gcmodule::DEBUG_ENABLED {
52-
eprintln!("[gc] Trace({}): visit .{}", stringify!(#ident), stringify!(#i));
53-
}
54-
self.#i.trace(tracer);
55-
}
56-
}
57-
};
58-
trace_fn_body.push(trace_field);
59-
let ty = field.ty;
60-
is_type_tracked_fn_body.push(quote! {
61-
if <#ty as _gcmodule::Trace>::is_type_tracked() {
62-
return true;
63-
}
64-
});
65-
}
66-
}
67-
Data::Enum(_) | Data::Union(_) => {
68-
trace_fn_body.push(quote! {
69-
compile_error!("enum or union are not supported");
70-
});
71-
}
72-
};
73-
}
74-
let generated = quote! {
75-
const _: () = {
76-
extern crate gcmodule as _gcmodule;
77-
impl #impl_generics _gcmodule::Trace for #ident #ty_generics #where_clause {
78-
fn trace(&self, tracer: &mut _gcmodule::Tracer) {
79-
#( #trace_fn_body )*
80-
}
26+
decl_derive!([Trace, attributes(skip_trace, ignore_tracking, force_tracking)] => derive_trace);
27+
28+
fn has_attr(attrs: &[Attribute], attr: &str) -> bool {
29+
attrs.iter().any(|a| a.path.is_ident(attr))
30+
}
31+
32+
fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream {
33+
if has_attr(&s.ast().attrs, "skip_trace") {
34+
s.filter(|_| false);
35+
return s.bound_impl(
36+
quote! {::gcmodule::Trace},
37+
quote! {
38+
fn trace(&self, _tracer: &mut ::gcmodule::Tracer) {}
8139
fn is_type_tracked() -> bool {
82-
#( #is_type_tracked_fn_body )*
8340
false
8441
}
85-
}
86-
};
87-
};
88-
generated.into()
89-
}
42+
},
43+
);
44+
}
45+
let force_tracking = has_attr(&s.ast().attrs, "force_tracking");
46+
47+
s.filter_variants(|f| !has_attr(f.ast().attrs, "skip_trace"));
48+
s.filter(|f| !has_attr(&f.ast().attrs, "skip_trace"));
49+
s.add_bounds(AddBounds::Fields);
50+
s.bind_with(|_| BindStyle::Ref);
9051

91-
fn is_skipped(attr: syn::Attribute) -> bool {
92-
// check if `#[trace(skip)]` exists.
93-
if attr.path.to_token_stream().to_string() == "trace" {
94-
for token in attr.tokens {
95-
if token.to_string() == "(skip)" {
52+
let trace_body = s.each(|bi| quote!(::gcmodule::Trace::trace(#bi, tracer)));
53+
54+
let is_type_tracked_body = if force_tracking {
55+
quote! {
56+
true
57+
}
58+
} else {
59+
s.filter(|f| !has_attr(&f.ast().attrs, "ignore_tracking"));
60+
let ty = s
61+
.variants()
62+
.iter()
63+
.flat_map(|v| v.bindings().iter())
64+
.map(|bi| &bi.ast().ty);
65+
quote! {
66+
#(
67+
if <#ty>::is_type_tracked() {
9668
return true;
9769
}
70+
)*
71+
false
9872
}
99-
}
100-
false
73+
};
74+
75+
s.bound_impl(
76+
quote! {::gcmodule::Trace},
77+
quote! {
78+
fn trace(&self, tracer: &mut ::gcmodule::Tracer) {
79+
match *self { #trace_body }
80+
}
81+
fn is_type_tracked() -> bool {
82+
#is_type_tracked_body
83+
}
84+
},
85+
)
10186
}

gcmodule_derive/tests/trace.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ fn test_type_parameters() {
4242
fn test_field_skip() {
4343
#[derive(DeriveTrace)]
4444
struct S2 {
45-
#[trace(skip)]
45+
#[skip_trace]
4646
_a: Option<Box<dyn Trace>>,
4747
_b: (u32, u64),
4848
}
@@ -52,29 +52,54 @@ fn test_field_skip() {
5252
#[test]
5353
fn test_container_skip() {
5454
#[derive(DeriveTrace)]
55-
#[trace(skip)]
55+
#[skip_trace]
5656
struct S0 {
5757
_a: Option<Box<dyn Trace>>,
5858
_b: (u32, u64),
5959
}
6060
assert!(!S0::is_type_tracked());
6161

6262
#[derive(DeriveTrace)]
63-
#[trace(skip)]
63+
#[skip_trace]
6464
union U0 {
6565
_b: (u32, u64),
6666
}
6767
assert!(!U0::is_type_tracked());
6868

6969
#[derive(DeriveTrace)]
70-
#[trace(skip)]
70+
#[skip_trace]
7171
enum E0 {
7272
_A(Option<Box<dyn Trace>>),
7373
_B(u32, u64),
7474
}
7575
assert!(!E0::is_type_tracked());
7676
}
7777

78+
#[test]
79+
fn test_recursive_struct() {
80+
#[derive(DeriveTrace)]
81+
struct A {
82+
b: Box<dyn Trace>,
83+
#[ignore_tracking]
84+
a: Box<A>,
85+
}
86+
assert!(A::is_type_tracked());
87+
88+
#[derive(DeriveTrace)]
89+
struct B {
90+
#[ignore_tracking]
91+
b: Box<B>,
92+
}
93+
assert!(!B::is_type_tracked());
94+
95+
#[derive(DeriveTrace)]
96+
#[force_tracking]
97+
struct C {
98+
c: (Box<C>, Box<dyn Trace>),
99+
}
100+
assert!(C::is_type_tracked());
101+
}
102+
78103
#[test]
79104
fn test_unnamed_struct() {
80105
#[derive(DeriveTrace)]

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
//! assert_eq!(gcmodule::count_thread_tracked(), 1);
132132
//! ```
133133
//!
134-
//! The `#[trace(skip)]` attribute can be used to skip tracking specified fields
134+
//! The `#[skip_trace]` attribute can be used to skip tracking specified fields
135135
//! in a structure.
136136
//!
137137
//! ```
@@ -143,7 +143,7 @@
143143
//! struct Foo {
144144
//! field: String,
145145
//!
146-
//! #[trace(skip)]
146+
//! #[skip_trace]
147147
//! alien: AlienStruct, // Field skipped in Trace implementation.
148148
//! }
149149
//! ```
@@ -299,7 +299,7 @@ pub use sync::{collect::ThreadedObjectSpace, ThreadedCc, ThreadedCcRef};
299299
/// a: S1,
300300
/// b: Option<S2<T, u8>>,
301301
///
302-
/// #[trace(skip)]
302+
/// #[skip_trace]
303303
/// c: AlienStruct, // c is not tracked by the collector.
304304
/// }
305305
///

0 commit comments

Comments
 (0)