Skip to main content

deepclone_derive/
lib.rs

1//! `#[derive(DeepClone)]` for the [`deepclone`](https://docs.rs/deepclone) crate.
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::{format_ident, quote, quote_spanned};
6use syn::{
7	Data, DeriveInput, Expr, Fields, Index, Path, WhereClause, parse_macro_input, parse_quote,
8	punctuated::Punctuated, spanned::Spanned,
9};
10
11/// What the `#[deepclone(..)]` attributes on the type itself ask for.
12#[derive(Default)]
13struct Container {
14	/// Replaces the generated bounds outright.
15	bound: Option<WhereClause>,
16	/// Clone the whole value shallowly rather than field by field.
17	clone: bool,
18}
19
20/// How one field is cloned, after its `#[deepclone(..)]` attribute is applied.
21enum Strategy {
22	/// Recurse, threading the cloner. Anything reached through here keeps its sharing.
23	Deep,
24	/// Shallow `Clone::clone`, opted into explicitly at the field.
25	Clone,
26	/// A user-supplied `fn(&Field, &mut Cloner) -> Field`.
27	With(Path),
28	/// Ignore the source value entirely.
29	Default,
30}
31
32/// The pattern that binds a variant's fields, `{ a: field_a, .. }` or `(field_0, ..)`.
33fn bind_fields(fields: &Fields) -> TokenStream2 {
34	match fields {
35		Fields::Named(named) => {
36			let bindings = named.named.iter().map(|field| {
37				let name = field.ident.as_ref().expect("named field has an identifier");
38				let binding = binding_ident(&quote!(#name));
39				quote!(#name: #binding)
40			});
41			quote!({ #(#bindings,)* })
42		}
43		Fields::Unnamed(unnamed) => {
44			let bindings = (0..unnamed.unnamed.len()).map(|index| {
45				let index = Index::from(index);
46				binding_ident(&quote!(#index))
47			});
48			quote!((#(#bindings,)*))
49		}
50		Fields::Unit => quote!(),
51	}
52}
53
54/// The name a variant's field is bound to, so named and tuple variants share one builder.
55fn binding_ident(member: &TokenStream2) -> proc_macro2::Ident {
56	format_ident!("field_{}", member.to_string().replace(['.', ' '], "_"))
57}
58
59/// Build `ctor { .. }` / `ctor(..)` / `ctor` from a per-field accessor, where `ctor` is
60/// `Self` for a struct and `Self::Variant` for an enum variant.
61fn clone_fields(
62	ctor: &TokenStream2,
63	fields: &Fields,
64	access: &dyn Fn(TokenStream2) -> TokenStream2,
65) -> syn::Result<TokenStream2> {
66	Ok(match fields {
67		Fields::Named(named) => {
68			let values = named
69				.named
70				.iter()
71				.map(|field| {
72					let name = field.ident.as_ref().expect("named field has an identifier");
73					let value = field_expr(field, access(quote!(#name)))?;
74					Ok(quote!(#name: #value))
75				})
76				.collect::<syn::Result<Vec<_>>>()?;
77			quote!(#ctor { #(#values,)* })
78		}
79		Fields::Unnamed(unnamed) => {
80			let values = unnamed
81				.unnamed
82				.iter()
83				.enumerate()
84				.map(|(index, field)| {
85					let index = Index::from(index);
86					field_expr(field, access(quote!(#index)))
87				})
88				.collect::<syn::Result<Vec<_>>>()?;
89			quote!(#ctor(#(#values,)*))
90		}
91		Fields::Unit => quote!(#ctor),
92	})
93}
94
95/// Read a container-level `#[deepclone(bound = "..")]`, which replaces the generated bounds.
96fn container_attrs(input: &DeriveInput) -> syn::Result<Container> {
97	let mut container = Container::default();
98	for attr in input
99		.attrs
100		.iter()
101		.filter(|attr| attr.path().is_ident("deepclone"))
102	{
103		attr.parse_nested_meta(|meta| {
104			if meta.path.is_ident("clone") {
105				container.clone = true;
106				return Ok(());
107			}
108			if !meta.path.is_ident("bound") {
109				return Err(meta.error(
110					"unknown `deepclone` container attribute, expected `clone` or `bound = \"..\"`",
111				));
112			}
113			let Expr::Lit(syn::ExprLit {
114				lit: syn::Lit::Str(text),
115				..
116			}) = meta.value()?.parse::<Expr>()?
117			else {
118				return Err(meta.error("`bound` expects a string, as in `bound = \"T: Copy\"`"));
119			};
120			let predicates = text.parse_with(Punctuated::parse_terminated)?;
121			container.bound = Some(WhereClause {
122				where_token: Default::default(),
123				predicates,
124			});
125			Ok(())
126		})?;
127	}
128	Ok(container)
129}
130
131/// Derive `DeepClone`, cloning every field through the same `Cloner`.
132///
133/// Structs (named, tuple, and unit), enums, generics, and where-clauses are supported. Unions
134/// are not, unless `#[deepclone(clone)]` makes the fields irrelevant. Every type parameter gains a `DeepClone` bound, as `derive(Clone)` adds a `Clone`
135/// bound.
136///
137/// Nothing here inspects field types: `Rc` and `Arc` reach the cloner through their own
138/// `DeepClone` impls, so `Vec<Rc<T>>` and `HashMap<K, Rc<T>>` work too, which a derive
139/// matching on the literal token `Rc` would miss.
140///
141/// No `'static` bound is added, so a generic type with an `Rc<..T..>` field needs `T: 'static`
142/// on its own declaration.
143///
144/// # Field attributes
145///
146/// - `#[deepclone(clone)]` — use `Clone::clone`, which on an `Rc` shares the allocation.
147///   Preferrable (and correct) when nothing reachable through the field can be mutated.
148/// - `#[deepclone(with = path)]` — call `path(&field, cloner)`.
149/// - `#[deepclone(default)]` — ignore the source value and use `Default::default()`.
150///
151/// # Container attributes
152///
153/// - `#[deepclone(clone)]` — clone the whole value with `Clone::clone` instead of field by
154///   field. The right answer for a type that reaches nothing shared, since it asks nothing of
155///   the field types, so they need no `DeepClone` impl of their own. Bounds each type
156///   parameter by `Clone` rather than `DeepClone`.
157/// - `#[deepclone(bound = "T: MyBound")]` — replace the generated bounds, for when a
158///   `DeepClone` bound on every parameter is too strong.
159#[proc_macro_derive(DeepClone, attributes(deepclone))]
160pub fn derive_deep_clone(input: TokenStream) -> TokenStream {
161	let input = parse_macro_input!(input as DeriveInput);
162	expand(&input)
163		.unwrap_or_else(syn::Error::into_compile_error)
164		.into()
165}
166
167/// Build the `DeepClone` impl, or the error to report in its place.
168fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
169	let container = container_attrs(input)?;
170	if container.clone {
171		// The fields are never visited below, so an attribute on one would do nothing.
172		if let Some(field) = fields_of(&input.data)
173			.into_iter()
174			.find(|field| field.attrs.iter().any(|a| a.path().is_ident("deepclone")))
175		{
176			return Err(syn::Error::new(
177				field.span(),
178				"`#[deepclone(clone)]` on the type already clones every field, so a field \
179				 attribute here would have no effect",
180			));
181		}
182	}
183	// A whole-type `clone` never looks at the fields, so it works for any shape, unions
184	// included, and asks nothing of the field types.
185	let body = if container.clone {
186		quote!(::core::clone::Clone::clone(self))
187	} else {
188		match &input.data {
189			Data::Struct(data) => {
190				clone_fields(&quote!(Self), &data.fields, &|member| quote!(&self.#member))?
191			}
192			Data::Enum(data) => {
193				let arms = data
194					.variants
195					.iter()
196					.map(|variant| {
197						let name = &variant.ident;
198						let bindings = bind_fields(&variant.fields);
199						let fields =
200							clone_fields(&quote!(Self::#name), &variant.fields, &|member| {
201								let binding = binding_ident(&member);
202								quote!(#binding)
203							})?;
204						Ok(quote!(Self::#name #bindings => #fields))
205					})
206					.collect::<syn::Result<Vec<_>>>()?;
207				// An enum with no variants is uninhabited, so `match` on it needs no arms.
208				quote!(match self { #(#arms,)* })
209			}
210			Data::Union(data) => {
211				return Err(syn::Error::new(
212					data.union_token.span(),
213					"`DeepClone` cannot be derived for unions, because which field is live is \
214					 not known statically. `#[deepclone(clone)]` on the type clones it \
215					 shallowly instead",
216				));
217			}
218		}
219	};
220
221	let name = &input.ident;
222	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
223	let where_clause = match container.bound {
224		Some(bound) => bound,
225		None => {
226			let mut clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));
227			for param in input.generics.type_params() {
228				let param = &param.ident;
229				// A shallow clone of the whole value needs `Clone`, not `DeepClone`.
230				clause.predicates.push(if container.clone {
231					parse_quote!(#param: ::core::clone::Clone)
232				} else {
233					parse_quote!(#param: ::deepclone::DeepClone)
234				});
235			}
236			clause
237		}
238	};
239
240	Ok(quote! {
241		#[automatically_derived]
242		impl #impl_generics ::deepclone::DeepClone for #name #ty_generics #where_clause {
243			fn deep_clone_in(&self, cloner: &mut ::deepclone::Cloner) -> Self {
244				#body
245			}
246		}
247	})
248}
249
250/// The expression cloning one field, spanned at the field so type errors land there.
251fn field_expr(field: &syn::Field, access: TokenStream2) -> syn::Result<TokenStream2> {
252	// Spanned at the field's type, so an unsatisfied bound names the offending field.
253	let span = field.ty.span();
254	Ok(match field_strategy(field)? {
255		Strategy::Deep => {
256			quote_spanned!(span => ::deepclone::DeepClone::deep_clone_in(#access, cloner))
257		}
258		Strategy::Clone => quote_spanned!(span => ::core::clone::Clone::clone(#access)),
259		Strategy::With(path) => quote_spanned!(span => #path(#access, cloner)),
260		Strategy::Default => quote_spanned!(span => ::core::default::Default::default()),
261	})
262}
263
264/// The strategy a field's `#[deepclone(..)]` attribute asks for, defaulting to recursion.
265fn field_strategy(field: &syn::Field) -> syn::Result<Strategy> {
266	let mut strategy = None;
267	for attr in field
268		.attrs
269		.iter()
270		.filter(|attr| attr.path().is_ident("deepclone"))
271	{
272		attr.parse_nested_meta(|meta| {
273			let found = if meta.path.is_ident("clone") {
274				Strategy::Clone
275			} else if meta.path.is_ident("default") {
276				Strategy::Default
277			} else if meta.path.is_ident("with") {
278				Strategy::With(meta.value()?.parse()?)
279			} else {
280				return Err(meta.error(
281					"unknown `deepclone` field attribute, expected `clone`, `default`, or `with`",
282				));
283			};
284			if strategy.is_some() {
285				return Err(meta.error("conflicting `deepclone` field attributes"));
286			}
287			strategy = Some(found);
288			Ok(())
289		})?;
290	}
291	Ok(strategy.unwrap_or(Strategy::Deep))
292}
293
294/// Every field of a type, whatever its shape, for checks that do not care about the shape.
295fn fields_of(data: &Data) -> Vec<&syn::Field> {
296	match data {
297		Data::Struct(data) => data.fields.iter().collect(),
298		Data::Enum(data) => data
299			.variants
300			.iter()
301			.flat_map(|variant| variant.fields.iter())
302			.collect(),
303		Data::Union(data) => data.fields.named.iter().collect(),
304	}
305}