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