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