Skip to content
This repository has been archived by the owner on Jul 10, 2023. It is now read-only.

Update the derive crate #100

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ install:

build_script:
- cargo build --verbose --features "%FEATURES%"
- cargo build --manifest-path derive\Cargo.toml --verbose

test_script:
- cargo test --verbose --features "%FEATURES%"
- cargo test --manifest-path derive\Cargo.toml --verbose
10 changes: 7 additions & 3 deletions derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ name = "test"
path = "test.rs"

[dependencies]
syn = "0.11"
quote = "0.3"
synstructure = "0.5"
syn = "0.14"
quote = "0.6"
synstructure = "0.9"
proc-macro2 = "0.4.18"

[dev-dependencies]
heapsize = { path = ".." }
231 changes: 146 additions & 85 deletions derive/lib.rs
Original file line number Diff line number Diff line change
@@ -1,104 +1,165 @@
#[cfg(not(test))] extern crate proc_macro;
#[macro_use] extern crate quote;
extern crate syn;
#[macro_use]
extern crate synstructure;
#[macro_use]
extern crate quote;
extern crate proc_macro2;
extern crate syn;

#[cfg(not(test))]
#[proc_macro_derive(HeapSizeOf, attributes(ignore_heap_size_of))]
pub fn expand_token_stream(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
expand_string(&input.to_string()).parse().unwrap()
}
use syn::{Field, Meta, MetaList, MetaNameValue, Type};

fn expand_string(input: &str) -> String {
let mut type_ = syn::parse_macro_input(input).unwrap();
use synstructure::Structure;

let style = synstructure::BindStyle::Ref.into();
let match_body = synstructure::each_field(&mut type_, &style, |binding| {
let ignore = binding.field.attrs.iter().any(|attr| match attr.value {
syn::MetaItem::Word(ref ident) |
syn::MetaItem::List(ref ident, _) if ident == "ignore_heap_size_of" => {
panic!("#[ignore_heap_size_of] should have an explanation, \
e.g. #[ignore_heap_size_of = \"because reasons\"]");
}
syn::MetaItem::NameValue(ref ident, _) if ident == "ignore_heap_size_of" => {
true
fn heapsizeof_derive(mut s: Structure) -> proc_macro2::TokenStream {
let body =
s.filter(|bi| !should_ignore_field(bi.ast())).each(|bi| {
match bi.ast().ty {
Type::Array(_) => quote!{
for item in #bi.iter() {
sum += item.heap_size_of_children();
}
},
_ => quote!{ sum += #bi.heap_size_of_children(); },
}
_ => false,
});
if ignore {
None
} else if let syn::Ty::Array(..) = binding.field.ty {
Some(quote! {
for item in #binding.iter() {
sum += ::heapsize::HeapSizeOf::heap_size_of_children(item);
}
})
} else {
Some(quote! {
sum += ::heapsize::HeapSizeOf::heap_size_of_children(#binding);
})
}
});

let name = &type_.ident;
let (impl_generics, ty_generics, where_clause) = type_.generics.split_for_impl();
let mut where_clause = where_clause.clone();
for param in &type_.generics.ty_params {
where_clause.predicates.push(syn::WherePredicate::BoundPredicate(syn::WhereBoundPredicate {
bound_lifetimes: Vec::new(),
bounded_ty: syn::Ty::Path(None, param.ident.clone().into()),
bounds: vec![syn::TyParamBound::Trait(
syn::PolyTraitRef {
bound_lifetimes: Vec::new(),
trait_ref: syn::parse_path("::heapsize::HeapSizeOf").unwrap(),
},
syn::TraitBoundModifier::None
)],
}))
}

let tokens = quote! {
impl #impl_generics ::heapsize::HeapSizeOf for #name #ty_generics #where_clause {
#[inline]
#[allow(unused_variables, unused_mut, unreachable_code)]
s.gen_impl(quote! {
extern crate heapsize;

gen impl heapsize::HeapSizeOf for @Self {
fn heap_size_of_children(&self) -> usize {
let mut sum = 0;
match *self {
#match_body
}

match *self { #body }

sum
}
}
};

tokens.to_string()
})
}

#[test]
fn test_struct() {
let mut source = "struct Foo<T> { bar: Bar, baz: T, #[ignore_heap_size_of = \"\"] z: Arc<T> }";
let mut expanded = expand_string(source);
let mut no_space = expanded.replace(" ", "");
macro_rules! match_count {
($e: expr, $count: expr) => {
assert_eq!(no_space.matches(&$e.replace(" ", "")).count(), $count,
"counting occurences of {:?} in {:?} (whitespace-insensitive)",
$e, expanded)
decl_derive!([HeapSizeOf, attributes(ignore_heap_size_of)] => heapsizeof_derive);

const PANIC_MSG: &str = "#[ignore_heap_size_of] should have an explanation, \
e.g. #[ignore_heap_size_of = \"because reasons\"]";

fn should_ignore_field(ast: &Field) -> bool {
for attr in &ast.attrs {
match attr.interpret_meta().unwrap() {
Meta::Word(ref ident) | Meta::List(MetaList { ref ident, .. })
if ident == "ignore_heap_size_of" =>
{
panic!("{}", PANIC_MSG);
}
Meta::NameValue(MetaNameValue { ref ident, .. })
if ident == "ignore_heap_size_of" =>
{
return true
}
other => panic!("Other: {:?}", other),
}
}
match_count!("struct", 0);
match_count!("ignore_heap_size_of", 0);
match_count!("impl<T> ::heapsize::HeapSizeOf for Foo<T> where T: ::heapsize::HeapSizeOf {", 1);
match_count!("sum += ::heapsize::HeapSizeOf::heap_size_of_children(", 2);

source = "struct Bar([Baz; 3]);";
expanded = expand_string(source);
no_space = expanded.replace(" ", "");
match_count!("for item in", 1);

false
}

#[should_panic(expected = "should have an explanation")]
#[test]
fn test_no_reason() {
expand_string("struct A { #[ignore_heap_size_of] b: C }");
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn normal_struct() {
test_derive! {
heapsizeof_derive {
struct Foo {
a: u32,
things: Vec<Foo>,
array: [u32; 5],
}
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_heapsize_HeapSizeOf_FOR_Foo: () = {
extern crate heapsize;

impl heapsize::HeapSizeOf for Foo {
fn heap_size_of_children(&self) -> usize {
let mut sum = 0;
match * self {
Foo {
a: ref __binding_0,
things : ref __binding_1,
array: ref __binding_2,
}
=> {
{ sum += __binding_0.heap_size_of_children(); }
{ sum += __binding_1.heap_size_of_children(); }
{
for item in __binding_2.iter() {
sum += item.heap_size_of_children();
}
}
}
}
sum
}
}
};
}
}
}

#[test]
fn tuple_struct() {
test_derive! {
heapsizeof_derive {
struct Tuple([Box<u32>; 2], Box<u8>);
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_heapsize_HeapSizeOf_FOR_Tuple: () = {
extern crate heapsize ;

impl heapsize::HeapSizeOf for Tuple {
fn heap_size_of_children(&self) -> usize {
let mut sum = 0;
match *self {
Tuple (
ref __binding_0,
ref __binding_1 ,
)
=> {
{
for item in __binding_0.iter() {
sum += item.heap_size_of_children();
}
}
{
sum += __binding_1.heap_size_of_children();
}
}
}

sum
}
}
};
}
}
}

#[test]
#[should_panic(
expected = "#[ignore_heap_size_of] should have an explanation"
)]
fn all_ignored_fields_require_an_explanation() {
test_derive! {
heapsizeof_derive {
struct Blah {
#[ignore_heap_size_of]
foo: u32,
}
}
expands to {} no_build
}
}
}
49 changes: 35 additions & 14 deletions derive/test.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
#[macro_use] extern crate heapsize_derive;
#[macro_use]
extern crate heapsize_derive;
extern crate heapsize;

mod heapsize {
pub trait HeapSizeOf {
fn heap_size_of_children(&self) -> usize;
}
use heapsize::HeapSizeOf;
use std::mem;

impl<T> HeapSizeOf for Box<T> {
fn heap_size_of_children(&self) -> usize {
::std::mem::size_of::<T>()
}
}
#[derive(HeapSizeOf)]
struct Tuple([Box<u32>; 2], Box<u8>);

#[test]
fn tuple_struct() {
assert_eq!(
Tuple([Box::new(1), Box::new(2)], Box::new(3)).heap_size_of_children(),
9
);
}

#[test]
fn sanity_check_heapsize_works_as_expected() {
assert_eq!(Box::new(1_u8).heap_size_of_children(), mem::size_of::<u8>());
assert_eq!([Box::new(1), Box::new(2)].heap_size_of_children(), 2 * 4);
}

#[derive(HeapSizeOf)]
struct Foo([Box<u32>; 2], Box<u8>);
struct Normal {
first: Tuple,
second: Box<(u32, u32)>,
#[ignore_heap_size_of = "We don't care about this"]
ignored: Vec<Normal>,
}

#[test]
fn test() {
use heapsize::HeapSizeOf;
assert_eq!(Foo([Box::new(1), Box::new(2)], Box::new(3)).heap_size_of_children(), 9);
fn normal_struct() {
let tuple = Tuple([Box::new(1), Box::new(2)], Box::new(3));
let normal = Normal {
first: tuple,
second: Box::new((0, 0)),
ignored: Vec::with_capacity(1024),
};

let got = normal.heap_size_of_children();
assert_eq!(got, 9 + 2 * 4);
}