use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, Generics, WhereClause, WherePredicate, parse_quote, Type};
use crate::parse::{TypedPolicyValue, StructPolicy, MergeField};
pub fn generate_struct_impl(
struct_name: &Ident,
generics: &Generics,
fields: &[MergeField],
struct_policy: Option<&StructPolicy>,
where_clause: Option<&WhereClause>,
) -> TokenStream {
let mut impl_generics = generics.clone();
impl_generics.params.push(parse_quote!(P));
let (impl_generics_tokens, _, _) = impl_generics.split_for_impl();
let (_, ty_generics, _) = generics.split_for_impl();
let field_names: Vec<_> = fields.iter().map(|f| &f.ident).collect();
let struct_policy_setup = generate_struct_policy_setup(struct_policy);
let mut enhanced_where_clause = inject_bounds_into_where_clause(fields, where_clause);
for field in fields {
let config = crate::parse::parse_field_typed_policies(field);
let needs_eq_hash = config.sequence.as_ref().and_then(|v| v.as_literal_str()).map(|s| matches!(s, "union" | "intersect")).unwrap_or(false)
|| struct_policy.and_then(|sp| sp.sequence_dedupe).unwrap_or(false)
|| struct_policy.and_then(|sp| sp.unique_by.as_ref()).is_some();
if needs_eq_hash && is_vec_type(&field.ty) {
if let Type::Path(tp) = &field.ty
&& let Some(seg) = tp.path.segments.last()
&& seg.ident == "Vec"
&& let syn::PathArguments::AngleBracketed(args) = &seg.arguments
&& let Some(syn::GenericArgument::Type(elem_ty)) = args.args.first() {
let bound1: WherePredicate = parse_quote! { #elem_ty: Clone };
let bound2: WherePredicate = parse_quote! { #elem_ty: Eq };
let bound3: WherePredicate = parse_quote! { #elem_ty: ::core::hash::Hash };
enhanced_where_clause.predicates.push(bound1);
enhanced_where_clause.predicates.push(bound2);
enhanced_where_clause.predicates.push(bound3);
}
}
}
let policy_bound: WherePredicate = parse_quote! { P: ::deepmerge::Policy };
enhanced_where_clause.predicates.push(policy_bound);
let field_merges: Vec<TokenStream> = fields.iter().map(|field| {
generate_field_merge(field, struct_policy)
}).collect();
let _field_merges_clone_for_future = field_merges.clone();
quote! {
impl #impl_generics_tokens ::deepmerge::DeepMerge<P> for #struct_name #ty_generics #enhanced_where_clause
{
fn merge_with_policy(&mut self, other: Self, policy: &P) {
let Self { #(#field_names),* } = other;
#struct_policy_setup
#(#field_merges)*
}
}
}
}
pub fn generate_enum_impl(
enum_name: &Ident,
generics: &Generics,
where_clause: Option<&WhereClause>,
) -> TokenStream {
let mut impl_generics = generics.clone();
impl_generics.params.push(parse_quote!(P));
let (impl_generics_tokens, _, _) = impl_generics.split_for_impl();
let (_, ty_generics, _) = generics.split_for_impl();
quote! {
impl #impl_generics_tokens ::deepmerge::DeepMerge<P> for #enum_name #ty_generics #where_clause
where
P: ::deepmerge::Policy,
{
fn merge_with_policy(&mut self, other: Self, _policy: &P) {
*self = other;
}
}
}
}
fn generate_struct_policy_setup(_struct_policy: Option<&StructPolicy>) -> TokenStream {
quote! {
let struct_policy = policy;
}
}
fn generate_field_merge(field: &MergeField, struct_policy: Option<&StructPolicy>) -> TokenStream {
let field_name = field.ident.as_ref().expect("Named field");
let config = crate::parse::parse_field_typed_policies(field);
if config.skip {
return quote! {
let _ = #field_name;
};
}
if let Some(custom_fn) = &config.with {
return quote! {
#custom_fn(&mut self.#field_name, #field_name);
};
}
let mut body = if is_string_type(&field.ty) {
if let Some(ref string_policy) = config.string {
let policy_tokens = generate_policy_variant_tokens("string", string_policy);
quote! {
match #policy_tokens {
::deepmerge::StringMerge::Replace => { self.#field_name = #field_name; },
::deepmerge::StringMerge::Keep => { let _ = #field_name; },
::deepmerge::StringMerge::Concat => { self.#field_name.push_str(&#field_name); },
::deepmerge::StringMerge::ConcatWithSep(sep) => {
if !self.#field_name.is_empty() && !#field_name.is_empty() { self.#field_name.push_str(sep); }
self.#field_name.push_str(&#field_name);
},
_ => { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
}
} else if let Some(struct_policy) = struct_policy {
if let Some(ref struct_string_policy) = struct_policy.string {
let policy_tokens = generate_policy_variant_tokens("string", struct_string_policy);
quote! {
match #policy_tokens {
::deepmerge::StringMerge::Replace => { self.#field_name = #field_name; },
::deepmerge::StringMerge::Keep => { let _ = #field_name; },
::deepmerge::StringMerge::Concat => { self.#field_name.push_str(&#field_name); },
::deepmerge::StringMerge::ConcatWithSep(sep) => {
if !self.#field_name.is_empty() && !#field_name.is_empty() { self.#field_name.push_str(sep); }
self.#field_name.push_str(&#field_name);
},
_ => { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
}
} else {
quote! {
self.#field_name.merge_with_policy(#field_name, struct_policy);
}
}
} else {
quote! {
self.#field_name.merge_with_policy(#field_name, struct_policy);
}
}
} else if is_bool_type(&field.ty) {
if let Some(ref bool_policy) = config.bool_config {
let policy_tokens = generate_policy_variant_tokens("bool", bool_policy);
quote! {
match #policy_tokens {
::deepmerge::BoolMerge::Replace => { self.#field_name = #field_name; },
::deepmerge::BoolMerge::Keep => { let _ = #field_name; },
::deepmerge::BoolMerge::TrueWins => { if #field_name { self.#field_name = true; } },
::deepmerge::BoolMerge::FalseWins => { if !#field_name { self.#field_name = false; } },
_ => { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
}
} else if let Some(struct_policy) = struct_policy {
if let Some(ref struct_bool) = struct_policy.bool_policy {
let policy_tokens = generate_policy_variant_tokens("bool", struct_bool);
quote! {
match #policy_tokens {
::deepmerge::BoolMerge::Replace => { self.#field_name = #field_name; },
::deepmerge::BoolMerge::Keep => { let _ = #field_name; },
::deepmerge::BoolMerge::TrueWins => { if #field_name { self.#field_name = true; } },
::deepmerge::BoolMerge::FalseWins => { if !#field_name { self.#field_name = false; } },
_ => { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
}
} else {
quote! { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
} else {
quote! { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
} else if is_number_primitive(&field.ty) {
if let Some(ref number_policy) = config.number {
let policy_tokens = generate_policy_variant_tokens("number", number_policy);
quote! {
match #policy_tokens {
::deepmerge::NumberMerge::Replace => { self.#field_name = #field_name; },
::deepmerge::NumberMerge::Keep => { let _ = #field_name; },
::deepmerge::NumberMerge::Max => { self.#field_name = self.#field_name.max(#field_name); },
::deepmerge::NumberMerge::Min => { self.#field_name = self.#field_name.min(#field_name); },
::deepmerge::NumberMerge::Sum => { self.#field_name = self.#field_name + #field_name; },
}
}
} else if let Some(struct_policy) = struct_policy {
if let Some(ref struct_number) = struct_policy.number {
let policy_tokens = generate_policy_variant_tokens("number", struct_number);
quote! {
match #policy_tokens {
::deepmerge::NumberMerge::Replace => { self.#field_name = #field_name; },
::deepmerge::NumberMerge::Keep => { let _ = #field_name; },
::deepmerge::NumberMerge::Max => { self.#field_name = self.#field_name.max(#field_name); },
::deepmerge::NumberMerge::Min => { self.#field_name = self.#field_name.min(#field_name); },
::deepmerge::NumberMerge::Sum => { self.#field_name = self.#field_name + #field_name; },
}
}
} else {
quote! { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
} else {
quote! { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
} else if is_vec_type(&field.ty) {
if let Some(ref sequence_policy) = config.sequence {
let policy_tokens = generate_policy_variant_tokens("sequence", sequence_policy);
quote! {
match #policy_tokens {
::deepmerge::SequenceMerge::Append | ::deepmerge::SequenceMerge::Extend => {
self.#field_name.reserve(#field_name.len());
self.#field_name.extend(#field_name);
},
::deepmerge::SequenceMerge::Prepend => {
let mut result = #field_name;
result.reserve(self.#field_name.len());
result.extend(::core::mem::take(&mut self.#field_name));
self.#field_name = result;
},
_ => {
self.#field_name.reserve(#field_name.len());
self.#field_name.extend(#field_name);
}
}
}
} else if let Some(struct_policy) = struct_policy {
if let Some(ref struct_seq) = struct_policy.sequence {
let policy_tokens = generate_policy_variant_tokens("sequence", struct_seq);
quote! {
match #policy_tokens {
::deepmerge::SequenceMerge::Append | ::deepmerge::SequenceMerge::Extend => {
self.#field_name.reserve(#field_name.len());
self.#field_name.extend(#field_name);
},
::deepmerge::SequenceMerge::Prepend => {
let mut result = #field_name;
result.reserve(self.#field_name.len());
result.extend(::core::mem::take(&mut self.#field_name));
self.#field_name = result;
},
_ => {
self.#field_name.reserve(#field_name.len());
self.#field_name.extend(#field_name);
}
}
}
} else {
quote! { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
} else {
quote! { self.#field_name.merge_with_policy(#field_name, struct_policy); }
}
} else {
quote! {
self.#field_name.merge_with_policy(#field_name, struct_policy);
}
};
if is_vec_type(&field.ty) {
if let Some(key_fn_expr) = &config.unique_by {
body = quote! {
#body
#[cfg(all(feature = "std", feature = "alloc"))]
{
::deepmerge::vec_dedupe_in_place_by_key(&mut self.#field_name, #key_fn_expr);
}
};
} else if config.dedupe {
body = quote! {
#body
#[cfg(all(feature = "std", feature = "alloc"))]
{
::deepmerge::vec_dedupe_in_place(&mut self.#field_name);
}
};
} else if let Some(struct_policy) = struct_policy {
if let Some(true) = struct_policy.sequence_dedupe {
body = quote! {
#body
#[cfg(all(feature = "std", feature = "alloc"))]
{
::deepmerge::vec_dedupe_in_place(&mut self.#field_name);
}
};
}
if let Some(ref key_fn_expr) = struct_policy.unique_by {
body = quote! {
#body
#[cfg(all(feature = "std", feature = "alloc"))]
{
::deepmerge::vec_dedupe_in_place_by_key(&mut self.#field_name, #key_fn_expr);
}
};
}
}
}
body
}
#[allow(clippy::too_many_lines, clippy::match_same_arms)]
fn generate_policy_variant_tokens(
policy_type: &str,
value: &TypedPolicyValue,
) -> TokenStream {
match value {
TypedPolicyValue::Literal(lit_value) => {
match policy_type {
"sequence" => match lit_value.as_str() {
"append" => quote! { ::deepmerge::SequenceMerge::Append },
"prepend" => quote! { ::deepmerge::SequenceMerge::Prepend },
"union" => quote! { ::deepmerge::SequenceMerge::Union },
"extend" => quote! { ::deepmerge::SequenceMerge::Extend },
"intersect" => quote! { ::deepmerge::SequenceMerge::Intersect },
_ => quote! { ::deepmerge::SequenceMerge::Append },
},
"map" => match lit_value.as_str() {
"overlay" => quote! { ::deepmerge::MapMerge::Overlay },
"union" => quote! { ::deepmerge::MapMerge::Union },
"left" => quote! { ::deepmerge::MapMerge::Left },
"right" => quote! { ::deepmerge::MapMerge::Right },
_ => quote! { ::deepmerge::MapMerge::Overlay },
},
"option" => match lit_value.as_str() {
"take" => quote! { ::deepmerge::OptionMerge::Take },
"preserve" => quote! { ::deepmerge::OptionMerge::Preserve },
"or_left" => quote! { ::deepmerge::OptionMerge::OrLeft },
_ => quote! { ::deepmerge::OptionMerge::Take },
},
"number" => match lit_value.as_str() {
"sum" => quote! { ::deepmerge::NumberMerge::Sum },
"max" => quote! { ::deepmerge::NumberMerge::Max },
"min" => quote! { ::deepmerge::NumberMerge::Min },
"replace" => quote! { ::deepmerge::NumberMerge::Replace },
"keep" => quote! { ::deepmerge::NumberMerge::Keep },
_ => quote! { ::deepmerge::NumberMerge::Replace },
},
"bool" => match lit_value.as_str() {
"true_wins" => quote! { ::deepmerge::BoolMerge::TrueWins },
"false_wins" => quote! { ::deepmerge::BoolMerge::FalseWins },
"replace" => quote! { ::deepmerge::BoolMerge::Replace },
"keep" => quote! { ::deepmerge::BoolMerge::Keep },
_ => quote! { ::deepmerge::BoolMerge::Replace },
},
"string" => match lit_value.as_str() {
"concat" => quote! { ::deepmerge::StringMerge::Concat },
"replace" => quote! { ::deepmerge::StringMerge::Replace },
"keep" => quote! { ::deepmerge::StringMerge::Keep },
_ => quote! { ::deepmerge::StringMerge::Replace },
},
"condition" => match lit_value.as_str() {
"always" => quote! { ::deepmerge::Condition::Always },
"non_empty" => quote! { ::deepmerge::Condition::NonEmpty },
"non_default" => quote! { ::deepmerge::Condition::NonDefault },
"some" => quote! { ::deepmerge::Condition::Some },
"changed" => quote! { ::deepmerge::Condition::Changed },
_ => quote! { ::deepmerge::Condition::Always },
},
_ => quote! { },
}
},
TypedPolicyValue::Ident(ident) => {
let ident_str = ident.to_string();
match policy_type {
"sequence" => match ident_str.as_str() {
"append" => quote! { ::deepmerge::SequenceMerge::Append },
"prepend" => quote! { ::deepmerge::SequenceMerge::Prepend },
"union" => quote! { ::deepmerge::SequenceMerge::Union },
"extend" => quote! { ::deepmerge::SequenceMerge::Extend },
"intersect" => quote! { ::deepmerge::SequenceMerge::Intersect },
_ => quote! { #ident },
},
"map" => match ident_str.as_str() {
"overlay" => quote! { ::deepmerge::MapMerge::Overlay },
"union" => quote! { ::deepmerge::MapMerge::Union },
"left" => quote! { ::deepmerge::MapMerge::Left },
"right" => quote! { ::deepmerge::MapMerge::Right },
_ => quote! { #ident },
},
"option" => match ident_str.as_str() {
"take" => quote! { ::deepmerge::OptionMerge::Take },
"preserve" => quote! { ::deepmerge::OptionMerge::Preserve },
"or_left" => quote! { ::deepmerge::OptionMerge::OrLeft },
_ => quote! { #ident },
},
"number" => match ident_str.as_str() {
"sum" => quote! { ::deepmerge::NumberMerge::Sum },
"max" => quote! { ::deepmerge::NumberMerge::Max },
"min" => quote! { ::deepmerge::NumberMerge::Min },
"replace" => quote! { ::deepmerge::NumberMerge::Replace },
"keep" => quote! { ::deepmerge::NumberMerge::Keep },
_ => quote! { #ident },
},
"bool" => match ident_str.as_str() {
"true_wins" => quote! { ::deepmerge::BoolMerge::TrueWins },
"false_wins" => quote! { ::deepmerge::BoolMerge::FalseWins },
"replace" => quote! { ::deepmerge::BoolMerge::Replace },
"keep" => quote! { ::deepmerge::BoolMerge::Keep },
_ => quote! { #ident },
},
"string" => match ident_str.as_str() {
"concat" => quote! { ::deepmerge::StringMerge::Concat },
"replace" => quote! { ::deepmerge::StringMerge::Replace },
"keep" => quote! { ::deepmerge::StringMerge::Keep },
_ => quote! { #ident },
},
"condition" => match ident_str.as_str() {
"always" => quote! { ::deepmerge::Condition::Always },
"non_empty" => quote! { ::deepmerge::Condition::NonEmpty },
"non_default" => quote! { ::deepmerge::Condition::NonDefault },
"some" => quote! { ::deepmerge::Condition::Some },
"changed" => quote! { ::deepmerge::Condition::Changed },
_ => quote! { #ident },
},
_ => quote! { #ident },
}
},
TypedPolicyValue::Path(expr) => quote! { #expr },
}
}
fn inject_bounds_into_where_clause(
fields: &[MergeField],
existing: Option<&WhereClause>,
) -> syn::WhereClause {
let mut where_clause = existing.cloned().unwrap_or_else(|| WhereClause {
where_token: syn::token::Where::default(),
predicates: syn::punctuated::Punctuated::default(),
});
for field in fields {
let ty = &field.ty;
let bound: WherePredicate = parse_quote! { #ty: ::deepmerge::DeepMerge<P> };
where_clause.predicates.push(bound);
}
where_clause
}
fn is_string_type(ty: &Type) -> bool {
match ty {
Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
segment.ident == "String"
} else {
false
}
}
_ => false,
}
}
fn is_vec_type(ty: &Type) -> bool {
match ty {
Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
segment.ident == "Vec"
} else {
false
}
}
_ => false,
}
}
fn is_bool_type(ty: &Type) -> bool {
matches!(ty, Type::Path(tp) if tp.path.segments.last().map(|s| s.ident == "bool").unwrap_or(false))
}
fn is_number_primitive(ty: &Type) -> bool {
matches!(ty, Type::Path(tp) if tp.path.segments.last().map(|s| {
let id = &s.ident;
id == "i8" || id == "i16" || id == "i32" || id == "i64" || id == "i128" || id == "isize"
|| id == "u8" || id == "u16" || id == "u32" || id == "u64" || id == "u128" || id == "usize"
|| id == "f32" || id == "f64"
}).unwrap_or(false))
}