Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions dumpster/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,13 @@ use loom::{
lazy_static,
sync::atomic::{fence, AtomicUsize, Ordering},
};
use std::fmt::Display;
#[cfg(not(loom))]
use std::sync::atomic::{fence, AtomicUsize, Ordering};
use std::{
alloc::{dealloc, handle_alloc_error, Layout},
any::TypeId,
borrow::{Borrow, Cow},
fmt::Debug,
fmt::{Debug, Display},
mem::{self, ManuallyDrop, MaybeUninit},
num::NonZeroUsize,
ops::Deref,
Expand Down
1 change: 1 addition & 0 deletions dumpster_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ proc-macro = true
proc-macro2 = "1.0.60"
quote = "1.0"
syn = "2.0"
synstructure = "0.13.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
197 changes: 26 additions & 171 deletions dumpster_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,198 +11,53 @@
#![warn(clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]

use proc_macro2::{TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned, ToTokens as _};
use syn::{
parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Fields, GenericParam,
Generics, Ident, Index, Path,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Path, Result};

#[proc_macro_derive(Trace, attributes(dumpster))]
/// Derive `Trace` for a type.
pub fn derive_trace(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
synstructure::decl_derive!(
[Trace, attributes(dumpster)] =>
/// Derive `Trace` for a type.
derive_trace
);

fn derive_trace(mut s: synstructure::Structure) -> Result<TokenStream> {
let mut dumpster: Path = parse_quote!(::dumpster);

// look for `crate` argument
for attr in &input.attrs {
for attr in &s.ast().attrs {
if !attr.path().is_ident("dumpster") {
continue;
}

let result = attr.parse_nested_meta(|meta| {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
dumpster = meta.value()?.parse()?;
Ok(())
} else {
Err(meta.error("unsupported attribute"))
}
});

if let Err(err) = result {
return err.into_compile_error().into();
}
})?;
}

// name of the type being implemented
let name = &input.ident;

// generic parameters of the type being implemented
let generics = add_trait_bounds(&dumpster, input.generics);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let impl_generics = {
let tokens = impl_generics.into_token_stream();
let param = quote! { __V: #dumpster::Visitor };

let params = if tokens.is_empty() {
quote! { #param }
} else {
// remove the angle bracket delimiters
let mut tokens: Vec<TokenTree> = tokens.into_iter().skip(1).collect();
tokens.pop();

let tokens: TokenStream = tokens.into_iter().collect();

quote! { #param, #tokens }
};
// Every field must implement `Trace` (but the generics don't).
s.add_bounds(synstructure::AddBounds::Fields);

quote! { < #params > }
};
let match_arms = s.each(|bi| {
quote! {
#dumpster::TraceWith::accept(#bi, visitor)?;
}
});

let do_visitor = delegate_methods(&dumpster, name, &input.data);
let body = quote!(match *self { #match_arms });

let generated = quote! {
unsafe impl #impl_generics #dumpster::TraceWith<__V> for #name #ty_generics #where_clause {
Ok(s.gen_impl(quote! {
gen unsafe impl<__V: #dumpster::Visitor> #dumpster::TraceWith<__V> for @Self {
#[inline]
fn accept(&self, visitor: &mut __V) -> ::core::result::Result<(), ()> {
#do_visitor
#body
::core::result::Result::Ok(())
}
}
};

generated.into()
}

/// Collect the trait bounds for some generic expression.
fn add_trait_bounds(dumpster: &Path, mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param
.bounds
.push(parse_quote!(#dumpster::TraceWith<__V>));
}
}
generics
}

#[allow(clippy::too_many_lines)]
/// Generate method implementations for [`Trace`] for some data type.
fn delegate_methods(dumpster: &Path, name: &Ident, data: &Data) -> TokenStream {
match data {
Data::Struct(data) => match data.fields {
Fields::Named(ref f) => {
let delegate_visit = f.named.iter().map(|f| {
let name = &f.ident;
quote_spanned! {f.span() =>
#dumpster::TraceWith::accept(
&self.#name,
visitor
)?;
}
});

quote! { #(#delegate_visit)* ::core::result::Result::Ok(()) }
}
Fields::Unnamed(ref f) => {
let delegate_visit = f.unnamed.iter().enumerate().map(|(i, f)| {
let index = Index::from(i);
quote_spanned! {f.span() =>
#dumpster::TraceWith::accept(
&self.#index,
visitor
)?;
}
});

quote! { #(#delegate_visit)* ::core::result::Result::Ok(()) }
}
Fields::Unit => quote! { ::core::result::Result::Ok(()) },
},
Data::Enum(e) => {
let mut delegate_visit = TokenStream::new();
for var in &e.variants {
let var_name = &var.ident;

match &var.fields {
Fields::Named(n) => {
let mut binding = TokenStream::new();
let mut execution_visit = TokenStream::new();
for (i, name) in n.named.iter().enumerate() {
let field_name = format_ident!("field{i}");
let field_ident = name.ident.as_ref().unwrap();
if i == 0 {
binding.extend(quote! {
#field_ident: #field_name
});
} else {
binding.extend(quote! {
, #field_ident: #field_name
});
}

execution_visit.extend(quote! {
#dumpster::TraceWith::accept(
#field_name,
visitor
)?;
});
}

delegate_visit.extend(
quote! {#name::#var_name{#binding} => {#execution_visit ::core::result::Result::Ok(())},},
);
}
Fields::Unnamed(u) => {
let mut binding = TokenStream::new();
let mut execution_visit = TokenStream::new();
for (i, _) in u.unnamed.iter().enumerate() {
let field_name = format_ident!("field{i}");
if i == 0 {
binding.extend(quote! {
#field_name
});
} else {
binding.extend(quote! {
, #field_name
});
}

execution_visit.extend(quote! {
#dumpster::TraceWith::accept(
#field_name,
visitor
)?;
});
}

delegate_visit.extend(
quote! {#name::#var_name(#binding) => {#execution_visit ::core::result::Result::Ok(())},},
);
}
Fields::Unit => {
delegate_visit
.extend(quote! {#name::#var_name => ::core::result::Result::Ok(()),});
}
}
}

quote! {match self {#delegate_visit}}
}
Data::Union(u) => {
quote_spanned! {
u.union_token.span => compile_error!("`Trace` must be manually implemented for unions");
}
}
}
}))
}
28 changes: 26 additions & 2 deletions dumpster_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@

use std::{
cell::RefCell,
marker::PhantomData,
sync::atomic::{AtomicU8, AtomicUsize, Ordering},
};

use dumpster::unsync::{collect, Gc};
use dumpster_derive::Trace;
use dumpster::{
unsync::{collect, Gc},
Trace,
};

#[derive(Trace)]
struct Empty;
Expand Down Expand Up @@ -184,3 +187,24 @@ fn unsync_as_ptr() {
assert_ne!(Gc::as_ptr(&b.0), Gc::as_ptr(&b2.0));
assert_ne!(Gc::as_ptr(&b.0), empty2_ptr);
}

const fn assert_implements_trace(_: &impl Trace) {}

/// Some struct that doesn't implement `Trace`.
struct DoesNotImplTrace;

// A generic struct should not have a `Trace` bound for each generic, but for the fields instead.
const _: () = {
// All fields of this type implement `Trace` regardless of `T`
// so the struct also implements `Trace` regardless of `T`.
#[derive(Trace)]
struct GenericStruct<T> {
fn_ptr: fn(T) -> T,
phantom: PhantomData<T>,
}

assert_implements_trace(&GenericStruct::<DoesNotImplTrace> {
fn_ptr: |x| x,
phantom: PhantomData,
});
};
Loading