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/// How one field is cloned, after its `#[deepclone(..)]` attribute is applied.
12enum Strategy {
13	/// Recurse, threading the cloner. Anything reached through here keeps its sharing.
14	Deep,
15	/// Shallow `Clone::clone`, opted into explicitly at the field.
16	Clone,
17	/// A user-supplied `fn(&Field, &mut Cloner) -> Field`.
18	With(Path),
19	/// Ignore the source value entirely.
20	Default,
21}
22
23/// The pattern that binds a variant's fields, `{ a: field_a, .. }` or `(field_0, ..)`.
24fn bind_fields(fields: &Fields) -> TokenStream2 {
25	match fields {
26		Fields::Named(named) => {
27			let bindings = named.named.iter().map(|field| {
28				let name = field.ident.as_ref().expect("named field has an identifier");
29				let binding = binding_ident(&quote!(#name));
30				quote!(#name: #binding)
31			});
32			quote!({ #(#bindings,)* })
33		}
34		Fields::Unnamed(unnamed) => {
35			let bindings = (0..unnamed.unnamed.len()).map(|index| {
36				let index = Index::from(index);
37				binding_ident(&quote!(#index))
38			});
39			quote!((#(#bindings,)*))
40		}
41		Fields::Unit => quote!(),
42	}
43}
44
45/// The name a variant's field is bound to, so named and tuple variants share one builder.
46fn binding_ident(member: &TokenStream2) -> proc_macro2::Ident {
47	format_ident!("field_{}", member.to_string().replace(['.', ' '], "_"))
48}
49
50/// Build `ctor { .. }` / `ctor(..)` / `ctor` from a per-field accessor, where `ctor` is
51/// `Self` for a struct and `Self::Variant` for an enum variant.
52fn clone_fields(
53	ctor: &TokenStream2,
54	fields: &Fields,
55	access: &dyn Fn(TokenStream2) -> TokenStream2,
56) -> syn::Result<TokenStream2> {
57	Ok(match fields {
58		Fields::Named(named) => {
59			let values = named
60				.named
61				.iter()
62				.map(|field| {
63					let name = field.ident.as_ref().expect("named field has an identifier");
64					let value = field_expr(field, access(quote!(#name)))?;
65					Ok(quote!(#name: #value))
66				})
67				.collect::<syn::Result<Vec<_>>>()?;
68			quote!(#ctor { #(#values,)* })
69		}
70		Fields::Unnamed(unnamed) => {
71			let values = unnamed
72				.unnamed
73				.iter()
74				.enumerate()
75				.map(|(index, field)| {
76					let index = Index::from(index);
77					field_expr(field, access(quote!(#index)))
78				})
79				.collect::<syn::Result<Vec<_>>>()?;
80			quote!(#ctor(#(#values,)*))
81		}
82		Fields::Unit => quote!(#ctor),
83	})
84}
85
86/// Read a container-level `#[deepclone(bound = "..")]`, which replaces the generated bounds.
87fn container_bound(input: &DeriveInput) -> syn::Result<Option<WhereClause>> {
88	let mut bound = None;
89	for attr in input
90		.attrs
91		.iter()
92		.filter(|attr| attr.path().is_ident("deepclone"))
93	{
94		attr.parse_nested_meta(|meta| {
95			if !meta.path.is_ident("bound") {
96				return Err(meta
97					.error("unknown `deepclone` container attribute, expected `bound = \"..\"`"));
98			}
99			let Expr::Lit(syn::ExprLit {
100				lit: syn::Lit::Str(text),
101				..
102			}) = meta.value()?.parse::<Expr>()?
103			else {
104				return Err(meta.error("`bound` expects a string, as in `bound = \"T: Copy\"`"));
105			};
106			let predicates = text.parse_with(Punctuated::parse_terminated)?;
107			bound = Some(WhereClause {
108				where_token: Default::default(),
109				predicates,
110			});
111			Ok(())
112		})?;
113	}
114	Ok(bound)
115}
116
117/// Derive `DeepClone`, cloning every field through the same `Cloner`.
118///
119/// Structs (named, tuple, and unit), enums, generics, and where-clauses are supported; unions
120/// are not. Every type parameter gains a `DeepClone` bound, as `derive(Clone)` adds a `Clone`
121/// bound.
122///
123/// Nothing here inspects field types: `Rc` and `Arc` reach the cloner through their own
124/// `DeepClone` impls, so `Vec<Rc<T>>` and `HashMap<K, Rc<T>>` work too, which a derive
125/// matching on the literal token `Rc` would miss.
126///
127/// No `'static` bound is added, so a generic type with an `Rc<..T..>` field needs `T: 'static`
128/// on its own declaration.
129///
130/// # Field attributes
131///
132/// - `#[deepclone(clone)]` — use `Clone::clone`. Correct for immutable or unshared data,
133///   never for an `Rc` you want independent.
134/// - `#[deepclone(with = path)]` — call `path(&field, cloner)`.
135/// - `#[deepclone(default)]` — ignore the source value and use `Default::default()`.
136///
137/// # Container attributes
138///
139/// - `#[deepclone(bound = "T: MyBound")]` — replace the generated bounds, for when a
140///   `DeepClone` bound on every parameter is too strong.
141#[proc_macro_derive(DeepClone, attributes(deepclone))]
142pub fn derive_deep_clone(input: TokenStream) -> TokenStream {
143	let input = parse_macro_input!(input as DeriveInput);
144	expand(&input)
145		.unwrap_or_else(syn::Error::into_compile_error)
146		.into()
147}
148
149/// Build the `DeepClone` impl, or the error to report in its place.
150fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
151	let body = match &input.data {
152		Data::Struct(data) => {
153			clone_fields(&quote!(Self), &data.fields, &|member| quote!(&self.#member))?
154		}
155		Data::Enum(data) => {
156			let arms = data
157				.variants
158				.iter()
159				.map(|variant| {
160					let name = &variant.ident;
161					let bindings = bind_fields(&variant.fields);
162					let fields = clone_fields(&quote!(Self::#name), &variant.fields, &|member| {
163						let binding = binding_ident(&member);
164						quote!(#binding)
165					})?;
166					Ok(quote!(Self::#name #bindings => #fields))
167				})
168				.collect::<syn::Result<Vec<_>>>()?;
169			// An enum with no variants is uninhabited, so `match` on it needs no arms.
170			quote!(match self { #(#arms,)* })
171		}
172		Data::Union(data) => {
173			return Err(syn::Error::new(
174				data.union_token.span(),
175				"`DeepClone` cannot be derived for unions, because which field is live is not \
176                 known statically",
177			));
178		}
179	};
180
181	let name = &input.ident;
182	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
183	let where_clause = match container_bound(input)? {
184		Some(bound) => bound,
185		None => {
186			let mut clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));
187			for param in input.generics.type_params() {
188				let param = &param.ident;
189				clause
190					.predicates
191					.push(parse_quote!(#param: ::deepclone::DeepClone));
192			}
193			clause
194		}
195	};
196
197	Ok(quote! {
198		#[automatically_derived]
199		impl #impl_generics ::deepclone::DeepClone for #name #ty_generics #where_clause {
200			fn deep_clone_in(&self, cloner: &mut ::deepclone::Cloner) -> Self {
201				#body
202			}
203		}
204	})
205}
206
207/// The expression cloning one field, spanned at the field so type errors land there.
208fn field_expr(field: &syn::Field, access: TokenStream2) -> syn::Result<TokenStream2> {
209	// Spanned at the field's type, so an unsatisfied bound names the offending field.
210	let span = field.ty.span();
211	Ok(match field_strategy(field)? {
212		Strategy::Deep => {
213			quote_spanned!(span => ::deepclone::DeepClone::deep_clone_in(#access, cloner))
214		}
215		Strategy::Clone => quote_spanned!(span => ::core::clone::Clone::clone(#access)),
216		Strategy::With(path) => quote_spanned!(span => #path(#access, cloner)),
217		Strategy::Default => quote_spanned!(span => ::core::default::Default::default()),
218	})
219}
220
221/// The strategy a field's `#[deepclone(..)]` attribute asks for, defaulting to recursion.
222fn field_strategy(field: &syn::Field) -> syn::Result<Strategy> {
223	let mut strategy = None;
224	for attr in field
225		.attrs
226		.iter()
227		.filter(|attr| attr.path().is_ident("deepclone"))
228	{
229		attr.parse_nested_meta(|meta| {
230			let found = if meta.path.is_ident("clone") {
231				Strategy::Clone
232			} else if meta.path.is_ident("default") {
233				Strategy::Default
234			} else if meta.path.is_ident("with") {
235				Strategy::With(meta.value()?.parse()?)
236			} else {
237				return Err(meta.error(
238					"unknown `deepclone` field attribute, expected `clone`, `default`, or `with`",
239				));
240			};
241			if strategy.is_some() {
242				return Err(meta.error("conflicting `deepclone` field attributes"));
243			}
244			strategy = Some(found);
245			Ok(())
246		})?;
247	}
248	Ok(strategy.unwrap_or(Strategy::Deep))
249}