Skip to main content

priority_lfu_derive/
lib.rs

1//! A basic [`DeepSizeOf`](priority_lfu::DeepSizeOf) Derive implementation
2//!
3//! Mainly from `syn`'s [`heap_size` derive example][heap_size]
4//!
5//! [heap_size]: https://github.com/dtolnay/syn/commits/master/examples/heapsize/heapsize_derive/src/lib.rs
6
7extern crate proc_macro;
8
9use proc_macro2::TokenStream;
10use quote::{quote, quote_spanned};
11use syn::spanned::Spanned;
12use syn::{
13	Data, DeriveInput, Fields, GenericParam, Generics, Index, parse_macro_input, parse_quote,
14};
15
16/// Returns the path to the priority_lfu crate.
17/// Uses `crate` when compiling the priority-lfu library itself, otherwise `::priority_lfu`.
18fn crate_path() -> TokenStream {
19	// CARGO_CRATE_NAME gives the actual crate being compiled (with hyphens as underscores).
20	// For src/lib.rs of priority-lfu: CARGO_CRATE_NAME=priority_lfu
21	// For tests/examples: CARGO_CRATE_NAME=integration_tests, etc.
22	// For doctests: CARGO_CRATE_NAME=priority_lfu BUT we need to use external path.
23	//
24	// Detect doctests via UNSTABLE_RUSTDOC_TEST_PATH which rustdoc sets for doctest compilation.
25	let is_priority_lfu_lib =
26		std::env::var("CARGO_CRATE_NAME").is_ok_and(|name| name == "priority_lfu");
27	let is_doctest = std::env::var_os("UNSTABLE_RUSTDOC_TEST_PATH").is_some()
28		|| std::env::var_os("RUSTDOC").is_some();
29
30	if is_priority_lfu_lib && !is_doctest {
31		quote!(crate)
32	} else {
33		quote!(::priority_lfu)
34	}
35}
36
37#[proc_macro_derive(DeepSizeOf)]
38pub fn derive_deep_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
39	// Parse the input tokens into a syntax tree.
40	let input = parse_macro_input!(input as DeriveInput);
41
42	// Used in the quasi-quotation below as `#name`.
43	let name = input.ident;
44
45	let krate = crate_path();
46
47	// Add a bound `T: DeepSizeOf` to every type parameter T.
48	let generics = add_trait_bounds(input.generics, &krate);
49	let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
50
51	// Generate an expression to sum up the size of each field.
52	let sum = deepsize_sum(&input.data, &name, &krate);
53
54	let expanded = quote! {
55		// The generated impl.
56		impl #impl_generics #krate::DeepSizeOf for #name #ty_generics #where_clause {
57			fn deep_size_of_children(&self, context: &mut #krate::Context) -> usize {
58				#sum
59			}
60		}
61	};
62
63	// Hand the output tokens back to the compiler.
64	proc_macro::TokenStream::from(expanded)
65}
66
67// Add a bound `T: DeepSizeOf` to every type parameter T.
68fn add_trait_bounds(mut generics: Generics, krate: &TokenStream) -> Generics {
69	for param in &mut generics.params {
70		if let GenericParam::Type(ref mut type_param) = *param {
71			type_param.bounds.push(parse_quote!(#krate::DeepSizeOf));
72		}
73	}
74	generics
75}
76
77fn match_fields(fields: &syn::Fields, krate: &TokenStream) -> TokenStream {
78	match fields {
79		Fields::Named(fields) => {
80			let recurse = fields.named.iter().map(|f| {
81				let name = &f.ident;
82				quote_spanned! {f.span()=>
83					#krate::DeepSizeOf::deep_size_of_children(&self.#name, context)
84				}
85			});
86			quote! {
87				0 #(+ #recurse)*
88			}
89		}
90		Fields::Unnamed(fields) => {
91			let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
92				let index = Index::from(i);
93				quote_spanned! {f.span()=>
94					#krate::DeepSizeOf::deep_size_of_children(&self.#index, context)
95				}
96			});
97			quote! {
98				0 #(+ #recurse)*
99			}
100		}
101		Fields::Unit => {
102			// Unit structs cannot own more than 0 bytes of memory.
103			quote!(0)
104		}
105	}
106}
107
108fn match_enum_fields(fields: &syn::Fields, krate: &TokenStream) -> TokenStream {
109	match fields {
110		Fields::Named(fields) => {
111			let recurse = fields.named.iter().map(|f| {
112				let name = &f.ident;
113				quote_spanned! {f.span()=>
114					#krate::DeepSizeOf::deep_size_of_children(#name, context)
115				}
116			});
117			quote! {
118				0 #(+ #recurse)*
119			}
120		}
121		Fields::Unnamed(fields) => {
122			let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
123				let i = syn::Ident::new(&format!("_{}", i), proc_macro2::Span::call_site());
124				quote_spanned! {f.span()=>
125					#krate::DeepSizeOf::deep_size_of_children(#i, context)
126				}
127			});
128			quote! {
129				0 #(+ #recurse)*
130			}
131		}
132		Fields::Unit => {
133			// Unit structs cannot own more than 0 bytes of memory.
134			quote!(0)
135		}
136	}
137}
138
139fn get_matcher(var: &syn::Variant) -> TokenStream {
140	let matcher = match &var.fields {
141		Fields::Unit => TokenStream::new(),
142		Fields::Unnamed(fields) => {
143			let fields: TokenStream = (0..fields.unnamed.len())
144				.map(|n| {
145					let i = syn::Ident::new(&format!("_{}", n), proc_macro2::Span::call_site());
146					quote!(#i,)
147				})
148				.collect();
149			quote!((#fields))
150		}
151		Fields::Named(fields) => {
152			let fields: TokenStream = fields
153				.named
154				.iter()
155				.map(|f| {
156					let i = f.ident.as_ref().expect("Field identifier should be present");
157					quote!(#i,)
158				})
159				.collect();
160			quote!({#fields})
161		}
162	};
163
164	quote!(#matcher)
165}
166
167/// Generate an expression to sum up the size of each field.
168fn deepsize_sum(data: &Data, struct_name: &proc_macro2::Ident, krate: &TokenStream) -> TokenStream {
169	match *data {
170		Data::Struct(ref inner) => match_fields(&inner.fields, krate),
171		Data::Enum(ref inner) => {
172			let arms = inner.variants.iter().map(|var| {
173				let matcher = get_matcher(var);
174				let output = match_enum_fields(&var.fields, krate);
175				let name = &var.ident;
176				let ident = quote!(#struct_name::#name);
177				quote!(#ident #matcher => #output,)
178			});
179
180			quote! {
181				match self {
182					#(#arms)*
183					_ => 0 // This is needed for empty enums
184				}
185			}
186		}
187		Data::Union(_) => unimplemented!(),
188	}
189}