Skip to content

Commit

Permalink
feat: synstructure derive
Browse files Browse the repository at this point in the history
Signed-off-by: Yaroslav Bolyukin <[email protected]>
  • Loading branch information
CertainLach committed Nov 26, 2021
1 parent b95d96b commit 1124cc2
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 79 deletions.
47 changes: 47 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
description = "Dotfiles manager";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = { self, nixpkgs, flake-utils, rust-overlay, naersk }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs
{
inherit system;
overlays = [ rust-overlay.overlay ];
};
rust = ((pkgs.rustChannelOf { date = "2021-08-16"; channel = "nightly"; }).default.override {
extensions = [ "rust-src" ];
});
naersk-lib = naersk.lib."${system}".override {
rustc = rust;
cargo = rust;
};
in
{
defaultPackage = naersk-lib.buildPackage {
pname = "dotman";
root = ./.;
buildInputs = with pkgs; [
pkgs.sqlite
];
};
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs;[
pkgs.binutils
pkgs.pkgconfig
pkgs.clang
pkgs.x11
pkgs.alsaLib
pkgs.libudev
pkgs.sqlite
rust
];
};
}
);
}
4 changes: 3 additions & 1 deletion gcmodule_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ proc-macro = true
[dependencies]
quote = "1"
syn = { version = "1", features = ["derive"] }
proc-macro2 = "1.0.32"
synstructure = "0.12"

[dev-dependencies]
gcmodule = { path = ".." }
gcmodule = { path = ".." }
127 changes: 56 additions & 71 deletions gcmodule_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,91 +11,76 @@
//! a: String,
//! b: Option<T>,
//!
//! #[trace(skip)] // ignore this field for Trace.
//! #[skip_trace] // ignore this field for Trace.
//! c: MyType,
//! }
//!
//! struct MyType;
//! ```
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use quote::ToTokens;
use syn::Data;
use syn::Attribute;
use synstructure::{decl_derive, AddBounds, BindStyle, Structure};

#[proc_macro_derive(Trace, attributes(trace))]
pub fn gcmodule_trace_derive(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let ident = input.ident;
let mut trace_fn_body = Vec::new();
let mut is_type_tracked_fn_body = Vec::new();
if !input.attrs.into_iter().any(is_skipped) {
match input.data {
Data::Struct(data) => {
for (i, field) in data.fields.into_iter().enumerate() {
if field.attrs.into_iter().any(is_skipped) {
continue;
}
let trace_field = match field.ident {
Some(i) => quote! {
if gcmodule::DEBUG_ENABLED {
eprintln!("[gc] Trace({}): visit .{}", stringify!(#ident), stringify!(#i));
}
self.#i.trace(tracer);
},
None => {
let i = syn::Index::from(i);
quote! {
if gcmodule::DEBUG_ENABLED {
eprintln!("[gc] Trace({}): visit .{}", stringify!(#ident), stringify!(#i));
}
self.#i.trace(tracer);
}
}
};
trace_fn_body.push(trace_field);
let ty = field.ty;
is_type_tracked_fn_body.push(quote! {
if <#ty as _gcmodule::Trace>::is_type_tracked() {
return true;
}
});
}
}
Data::Enum(_) | Data::Union(_) => {
trace_fn_body.push(quote! {
compile_error!("enum or union are not supported");
});
}
};
}
let generated = quote! {
const _: () = {
extern crate gcmodule as _gcmodule;
impl #impl_generics _gcmodule::Trace for #ident #ty_generics #where_clause {
fn trace(&self, tracer: &mut _gcmodule::Tracer) {
#( #trace_fn_body )*
}
decl_derive!([Trace, attributes(skip_trace, ignore_tracking, force_tracking)] => derive_trace);

fn has_attr(attrs: &[Attribute], attr: &str) -> bool {
attrs.iter().any(|a| a.path.is_ident(attr))
}

fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream {
if has_attr(&s.ast().attrs, "skip_trace") {
s.filter(|_| false);
return s.bound_impl(
quote! {::gcmodule::Trace},
quote! {
fn trace(&self, _tracer: &mut ::gcmodule::Tracer) {}
fn is_type_tracked() -> bool {
#( #is_type_tracked_fn_body )*
false
}
}
};
};
generated.into()
}
},
);
}
let force_track = has_attr(&s.ast().attrs, "force_track");

s.filter_variants(|f| !has_attr(f.ast().attrs, "skip_trace"));
s.filter(|f| !has_attr(&f.ast().attrs, "skip_trace"));
s.add_bounds(AddBounds::Fields);
s.bind_with(|_| BindStyle::Ref);

fn is_skipped(attr: syn::Attribute) -> bool {
// check if `#[trace(skip)]` exists.
if attr.path.to_token_stream().to_string() == "trace" {
for token in attr.tokens {
if token.to_string() == "(skip)" {
let trace_body = s.each(|bi| quote!(::gcmodule::Trace::trace(#bi, tracer)));

let is_type_tracked_body = if force_track {
quote! {
true
}
} else {
s.filter(|f| !has_attr(&f.ast().attrs, "ignore_tracking"));
let ty = s
.variants()
.iter()
.flat_map(|v| v.bindings().iter())
.map(|bi| &bi.ast().ty);
quote! {
#(
if <#ty>::is_type_tracked() {
return true;
}
)*
false
}
}
false
};

s.bound_impl(
quote! {::gcmodule::Trace},
quote! {
fn trace(&self, tracer: &mut ::gcmodule::Tracer) {
match *self { #trace_body }
}
fn is_type_tracked() -> bool {
#is_type_tracked_body
}
},
)
}
33 changes: 29 additions & 4 deletions gcmodule_derive/tests/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn test_type_parameters() {
fn test_field_skip() {
#[derive(DeriveTrace)]
struct S2 {
#[trace(skip)]
#[skip_trace]
_a: Option<Box<dyn Trace>>,
_b: (u32, u64),
}
Expand All @@ -52,29 +52,54 @@ fn test_field_skip() {
#[test]
fn test_container_skip() {
#[derive(DeriveTrace)]
#[trace(skip)]
#[skip_trace]
struct S0 {
_a: Option<Box<dyn Trace>>,
_b: (u32, u64),
}
assert!(!S0::is_type_tracked());

#[derive(DeriveTrace)]
#[trace(skip)]
#[skip_trace]
union U0 {
_b: (u32, u64),
}
assert!(!U0::is_type_tracked());

#[derive(DeriveTrace)]
#[trace(skip)]
#[skip_trace]
enum E0 {
_A(Option<Box<dyn Trace>>),
_B(u32, u64),
}
assert!(!E0::is_type_tracked());
}

#[test]
fn test_recursive_struct() {
#[derive(DeriveTrace)]
struct A {
b: Box<dyn Trace>,
#[ignore_tracking]
a: Box<A>,
}
assert!(A::is_type_tracked());

#[derive(DeriveTrace)]
struct B {
#[ignore_tracking]
b: Box<B>,
}
assert!(!B::is_type_tracked());

#[derive(DeriveTrace)]
#[force_tracking]
struct C {
c: (Box<C>, Box<dyn Trace>),
}
assert!(C::is_type_tracked());
}

#[test]
fn test_unnamed_struct() {
#[derive(DeriveTrace)]
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
//! assert_eq!(gcmodule::count_thread_tracked(), 1);
//! ```
//!
//! The `#[trace(skip)]` attribute can be used to skip tracking specified fields
//! The `#[skip_trace]` attribute can be used to skip tracking specified fields
//! in a structure.
//!
//! ```
Expand All @@ -143,7 +143,7 @@
//! struct Foo {
//! field: String,
//!
//! #[trace(skip)]
//! #[skip_trace]
//! alien: AlienStruct, // Field skipped in Trace implementation.
//! }
//! ```
Expand Down Expand Up @@ -299,7 +299,7 @@ pub use sync::{collect::ThreadedObjectSpace, ThreadedCc, ThreadedCcRef};
/// a: S1,
/// b: Option<S2<T, u8>>,
///
/// #[trace(skip)]
/// #[skip_trace]
/// c: AlienStruct, // c is not tracked by the collector.
/// }
///
Expand Down

0 comments on commit 1124cc2

Please sign in to comment.