deepclone-derive 0.3.1

Derive macro for the `deepclone` crate
Documentation
//! `#[derive(DeepClone)]` for the [`deepclone`](https://docs.rs/deepclone) crate.

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, quote_spanned};
use syn::{
	Data, DeriveInput, Expr, Fields, Index, Path, WhereClause, parse_macro_input, parse_quote,
	punctuated::Punctuated, spanned::Spanned,
};

/// What the `#[deepclone(..)]` attributes on the type itself ask for.
#[derive(Default)]
struct Container {
	/// Replaces the generated bounds outright.
	bound: Option<WhereClause>,
	/// Clone the whole value shallowly rather than field by field.
	clone: bool,
}

/// How one field is cloned, after its `#[deepclone(..)]` attribute is applied.
enum Strategy {
	/// Recurse, threading the cloner. Anything reached through here keeps its sharing.
	Deep,
	/// Shallow `Clone::clone`, opted into explicitly at the field.
	Clone,
	/// A user-supplied `fn(&Field, &mut Cloner) -> Field`.
	With(Path),
	/// Ignore the source value entirely.
	Default,
}

/// The pattern that binds a variant's fields, `{ a: field_a, .. }` or `(field_0, ..)`.
fn bind_fields(fields: &Fields) -> TokenStream2 {
	match fields {
		Fields::Named(named) => {
			let bindings = named.named.iter().map(|field| {
				let name = field.ident.as_ref().expect("named field has an identifier");
				let binding = binding_ident(&quote!(#name));
				quote!(#name: #binding)
			});
			quote!({ #(#bindings,)* })
		}
		Fields::Unnamed(unnamed) => {
			let bindings = (0..unnamed.unnamed.len()).map(|index| {
				let index = Index::from(index);
				binding_ident(&quote!(#index))
			});
			quote!((#(#bindings,)*))
		}
		Fields::Unit => quote!(),
	}
}

/// The name a variant's field is bound to, so named and tuple variants share one builder.
fn binding_ident(member: &TokenStream2) -> proc_macro2::Ident {
	format_ident!("field_{}", member.to_string().replace(['.', ' '], "_"))
}

/// Build `ctor { .. }` / `ctor(..)` / `ctor` from a per-field accessor, where `ctor` is
/// `Self` for a struct and `Self::Variant` for an enum variant.
fn clone_fields(
	ctor: &TokenStream2,
	fields: &Fields,
	access: &dyn Fn(TokenStream2) -> TokenStream2,
) -> syn::Result<TokenStream2> {
	Ok(match fields {
		Fields::Named(named) => {
			let values = named
				.named
				.iter()
				.map(|field| {
					let name = field.ident.as_ref().expect("named field has an identifier");
					let value = field_expr(field, access(quote!(#name)))?;
					Ok(quote!(#name: #value))
				})
				.collect::<syn::Result<Vec<_>>>()?;
			quote!(#ctor { #(#values,)* })
		}
		Fields::Unnamed(unnamed) => {
			let values = unnamed
				.unnamed
				.iter()
				.enumerate()
				.map(|(index, field)| {
					let index = Index::from(index);
					field_expr(field, access(quote!(#index)))
				})
				.collect::<syn::Result<Vec<_>>>()?;
			quote!(#ctor(#(#values,)*))
		}
		Fields::Unit => quote!(#ctor),
	})
}

/// Read a container-level `#[deepclone(bound = "..")]`, which replaces the generated bounds.
fn container_attrs(input: &DeriveInput) -> syn::Result<Container> {
	let mut container = Container::default();
	for attr in input
		.attrs
		.iter()
		.filter(|attr| attr.path().is_ident("deepclone"))
	{
		attr.parse_nested_meta(|meta| {
			if meta.path.is_ident("clone") {
				container.clone = true;
				return Ok(());
			}
			if !meta.path.is_ident("bound") {
				return Err(meta.error(
					"unknown `deepclone` container attribute, expected `clone` or `bound = \"..\"`",
				));
			}
			let Expr::Lit(syn::ExprLit {
				lit: syn::Lit::Str(text),
				..
			}) = meta.value()?.parse::<Expr>()?
			else {
				return Err(meta.error("`bound` expects a string, as in `bound = \"T: Copy\"`"));
			};
			let predicates = text.parse_with(Punctuated::parse_terminated)?;
			container.bound = Some(WhereClause {
				where_token: Default::default(),
				predicates,
			});
			Ok(())
		})?;
	}
	Ok(container)
}

/// Derive `DeepClone`, cloning every field through the same `Cloner`.
///
/// Structs (named, tuple, and unit), enums, generics, and where-clauses are supported. Unions
/// are not, unless `#[deepclone(clone)]` makes the fields irrelevant. Every type parameter gains a `DeepClone` bound, as `derive(Clone)` adds a `Clone`
/// bound.
///
/// Nothing here inspects field types: `Rc` and `Arc` reach the cloner through their own
/// `DeepClone` impls, so `Vec<Rc<T>>` and `HashMap<K, Rc<T>>` work too, which a derive
/// matching on the literal token `Rc` would miss.
///
/// No `'static` bound is added, so a generic type with an `Rc<..T..>` field needs `T: 'static`
/// on its own declaration.
///
/// # Field attributes
///
/// - `#[deepclone(clone)]` — use `Clone::clone`, which on an `Rc` shares the allocation.
///   Preferrable (and correct) when nothing reachable through the field can be mutated.
/// - `#[deepclone(with = path)]` — call `path(&field, cloner)`.
/// - `#[deepclone(default)]` — ignore the source value and use `Default::default()`.
///
/// # Container attributes
///
/// - `#[deepclone(clone)]` — clone the whole value with `Clone::clone` instead of field by
///   field. The right answer for a type that reaches nothing shared, since it asks nothing of
///   the field types, so they need no `DeepClone` impl of their own. Bounds each type
///   parameter by `Clone` rather than `DeepClone`.
/// - `#[deepclone(bound = "T: MyBound")]` — replace the generated bounds, for when a
///   `DeepClone` bound on every parameter is too strong.
#[proc_macro_derive(DeepClone, attributes(deepclone))]
pub fn derive_deep_clone(input: TokenStream) -> TokenStream {
	let input = parse_macro_input!(input as DeriveInput);
	expand(&input)
		.unwrap_or_else(syn::Error::into_compile_error)
		.into()
}

/// Build the `DeepClone` impl, or the error to report in its place.
fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
	let container = container_attrs(input)?;
	if container.clone {
		// The fields are never visited below, so an attribute on one would do nothing.
		if let Some(field) = fields_of(&input.data)
			.into_iter()
			.find(|field| field.attrs.iter().any(|a| a.path().is_ident("deepclone")))
		{
			return Err(syn::Error::new(
				field.span(),
				"`#[deepclone(clone)]` on the type already clones every field, so a field \
				 attribute here would have no effect",
			));
		}
	}
	// A whole-type `clone` never looks at the fields, so it works for any shape, unions
	// included, and asks nothing of the field types.
	let body = if container.clone {
		quote!(::core::clone::Clone::clone(self))
	} else {
		match &input.data {
			Data::Struct(data) => {
				clone_fields(&quote!(Self), &data.fields, &|member| quote!(&self.#member))?
			}
			Data::Enum(data) => {
				let arms = data
					.variants
					.iter()
					.map(|variant| {
						let name = &variant.ident;
						let bindings = bind_fields(&variant.fields);
						let fields =
							clone_fields(&quote!(Self::#name), &variant.fields, &|member| {
								let binding = binding_ident(&member);
								quote!(#binding)
							})?;
						Ok(quote!(Self::#name #bindings => #fields))
					})
					.collect::<syn::Result<Vec<_>>>()?;
				// An enum with no variants is uninhabited, so `match` on it needs no arms.
				quote!(match self { #(#arms,)* })
			}
			Data::Union(data) => {
				return Err(syn::Error::new(
					data.union_token.span(),
					"`DeepClone` cannot be derived for unions, because which field is live is \
					 not known statically. `#[deepclone(clone)]` on the type clones it \
					 shallowly instead",
				));
			}
		}
	};

	let name = &input.ident;
	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
	let where_clause = match container.bound {
		Some(bound) => bound,
		None => {
			let mut clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));
			for param in input.generics.type_params() {
				let param = &param.ident;
				// A shallow clone of the whole value needs `Clone`, not `DeepClone`.
				clause.predicates.push(if container.clone {
					parse_quote!(#param: ::core::clone::Clone)
				} else {
					parse_quote!(#param: ::deepclone::DeepClone)
				});
			}
			clause
		}
	};

	Ok(quote! {
		#[automatically_derived]
		impl #impl_generics ::deepclone::DeepClone for #name #ty_generics #where_clause {
			fn deep_clone_in(&self, cloner: &mut ::deepclone::Cloner) -> Self {
				#body
			}
		}
	})
}

/// The expression cloning one field, spanned at the field so type errors land there.
fn field_expr(field: &syn::Field, access: TokenStream2) -> syn::Result<TokenStream2> {
	// Spanned at the field's type, so an unsatisfied bound names the offending field.
	let span = field.ty.span();
	Ok(match field_strategy(field)? {
		Strategy::Deep => {
			quote_spanned!(span => ::deepclone::DeepClone::deep_clone_in(#access, cloner))
		}
		Strategy::Clone => quote_spanned!(span => ::core::clone::Clone::clone(#access)),
		Strategy::With(path) => quote_spanned!(span => #path(#access, cloner)),
		Strategy::Default => quote_spanned!(span => ::core::default::Default::default()),
	})
}

/// The strategy a field's `#[deepclone(..)]` attribute asks for, defaulting to recursion.
fn field_strategy(field: &syn::Field) -> syn::Result<Strategy> {
	let mut strategy = None;
	for attr in field
		.attrs
		.iter()
		.filter(|attr| attr.path().is_ident("deepclone"))
	{
		attr.parse_nested_meta(|meta| {
			let found = if meta.path.is_ident("clone") {
				Strategy::Clone
			} else if meta.path.is_ident("default") {
				Strategy::Default
			} else if meta.path.is_ident("with") {
				Strategy::With(meta.value()?.parse()?)
			} else {
				return Err(meta.error(
					"unknown `deepclone` field attribute, expected `clone`, `default`, or `with`",
				));
			};
			if strategy.is_some() {
				return Err(meta.error("conflicting `deepclone` field attributes"));
			}
			strategy = Some(found);
			Ok(())
		})?;
	}
	Ok(strategy.unwrap_or(Strategy::Deep))
}

/// Every field of a type, whatever its shape, for checks that do not care about the shape.
fn fields_of(data: &Data) -> Vec<&syn::Field> {
	match data {
		Data::Struct(data) => data.fields.iter().collect(),
		Data::Enum(data) => data
			.variants
			.iter()
			.flat_map(|variant| variant.fields.iter())
			.collect(),
		Data::Union(data) => data.fields.named.iter().collect(),
	}
}