Skip to main content

matchmaker_partial_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{ToTokens, format_ident, quote};
3use std::collections::HashSet;
4use syn::{
5    Fields, GenericArgument, ItemStruct, LitStr, Meta, Path, PathArguments, Token, Type,
6    parse::Parse, parse_macro_input, spanned::Spanned,
7};
8
9#[proc_macro_attribute]
10pub fn partial(attr: TokenStream, item: TokenStream) -> TokenStream {
11    let mut input = parse_macro_input!(item as ItemStruct);
12
13    if !cfg!(feature = "partial") {
14        input.attrs.retain(|attr| !attr.path().is_ident("partial"));
15        if let Fields::Named(fields) = &mut input.fields {
16            for field in &mut fields.named {
17                field.attrs.retain(|attr| !attr.path().is_ident("partial"));
18            }
19        }
20        return quote!(#input).into();
21    }
22
23    let name = &input.ident;
24    let partial_name = format_ident!("Partial{}", name);
25
26    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
27    let vis = &input.vis;
28
29    let mut struct_recurse = false;
30    let mut struct_unwrap = false;
31    let mut generate_path_setter = false;
32    let mut enable_merge = false; // Additive change: Gate for merge/clear
33    let mut manual_derives: Option<proc_macro2::TokenStream> = None;
34    let mut manual_attrs: Vec<proc_macro2::TokenStream> = Vec::new();
35    let mut has_manual_attrs = false;
36
37    // --- 1. Parse macro arguments (Top level: #[partial(path, recurse, derive, attr)]) ---
38    if !attr.is_empty() {
39        let parser = syn::parse::Parser::parse2(
40            |input: syn::parse::ParseStream| {
41                while !input.is_empty() {
42                    let path: Path = input.parse()?;
43                    if path.is_ident("recurse") {
44                        struct_recurse = true;
45                    } else if path.is_ident("unwrap") {
46                        struct_unwrap = true;
47                    } else if path.is_ident("path") {
48                        generate_path_setter = true;
49                    } else if path.is_ident("merge") {
50                        enable_merge = true; // Mark as merge-enabled
51                    } else if path.is_ident("derive") {
52                        // Check if derive has parentheses
53                        if input.peek(syn::token::Paren) {
54                            let content;
55                            syn::parenthesized!(content in input);
56                            let paths = content.parse_terminated(Path::parse, Token![,])?;
57                            manual_derives = Some(quote! { #[derive(#paths)] });
58                        } else {
59                            // derive without parentheses -> just mark as manual (empty)
60                            manual_derives = Some(quote! {});
61                        }
62                    } else if path.is_ident("attr") {
63                        has_manual_attrs = true;
64                        if input.peek(syn::token::Paren) {
65                            let content;
66                            syn::parenthesized!(content in input);
67                            let inner: Meta = content.parse()?;
68                            manual_attrs.push(quote! { #[#inner] });
69                        }
70                    } else {
71                        // Error on unknown attributes
72                        return Err(syn::Error::new(
73                            path.span(),
74                            format!("unknown partial attribute: {}", path.to_token_stream()),
75                        ));
76                    }
77
78                    if input.peek(Token![,]) {
79                        input.parse::<Token![,]>()?;
80                    }
81                }
82                Ok(())
83            },
84            attr.into(),
85        );
86
87        if let Err(e) = parser {
88            return e.to_compile_error().into();
89        }
90    }
91
92    // --- 2. Remove any #[partial] attributes from the struct & check for 'path' ---
93    let mut attr_errors = Vec::new();
94    input.attrs.retain(|attr| {
95        if attr.path().is_ident("partial") {
96            let res = attr.parse_nested_meta(|meta| {
97                if meta.path.is_ident("recurse") {
98                    struct_recurse = true;
99                } else if meta.path.is_ident("unwrap") {
100                    struct_unwrap = true;
101                } else if meta.path.is_ident("path") {
102                    generate_path_setter = true;
103                } else if meta.path.is_ident("merge") {
104                    enable_merge = true; // Mark as merge-enabled from struct attribute
105                } else if meta.path.is_ident("derive") {
106                    if meta.input.peek(syn::token::Paren) {
107                        let content;
108                        syn::parenthesized!(content in meta.input);
109                        let paths = content.parse_terminated(Path::parse, Token![,]).unwrap();
110                        manual_derives = Some(quote! { #[derive(#paths)] });
111                    }
112                } else if meta.path.is_ident("attr") {
113                    has_manual_attrs = true;
114                    if meta.input.peek(syn::token::Paren) {
115                        let content;
116                        syn::parenthesized!(content in meta.input);
117                        let inner: Meta = content.parse().unwrap();
118                        manual_attrs.push(quote! { #[#inner] });
119                    }
120                } else {
121                    return Err(meta.error(format!(
122                        "unknown partial attribute: {}",
123                        meta.path.to_token_stream()
124                    )));
125                }
126                Ok(())
127            });
128
129            if let Err(e) = res {
130                attr_errors.push(e);
131            }
132            false
133        } else {
134            true
135        }
136    });
137
138    if let Some(err) = attr_errors.first() {
139        return err.to_compile_error().into();
140    }
141
142    // --- 3. Build final struct attributes ---
143    let mut final_attrs = Vec::new();
144    let mut has_default = false;
145
146    if let Some(manual) = manual_derives {
147        let manual_str = manual.to_token_stream().to_string();
148        if manual_str.contains("Default") {
149            has_default = true;
150        }
151        final_attrs.push(manual);
152    } else {
153        for attr in &input.attrs {
154            if attr.path().is_ident("derive") {
155                let tokens = attr.to_token_stream();
156                if tokens.to_string().contains("Default") {
157                    has_default = true;
158                }
159                final_attrs.push(tokens);
160            }
161        }
162    }
163
164    if !has_default {
165        final_attrs.push(quote! { #[derive(Default)] });
166    }
167
168    if has_manual_attrs {
169        final_attrs.extend(manual_attrs);
170    } else {
171        for attr in &input.attrs {
172            if !attr.path().is_ident("derive") {
173                final_attrs.push(attr.to_token_stream());
174            }
175        }
176    }
177
178    // --- 4. Process fields ---
179    let fields = match &mut input.fields {
180        Fields::Named(fields) => &mut fields.named,
181        _ => panic!("Partial only supports structs with named fields"),
182    };
183
184    let mut partial_field_defs = Vec::new();
185    let mut apply_field_stmts = Vec::new();
186    let mut merge_field_stmts = Vec::new();
187    let mut clear_field_stmts = Vec::new();
188    let mut set_field_arms = Vec::new();
189    let mut flattened_field_targets = Vec::new();
190    let mut used_idents = HashSet::new();
191
192    for field in fields.iter_mut() {
193        let field_name = &field.ident;
194        let field_vis = &field.vis;
195        let field_ty = &field.ty;
196
197        let mut skip_field = false;
198        let mut field_recurse = false;
199        let mut recurse_override: Option<Option<proc_macro2::TokenStream>> = None;
200        let mut field_unwrap = struct_unwrap;
201        let mut field_set: Option<String> = None;
202        let mut field_attrs_for_mirror = Vec::new();
203        let mut field_errors = Vec::new();
204        // 4 Check for Serde custom deserialization attributes
205        let mut custom_deserializer: Option<Path> = None;
206        let mut field_aliases = Vec::new();
207        let mut is_flattened = false;
208
209        field.attrs.retain(|attr| {
210            // --- 4a. Handle #[partial] attributes ---
211            if attr.path().is_ident("partial") {
212                let res = attr.parse_nested_meta(|meta| {
213                    if meta.path.is_ident("skip") {
214                        skip_field = true;
215                    } else if meta.path.is_ident("unwrap") {
216                        field_unwrap = true;
217                    } else if meta.path.is_ident("set") {
218                        let s: LitStr = meta.value()?.parse()?;
219                        field_set = Some(s.value());
220                    } else if meta.path.is_ident("alias") {
221                        let s: LitStr = meta.value()?.parse()?;
222                        field_aliases.push(s.value());
223                    } else if meta.path.is_ident("flatten") {
224                        is_flattened = true;
225                    } else if meta.path.is_ident("recurse") {
226                        if let Ok(value) = meta.value() {
227                            let s: LitStr = value.parse().unwrap();
228                            if s.value().is_empty() {
229                                recurse_override = Some(None);
230                            } else {
231                                let ty: Type = s.parse().unwrap();
232                                recurse_override = Some(Some(quote! { #ty }));
233                            }
234                        } else {
235                            // Enable recursion using default naming convention
236                            field_recurse = true;
237                        }
238                    } else if meta.path.is_ident("attr") {
239                        field_attrs_for_mirror.clear();
240                        if meta.input.peek(syn::token::Paren) {
241                            let content;
242                            syn::parenthesized!(content in meta.input);
243                            while !content.is_empty() {
244                                let inner_meta: Meta = content.parse()?;
245                                field_attrs_for_mirror.push(quote! { #[#inner_meta] });
246                                if content.peek(Token![,]) {
247                                    content.parse::<Token![,]>()?;
248                                }
249                            }
250                        }
251                    } else {
252                        return Err(meta.error(format!(
253                            "unknown partial attribute: {}",
254                            meta.path.to_token_stream()
255                        )));
256                    }
257                    Ok(())
258                });
259
260                if let Err(e) = res {
261                    field_errors.push(e);
262                }
263                return false; // Always drop #[partial]
264            }
265
266            // --- 4b. Handle #[serde] attributes ---
267            if attr.path().is_ident("serde") {
268                let mut drop_attr = false;
269                let _ = attr.parse_nested_meta(|meta| {
270                    if meta.path.is_ident("deserialize_with") {
271                        if let Ok(value) = meta.value()
272                            && let Ok(s) = value.parse::<LitStr>()
273                        {
274                            custom_deserializer = s.parse::<Path>().ok();
275                            drop_attr = true;
276                        }
277                    } else if meta.path.is_ident("with") {
278                        if let Ok(value) = meta.value()
279                            && let Ok(s) = value.parse::<LitStr>()
280                            && let Ok(mut p) = s.parse::<Path>()
281                        {
282                            p.segments.push(format_ident!("deserialize").into());
283                            custom_deserializer = Some(p);
284                            drop_attr = true;
285                        }
286                    } else if meta.path.is_ident("alias") {
287                        if let Ok(value) = meta.value()
288                            && let Ok(s) = value.parse::<LitStr>()
289                        {
290                            field_aliases.push(s.value());
291                        }
292                    } else if meta.path.is_ident("flatten") {
293                        is_flattened = true;
294                    }
295                    Ok(())
296                });
297
298                if drop_attr {
299                    return false; // Drop the #[serde] attribute
300                }
301            }
302
303            // Keep the attribute and mirror it if it's not a #[partial]
304            field_attrs_for_mirror.push(attr.to_token_stream());
305            true
306        });
307
308        if let Some(err) = field_errors.first() {
309            return err.to_compile_error().into();
310        }
311
312        if skip_field {
313            continue;
314        }
315
316        if let Some(ref s) = field_set
317            && s == "sequence"
318            && recurse_override.is_some()
319        {
320            return syn::Error::new(
321                field.span(),
322                "cannot use 'recurse' and 'set = \"sequence\"' on the same field",
323            )
324            .to_compile_error()
325            .into();
326        }
327
328        let is_opt = is_option(field_ty);
329        let inner_ty = if is_opt {
330            extract_inner_type_from_option(field_ty)
331        } else {
332            field_ty
333        };
334
335        let coll_info = get_collection_info(inner_ty);
336
337        // Determine if we should recurse
338        let mut should_recurse = (struct_recurse || field_recurse || recurse_override.is_some())
339            && !matches!(recurse_override, Some(None));
340
341        if let Some(ref s) = field_set
342            && s == "sequence"
343        {
344            should_recurse = false;
345        }
346
347        let current_field_ty: proc_macro2::TokenStream;
348        let mut is_recursive_field = false;
349
350        if let Some((kind, inners)) = coll_info {
351            let element_ty = inners
352                .last()
353                .expect("Collection must have at least one inner type");
354            let partial_element_ty = if should_recurse {
355                is_recursive_field = true;
356                if let Some(Some(ref overridden)) = recurse_override {
357                    overridden.clone()
358                } else if let Type::Path(tp) = element_ty {
359                    let mut p_path = tp.path.clone();
360                    if let Some(seg) = p_path.segments.last_mut() {
361                        seg.ident = format_ident!("Partial{}", seg.ident);
362                        quote! { #p_path }
363                    } else {
364                        quote! { #element_ty }
365                    }
366                } else {
367                    quote! { #element_ty }
368                }
369            } else {
370                quote! { #element_ty }
371            };
372
373            let coll_ident = match kind {
374                CollectionKind::Vec => quote! { Vec },
375                CollectionKind::HashSet => quote! { HashSet },
376                CollectionKind::BTreeSet => quote! { BTreeSet },
377                CollectionKind::HashMap => quote! { HashMap },
378                CollectionKind::BTreeMap => quote! { BTreeMap },
379            };
380
381            let partial_coll_ty = if inners.len() == 2 {
382                let key_ty = inners[0];
383                quote! { #coll_ident<#key_ty, #partial_element_ty> }
384            } else {
385                quote! { #coll_ident<#partial_element_ty> }
386            };
387
388            current_field_ty = if field_unwrap {
389                partial_coll_ty.clone()
390            } else {
391                quote! { Option<#partial_coll_ty> }
392            };
393
394            // --- Apply Logic ---
395            let target_expr = if is_opt {
396                quote! { self.#field_name.get_or_insert_with(Default::default) }
397            } else {
398                quote! { self.#field_name }
399            };
400
401            let apply_stmt = if is_recursive_field {
402                let element_apply = match kind {
403                    CollectionKind::Vec | CollectionKind::HashSet | CollectionKind::BTreeSet => {
404                        let push_method = if kind == CollectionKind::Vec {
405                            quote! { push }
406                        } else {
407                            quote! { insert }
408                        };
409                        if !field_unwrap {
410                            if kind == CollectionKind::Vec {
411                                quote! {
412                                    let mut p_it = p.into_iter();
413                                    for target in #target_expr.iter_mut() {
414                                        if let Some(p_item) = p_it.next() {
415                                            matchmaker_partial::Apply::apply(target, p_item);
416                                        } else {
417                                            break;
418                                        }
419                                    }
420                                    for p_item in p_it {
421                                        let mut t = <#element_ty as Default>::default();
422                                        matchmaker_partial::Apply::apply(&mut t, p_item);
423                                        #target_expr.push(t);
424                                    }
425                                }
426                            } else {
427                                quote! {
428                                    for p_item in p {
429                                        let mut t = <#element_ty as Default>::default();
430                                        matchmaker_partial::Apply::apply(&mut t, p_item);
431                                        #target_expr.insert(t);
432                                    }
433                                }
434                            }
435                        } else {
436                            quote! {
437                                for p_item in partial.#field_name {
438                                    let mut t = <#element_ty as Default>::default();
439                                    matchmaker_partial::Apply::apply(&mut t, p_item);
440                                    #target_expr.#push_method(t);
441                                }
442                            }
443                        }
444                    }
445                    CollectionKind::HashMap | CollectionKind::BTreeMap => {
446                        if !field_unwrap {
447                            quote! {
448                                for (k, p_v) in p {
449                                    if let Some(v) = #target_expr.get_mut(&k) {
450                                        matchmaker_partial::Apply::apply(v, p_v);
451                                    } else {
452                                        let mut v = <#element_ty as Default>::default();
453                                        matchmaker_partial::Apply::apply(&mut v, p_v);
454                                        #target_expr.insert(k, v);
455                                    }
456                                }
457                            }
458                        } else {
459                            quote! {
460                                for (k, p_v) in partial.#field_name {
461                                    if let Some(v) = #target_expr.get_mut(&k) {
462                                        matchmaker_partial::Apply::apply(v, p_v);
463                                    } else {
464                                        let mut v = <#element_ty as Default>::default();
465                                        matchmaker_partial::Apply::apply(&mut v, p_v);
466                                        #target_expr.insert(k, v);
467                                    }
468                                }
469                            }
470                        }
471                    }
472                };
473
474                if !field_unwrap {
475                    quote! { if let Some(p) = partial.#field_name { #element_apply } }
476                } else {
477                    element_apply
478                }
479            } else if !field_unwrap {
480                let val = if is_opt {
481                    quote! { Some(p) }
482                } else {
483                    quote! { p }
484                };
485                quote! { if let Some(p) = partial.#field_name { self.#field_name = #val; } }
486            } else if kind == CollectionKind::HashMap || kind == CollectionKind::BTreeMap {
487                quote! {
488                    for (k, v) in partial.#field_name {
489                        #target_expr.insert(k, v);
490                    }
491                }
492            } else {
493                quote! { #target_expr.extend(partial.#field_name.into_iter()); }
494            };
495            apply_field_stmts.push(apply_stmt);
496
497            // --- Merge Logic ---
498            if !field_unwrap {
499                merge_field_stmts.push(quote! {
500                    if let Some(other_coll) = other.#field_name {
501                        self.#field_name.get_or_insert_with(Default::default).extend(other_coll.into_iter());
502                    }
503                });
504                clear_field_stmts.push(quote! { self.#field_name = None; });
505            } else {
506                merge_field_stmts
507                    .push(quote! { self.#field_name.extend(other.#field_name.into_iter()); });
508                clear_field_stmts.push(quote! { self.#field_name.clear(); });
509            }
510
511            // --- Set Logic ---
512            if let Some(field_ident) = &field.ident {
513                let field_name_str = field_ident.to_string();
514                let field_name_str = field_name_str.strip_prefix("r#").unwrap_or(&field_name_str);
515
516                let is_sequence = field_set.as_deref() == Some("sequence");
517                let set_logic = if is_sequence {
518                    let assignment = if !field_unwrap {
519                        quote! { self.#field_ident = Some(deserialized); }
520                    } else {
521                        quote! { self.#field_ident.extend(deserialized); }
522                    };
523                    quote! {
524                        let deserialized: #partial_coll_ty = matchmaker_partial::deserialize(val)?;
525                        #assignment
526                    }
527                } else {
528                    let target = if !field_unwrap {
529                        quote! { self.#field_ident.get_or_insert_with(Default::default) }
530                    } else {
531                        quote! { self.#field_ident }
532                    };
533
534                    let set_full_coll_logic = if !field_unwrap {
535                        quote! { self.#field_ident = Some(new_map); }
536                    } else {
537                        quote! { #target.extend(new_map.into_iter()); }
538                    };
539
540                    if inners.len() == 2 {
541                        let key_ty = inners[0];
542                        let val_ty = if should_recurse {
543                            quote! { #partial_element_ty }
544                        } else {
545                            quote! { #element_ty }
546                        };
547
548                        let descent_logic = if should_recurse {
549                            quote! {
550                                if rest.is_empty() {
551                                    let mut combined = vec![key_str.clone()];
552                                    combined.extend_from_slice(val);
553                                    let (key, value): (#key_ty, #val_ty) = matchmaker_partial::deserialize(&combined)?;
554                                    let _ = #target.insert(key, value);
555                                } else {
556                                    let key: #key_ty = matchmaker_partial::deserialize(&[key_str.clone()])?;
557                                    let item = #target.entry(key).or_insert_with(Default::default);
558                                    matchmaker_partial::Set::set(item, rest, val)?;
559                                }
560                            }
561                        } else {
562                            quote! {
563                                if rest.is_empty() {
564                                    let mut combined = vec![key_str.clone()];
565                                    combined.extend_from_slice(val);
566                                    let (key, value): (#key_ty, #val_ty) = matchmaker_partial::deserialize(&combined)?;
567                                    let _ = #target.insert(key, value);
568                                } else {
569                                    return Err(matchmaker_partial::PartialSetError::ExtraPaths(rest.to_vec()));
570                                }
571                            }
572                        };
573
574                        quote! {
575                            if let Some((key_str, rest)) = tail.split_first() {
576                                #descent_logic
577                            } else {
578                                let new_map: #partial_coll_ty = matchmaker_partial::deserialize(val)?;
579                                #set_full_coll_logic
580                            }
581                        }
582                    } else {
583                        let push_method = match kind {
584                            CollectionKind::Vec => quote! { push },
585                            _ => quote! { insert },
586                        };
587                        let item_ty = if should_recurse {
588                            quote! { #partial_element_ty }
589                        } else {
590                            quote! { #element_ty }
591                        };
592                        quote! {
593                            if let Some((_, _)) = tail.split_first() {
594                                return Err(matchmaker_partial::PartialSetError::ExtraPaths(tail.to_vec()));
595                            }
596                            let item: #item_ty = matchmaker_partial::deserialize(val)?;
597                            #target.#push_method(item);
598                        }
599                    }
600                };
601
602                set_field_arms.push(quote! {
603                    #field_name_str #(| #field_aliases)* => {
604                        #set_logic
605                        Ok(())
606                    }
607                });
608            }
609        } else {
610            // Leaf field handling
611            current_field_ty = if should_recurse {
612                is_recursive_field = true;
613                let p_ty = if let Some(Some(ref overridden)) = recurse_override {
614                    overridden.clone()
615                } else if let Type::Path(ty_path) = inner_ty {
616                    let mut p_path = ty_path.path.clone();
617                    if let Some(seg) = p_path.segments.last_mut() {
618                        seg.ident = format_ident!("Partial{}", seg.ident);
619                        quote! { #p_path }
620                    } else {
621                        quote! { #inner_ty }
622                    }
623                } else {
624                    quote! { #inner_ty }
625                };
626
627                if field_unwrap {
628                    p_ty
629                } else if is_opt {
630                    quote! { Option<#p_ty> }
631                } else {
632                    p_ty
633                }
634            } else if field_unwrap {
635                quote! { #inner_ty }
636            } else if is_opt {
637                quote! { #field_ty }
638            } else {
639                quote! { Option<#field_ty> }
640            };
641
642            if is_recursive_field {
643                if !field_unwrap && is_opt {
644                    apply_field_stmts.push(quote! {
645                        if let Some(p) = partial.#field_name {
646                            if let Some(ref mut v) = self.#field_name {
647                                matchmaker_partial::Apply::apply(v, p);
648                            } else {
649                                self.#field_name = Some(matchmaker_partial::from(p));
650                            }
651                        }
652                    });
653                    merge_field_stmts.push(quote! {
654                        match (&mut self.#field_name, other.#field_name) {
655                            (Some(s), Some(o)) => matchmaker_partial::Merge::merge(s, o),
656                            (t @ None, Some(o)) => *t = Some(o),
657                            _ => {}
658                        }
659                    });
660                    clear_field_stmts.push(quote! { self.#field_name = None; });
661                } else if field_unwrap && is_opt {
662                    // Unwrapped recursive, base is Option
663                    apply_field_stmts.push(quote! {
664                        if let Some(ref mut v) = self.#field_name {
665                            matchmaker_partial::Apply::apply(v, partial.#field_name);
666                        } else {
667                            self.#field_name = Some(matchmaker_partial::from(partial.#field_name));
668                        }
669                    });
670                    merge_field_stmts.push(quote! { matchmaker_partial::Merge::merge(&mut self.#field_name, other.#field_name); });
671                    clear_field_stmts
672                        .push(quote! { matchmaker_partial::Merge::clear(&mut self.#field_name); });
673                } else {
674                    apply_field_stmts.push(quote! { matchmaker_partial::Apply::apply(&mut self.#field_name, partial.#field_name); });
675                    merge_field_stmts.push(quote! { matchmaker_partial::Merge::merge(&mut self.#field_name, other.#field_name); });
676                    clear_field_stmts
677                        .push(quote! { matchmaker_partial::Merge::clear(&mut self.#field_name); });
678                }
679
680                if let Some(field_ident) = &field.ident {
681                    let field_name_str = field_ident.to_string();
682                    let field_name_str =
683                        field_name_str.strip_prefix("r#").unwrap_or(&field_name_str);
684
685                    let set_target = if is_opt {
686                        quote! { self.#field_ident.get_or_insert_with(Default::default) }
687                    } else {
688                        quote! { &mut self.#field_ident }
689                    };
690
691                    if is_flattened {
692                        flattened_field_targets.push(set_target);
693                    } else {
694                        set_field_arms.push(quote! {
695                            #field_name_str #(| #field_aliases)* => {
696                                if tail.is_empty() {
697                                    return Err(matchmaker_partial::PartialSetError::EarlyEnd(head.clone()));
698                                }
699                                matchmaker_partial::Set::set(#set_target, tail, val)
700                            }
701                        });
702                    }
703                }
704            } else {
705                if field_unwrap {
706                    if is_opt {
707                        apply_field_stmts
708                            .push(quote! { self.#field_name = Some(partial.#field_name); });
709                    } else {
710                        apply_field_stmts.push(quote! { self.#field_name = partial.#field_name; });
711                    }
712                } else if !is_opt {
713                    apply_field_stmts.push(
714                        quote! { if let Some(v) = partial.#field_name { self.#field_name = v; } },
715                    );
716                } else {
717                    apply_field_stmts.push(
718                        quote! { if let Some(v) = partial.#field_name { self.#field_name = Some(v); } },
719                    );
720                }
721                merge_field_stmts.push(
722                    quote! { if other.#field_name.is_some() { self.#field_name = other.#field_name; } },
723                );
724                clear_field_stmts.push(quote! { self.#field_name = None; });
725
726                if let Some(field_ident) = &field.ident {
727                    let field_name_str = field_ident.to_string();
728                    let field_name_str =
729                        field_name_str.strip_prefix("r#").unwrap_or(&field_name_str);
730
731                    // Determine deserialization logic
732                    let set_logic = if let Some(custom_func) = custom_deserializer {
733                        // Logic: custom_func expects a Deserializer.
734                        // If the field is Option<T>, deserialize_with returns Option<T>, so we assign directly.
735                        // If the field is T, deserialize_with returns T, so we must wrap in Some().
736                        let assignment = if is_opt {
737                            quote! { self.#field_name = result; }
738                        } else {
739                            quote! { self.#field_name = Some(result); }
740                        };
741
742                        quote! {
743                            let mut deserializer = matchmaker_partial::SimpleDeserializer::from_slice(val);
744                            let result = #custom_func(&mut deserializer)?;
745                            #assignment
746                        }
747                    } else {
748                        // Logic: generic deserialize helper returns the inner type T.
749                        // We always assign Some(T).
750                        let inner_ty = extract_inner_type_from_option(field_ty);
751                        quote! {
752                            let deserialized = matchmaker_partial::deserialize::<#inner_ty>(val)?;
753                            self.#field_name = Some(deserialized);
754                        }
755                    };
756
757                    set_field_arms.push(quote! {
758                        #field_name_str #(| #field_aliases)* => {
759                            if !tail.is_empty() {
760                                return Err(matchmaker_partial::PartialSetError::ExtraPaths(tail.to_vec()));
761                            }
762                            #set_logic
763                            Ok(())
764                        }
765                    });
766                }
767            }
768        }
769
770        find_idents_in_tokens(current_field_ty.clone(), &mut used_idents);
771        partial_field_defs
772            .push(quote! { #(#field_attrs_for_mirror)* #field_vis #field_name: #current_field_ty });
773    }
774
775    // --- 5. Nuanced Generics Handling ---
776    let mut partial_generics = input.generics.clone();
777    partial_generics.params = partial_generics
778        .params
779        .into_iter()
780        .filter(|param| match param {
781            syn::GenericParam::Type(t) => used_idents.contains(&t.ident),
782            syn::GenericParam::Lifetime(l) => used_idents.contains(&l.lifetime.ident),
783            syn::GenericParam::Const(c) => used_idents.contains(&c.ident),
784        })
785        .collect();
786
787    let (p_impl_generics, p_ty_generics, p_where_clause) = partial_generics.split_for_impl();
788
789    // --- 6. Optional Path Setter Implementation ---
790    let path_setter_impl = if generate_path_setter {
791        quote! {
792            impl #p_impl_generics matchmaker_partial::Set for #partial_name #p_ty_generics #p_where_clause {
793                fn set(&mut self, path: &[String], val: &[String]) -> Result<(), matchmaker_partial::PartialSetError> {
794                    let (head, tail) = path.split_first().ok_or_else(|| {
795                        matchmaker_partial::PartialSetError::EarlyEnd("root".to_string())
796                    })?;
797
798                    match head.as_str() {
799                        #(#set_field_arms)*
800                        _ => {
801                            #(
802                                match matchmaker_partial::Set::set(#flattened_field_targets, path, val) {
803                                    Err(matchmaker_partial::PartialSetError::Missing(_)) => {}
804                                    x => return x,
805                                }
806                            )*
807                            Err(matchmaker_partial::PartialSetError::Missing(head.clone()))
808                        }
809                    }
810                }
811            }
812        }
813    } else {
814        quote! {}
815    };
816
817    // --- 7. Conditional Merge/Clear auto-impl ---
818    let merge_impl = if enable_merge {
819        quote! {
820            impl #p_impl_generics matchmaker_partial::Merge for #partial_name #p_ty_generics #p_where_clause {
821                fn merge(&mut self, other: Self) {
822                    #(#merge_field_stmts)*
823                }
824
825                fn clear(&mut self) {
826                    #(#clear_field_stmts)*
827                }
828            }
829        }
830    } else {
831        quote! {}
832    };
833
834    let expanded = quote! {
835        #input
836
837        #(#final_attrs)*
838        #vis struct #partial_name #p_ty_generics #p_where_clause {
839            #(#partial_field_defs),*
840        }
841
842        impl #impl_generics matchmaker_partial::Apply for #name #ty_generics #where_clause {
843            type Partial = #partial_name #p_ty_generics;
844            fn apply(&mut self, partial: Self::Partial) {
845                #(#apply_field_stmts)*
846            }
847        }
848
849        #merge_impl
850
851        #path_setter_impl
852    };
853
854    TokenStream::from(expanded)
855}
856
857fn is_option(ty: &Type) -> bool {
858    if let Type::Path(tp) = ty {
859        tp.path.segments.last().is_some_and(|s| s.ident == "Option")
860    } else {
861        false
862    }
863}
864
865#[derive(PartialEq, Clone, Copy)]
866enum CollectionKind {
867    Vec,
868    HashSet,
869    BTreeSet,
870    HashMap,
871    BTreeMap,
872}
873
874fn get_collection_info(ty: &Type) -> Option<(CollectionKind, Vec<&Type>)> {
875    if let Type::Path(tp) = ty {
876        let last_seg = tp.path.segments.last()?;
877        let kind = if last_seg.ident == "Vec" {
878            CollectionKind::Vec
879        } else if last_seg.ident == "HashSet" {
880            CollectionKind::HashSet
881        } else if last_seg.ident == "BTreeSet" {
882            CollectionKind::BTreeSet
883        } else if last_seg.ident == "HashMap" {
884            CollectionKind::HashMap
885        } else if last_seg.ident == "BTreeMap" {
886            CollectionKind::BTreeMap
887        } else {
888            return None;
889        };
890
891        let mut inner_types = Vec::new();
892        if let PathArguments::AngleBracketed(args) = &last_seg.arguments {
893            for arg in &args.args {
894                if let GenericArgument::Type(inner_ty) = arg {
895                    inner_types.push(inner_ty);
896                }
897            }
898        }
899        Some((kind, inner_types))
900    } else {
901        None
902    }
903}
904
905/// Helper to get 'T' out of 'Option<T>' or return 'T' if it's not an Option.
906fn extract_inner_type_from_option(ty: &Type) -> &Type {
907    if let Type::Path(tp) = ty
908        && let Some(last_seg) = tp.path.segments.last()
909        && last_seg.ident == "Option"
910        && let PathArguments::AngleBracketed(args) = &last_seg.arguments
911        && let Some(GenericArgument::Type(inner)) = args.args.first()
912    {
913        return inner;
914    }
915    ty
916}
917
918fn find_idents_in_tokens(tokens: proc_macro2::TokenStream, set: &mut HashSet<proc_macro2::Ident>) {
919    for token in tokens {
920        match token {
921            proc_macro2::TokenTree::Ident(id) => {
922                set.insert(id);
923            }
924            proc_macro2::TokenTree::Group(g) => find_idents_in_tokens(g.stream(), set),
925            _ => {}
926        }
927    }
928}