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("recurse") {
221                        if let Ok(value) = meta.value() {
222                            let s: LitStr = value.parse().unwrap();
223                            if s.value().is_empty() {
224                                recurse_override = Some(None);
225                            } else {
226                                let ty: Type = s.parse().unwrap();
227                                recurse_override = Some(Some(quote! { #ty }));
228                            }
229                        } else {
230                            // Enable recursion using default naming convention
231                            field_recurse = true;
232                        }
233                    } else if meta.path.is_ident("attr") {
234                        field_attrs_for_mirror.clear();
235                        if meta.input.peek(syn::token::Paren) {
236                            let content;
237                            syn::parenthesized!(content in meta.input);
238                            while !content.is_empty() {
239                                let inner_meta: Meta = content.parse()?;
240                                field_attrs_for_mirror.push(quote! { #[#inner_meta] });
241                                if content.peek(Token![,]) {
242                                    content.parse::<Token![,]>()?;
243                                }
244                            }
245                        }
246                    } else {
247                        return Err(meta.error(format!(
248                            "unknown partial attribute: {}",
249                            meta.path.to_token_stream()
250                        )));
251                    }
252                    Ok(())
253                });
254
255                if let Err(e) = res {
256                    field_errors.push(e);
257                }
258                return false; // Always drop #[partial]
259            }
260
261            // --- 4b. Handle #[serde] attributes ---
262            if attr.path().is_ident("serde") {
263                let mut drop_attr = false;
264                let _ = attr.parse_nested_meta(|meta| {
265                    if meta.path.is_ident("deserialize_with") {
266                        if let Ok(value) = meta.value() {
267                            if let Ok(s) = value.parse::<LitStr>() {
268                                custom_deserializer = s.parse::<Path>().ok();
269                                drop_attr = true;
270                            }
271                        }
272                    } else if meta.path.is_ident("with") {
273                        if let Ok(value) = meta.value() {
274                            if let Ok(s) = value.parse::<LitStr>() {
275                                if let Ok(mut p) = s.parse::<Path>() {
276                                    p.segments.push(format_ident!("deserialize").into());
277                                    custom_deserializer = Some(p);
278                                    drop_attr = true;
279                                }
280                            }
281                        }
282                    } else if meta.path.is_ident("alias") {
283                        if let Ok(value) = meta.value() {
284                            if let Ok(s) = value.parse::<LitStr>() {
285                                field_aliases.push(s.value());
286                            }
287                        }
288                    } else if meta.path.is_ident("flatten") {
289                        is_flattened = true;
290                    }
291                    Ok(())
292                });
293
294                if drop_attr {
295                    return false; // Drop the #[serde] attribute
296                }
297            }
298
299            // Keep the attribute and mirror it if it's not a #[partial]
300            field_attrs_for_mirror.push(attr.to_token_stream());
301            true
302        });
303
304        if let Some(err) = field_errors.first() {
305            return err.to_compile_error().into();
306        }
307
308        if skip_field {
309            continue;
310        }
311
312        if let Some(ref s) = field_set {
313            if s == "sequence" && recurse_override.is_some() {
314                return syn::Error::new(
315                    field.span(),
316                    "cannot use 'recurse' and 'set = \"sequence\"' on the same field",
317                )
318                .to_compile_error()
319                .into();
320            }
321        }
322
323        let is_opt = is_option(field_ty);
324        let inner_ty = if is_opt {
325            extract_inner_type_from_option(field_ty)
326        } else {
327            field_ty
328        };
329
330        let coll_info = get_collection_info(inner_ty);
331
332        // Determine if we should recurse
333        let mut should_recurse = (struct_recurse || field_recurse || recurse_override.is_some())
334            && !matches!(recurse_override, Some(None));
335
336        if let Some(ref s) = field_set {
337            if s == "sequence" {
338                should_recurse = false;
339            }
340        }
341
342        let current_field_ty: proc_macro2::TokenStream;
343        let mut is_recursive_field = false;
344
345        if let Some((kind, inners)) = coll_info {
346            let element_ty = inners
347                .last()
348                .expect("Collection must have at least one inner type");
349            let partial_element_ty = if should_recurse {
350                is_recursive_field = true;
351                if let Some(Some(ref overridden)) = recurse_override {
352                    overridden.clone()
353                } else if let Type::Path(tp) = element_ty {
354                    let mut p_path = tp.path.clone();
355                    if let Some(seg) = p_path.segments.last_mut() {
356                        seg.ident = format_ident!("Partial{}", seg.ident);
357                        quote! { #p_path }
358                    } else {
359                        quote! { #element_ty }
360                    }
361                } else {
362                    quote! { #element_ty }
363                }
364            } else {
365                quote! { #element_ty }
366            };
367
368            let coll_ident = match kind {
369                CollectionKind::Vec => quote! { Vec },
370                CollectionKind::HashSet => quote! { HashSet },
371                CollectionKind::BTreeSet => quote! { BTreeSet },
372                CollectionKind::HashMap => quote! { HashMap },
373                CollectionKind::BTreeMap => quote! { BTreeMap },
374            };
375
376            let partial_coll_ty = if inners.len() == 2 {
377                let key_ty = inners[0];
378                quote! { #coll_ident<#key_ty, #partial_element_ty> }
379            } else {
380                quote! { #coll_ident<#partial_element_ty> }
381            };
382
383            current_field_ty = if field_unwrap {
384                partial_coll_ty.clone()
385            } else {
386                quote! { Option<#partial_coll_ty> }
387            };
388
389            // --- Apply Logic ---
390            let target_expr = if is_opt {
391                quote! { self.#field_name.get_or_insert_with(Default::default) }
392            } else {
393                quote! { self.#field_name }
394            };
395
396            let apply_stmt = if is_recursive_field {
397                let element_apply = match kind {
398                    CollectionKind::Vec | CollectionKind::HashSet | CollectionKind::BTreeSet => {
399                        let push_method = if kind == CollectionKind::Vec {
400                            quote! { push }
401                        } else {
402                            quote! { insert }
403                        };
404                        if !field_unwrap {
405                            if kind == CollectionKind::Vec {
406                                quote! {
407                                    let mut p_it = p.into_iter();
408                                    for target in #target_expr.iter_mut() {
409                                        if let Some(p_item) = p_it.next() {
410                                            matchmaker_partial::Apply::apply(target, p_item);
411                                        } else {
412                                            break;
413                                        }
414                                    }
415                                    for p_item in p_it {
416                                        let mut t = <#element_ty as Default>::default();
417                                        matchmaker_partial::Apply::apply(&mut t, p_item);
418                                        #target_expr.push(t);
419                                    }
420                                }
421                            } else {
422                                quote! {
423                                    for p_item in p {
424                                        let mut t = <#element_ty as Default>::default();
425                                        matchmaker_partial::Apply::apply(&mut t, p_item);
426                                        #target_expr.insert(t);
427                                    }
428                                }
429                            }
430                        } else {
431                            quote! {
432                                for p_item in partial.#field_name {
433                                    let mut t = <#element_ty as Default>::default();
434                                    matchmaker_partial::Apply::apply(&mut t, p_item);
435                                    #target_expr.#push_method(t);
436                                }
437                            }
438                        }
439                    }
440                    CollectionKind::HashMap | CollectionKind::BTreeMap => {
441                        if !field_unwrap {
442                            quote! {
443                                for (k, p_v) in p {
444                                    if let Some(v) = #target_expr.get_mut(&k) {
445                                        matchmaker_partial::Apply::apply(v, p_v);
446                                    } else {
447                                        let mut v = <#element_ty as Default>::default();
448                                        matchmaker_partial::Apply::apply(&mut v, p_v);
449                                        #target_expr.insert(k, v);
450                                    }
451                                }
452                            }
453                        } else {
454                            quote! {
455                                for (k, p_v) in partial.#field_name {
456                                    if let Some(v) = #target_expr.get_mut(&k) {
457                                        matchmaker_partial::Apply::apply(v, p_v);
458                                    } else {
459                                        let mut v = <#element_ty as Default>::default();
460                                        matchmaker_partial::Apply::apply(&mut v, p_v);
461                                        #target_expr.insert(k, v);
462                                    }
463                                }
464                            }
465                        }
466                    }
467                };
468
469                if !field_unwrap {
470                    quote! { if let Some(p) = partial.#field_name { #element_apply } }
471                } else {
472                    element_apply
473                }
474            } else {
475                if !field_unwrap {
476                    let val = if is_opt {
477                        quote! { Some(p) }
478                    } else {
479                        quote! { p }
480                    };
481                    quote! { if let Some(p) = partial.#field_name { self.#field_name = #val; } }
482                } else if kind == CollectionKind::HashMap || kind == CollectionKind::BTreeMap {
483                    quote! {
484                        for (k, v) in partial.#field_name {
485                            #target_expr.insert(k, v);
486                        }
487                    }
488                } else {
489                    quote! { #target_expr.extend(partial.#field_name.into_iter()); }
490                }
491            };
492            apply_field_stmts.push(apply_stmt);
493
494            // --- Merge Logic ---
495            if !field_unwrap {
496                merge_field_stmts.push(quote! {
497                    if let Some(other_coll) = other.#field_name {
498                        self.#field_name.get_or_insert_with(Default::default).extend(other_coll.into_iter());
499                    }
500                });
501                clear_field_stmts.push(quote! { self.#field_name = None; });
502            } else {
503                merge_field_stmts
504                    .push(quote! { self.#field_name.extend(other.#field_name.into_iter()); });
505                clear_field_stmts.push(quote! { self.#field_name.clear(); });
506            }
507
508            // --- Set Logic ---
509            if let Some(field_ident) = &field.ident {
510                let field_name_str = field_ident.to_string();
511                let field_name_str = field_name_str.strip_prefix("r#").unwrap_or(&field_name_str);
512
513                let is_sequence = field_set.as_deref() == Some("sequence");
514                let set_logic = if is_sequence {
515                    let assignment = if !field_unwrap {
516                        quote! { self.#field_ident = Some(deserialized); }
517                    } else {
518                        quote! { self.#field_ident.extend(deserialized); }
519                    };
520                    quote! {
521                        let deserialized: #partial_coll_ty = matchmaker_partial::deserialize(val)?;
522                        #assignment
523                    }
524                } else {
525                    let target = if !field_unwrap {
526                        quote! { self.#field_ident.get_or_insert_with(Default::default) }
527                    } else {
528                        quote! { self.#field_ident }
529                    };
530
531                    let set_full_coll_logic = if !field_unwrap {
532                        quote! { self.#field_ident = Some(new_map); }
533                    } else {
534                        quote! { #target.extend(new_map.into_iter()); }
535                    };
536
537                    if inners.len() == 2 {
538                        let key_ty = inners[0];
539                        let val_ty = if should_recurse {
540                            quote! { #partial_element_ty }
541                        } else {
542                            quote! { #element_ty }
543                        };
544
545                        let descent_logic = if should_recurse {
546                            quote! {
547                                if rest.is_empty() {
548                                    let mut combined = vec![key_str.clone()];
549                                    combined.extend_from_slice(val);
550                                    let (key, value): (#key_ty, #val_ty) = matchmaker_partial::deserialize(&combined)?;
551                                    let _ = #target.insert(key, value);
552                                } else {
553                                    let key: #key_ty = matchmaker_partial::deserialize(&[key_str.clone()])?;
554                                    let item = #target.entry(key).or_insert_with(Default::default);
555                                    matchmaker_partial::Set::set(item, rest, val)?;
556                                }
557                            }
558                        } else {
559                            quote! {
560                                if rest.is_empty() {
561                                    let mut combined = vec![key_str.clone()];
562                                    combined.extend_from_slice(val);
563                                    let (key, value): (#key_ty, #val_ty) = matchmaker_partial::deserialize(&combined)?;
564                                    let _ = #target.insert(key, value);
565                                } else {
566                                    return Err(matchmaker_partial::PartialSetError::ExtraPaths(rest.to_vec()));
567                                }
568                            }
569                        };
570
571                        quote! {
572                            if let Some((key_str, rest)) = tail.split_first() {
573                                #descent_logic
574                            } else {
575                                let new_map: #partial_coll_ty = matchmaker_partial::deserialize(val)?;
576                                #set_full_coll_logic
577                            }
578                        }
579                    } else {
580                        let push_method = match kind {
581                            CollectionKind::Vec => quote! { push },
582                            _ => quote! { insert },
583                        };
584                        let item_ty = if should_recurse {
585                            quote! { #partial_element_ty }
586                        } else {
587                            quote! { #element_ty }
588                        };
589                        quote! {
590                            if let Some((_, _)) = tail.split_first() {
591                                return Err(matchmaker_partial::PartialSetError::ExtraPaths(tail.to_vec()));
592                            }
593                            let item: #item_ty = matchmaker_partial::deserialize(val)?;
594                            #target.#push_method(item);
595                        }
596                    }
597                };
598
599                set_field_arms.push(quote! {
600                    #field_name_str #(| #field_aliases)* => {
601                        #set_logic
602                        Ok(())
603                    }
604                });
605            }
606        } else {
607            // Leaf field handling
608            current_field_ty = if should_recurse {
609                is_recursive_field = true;
610                let p_ty = if let Some(Some(ref overridden)) = recurse_override {
611                    overridden.clone()
612                } else if let Type::Path(ty_path) = inner_ty {
613                    let mut p_path = ty_path.path.clone();
614                    if let Some(seg) = p_path.segments.last_mut() {
615                        seg.ident = format_ident!("Partial{}", seg.ident);
616                        quote! { #p_path }
617                    } else {
618                        quote! { #inner_ty }
619                    }
620                } else {
621                    quote! { #inner_ty }
622                };
623
624                if field_unwrap {
625                    p_ty
626                } else if is_opt {
627                    quote! { Option<#p_ty> }
628                } else {
629                    p_ty
630                }
631            } else if field_unwrap {
632                quote! { #inner_ty }
633            } else if is_opt {
634                quote! { #field_ty }
635            } else {
636                quote! { Option<#field_ty> }
637            };
638
639            if is_recursive_field {
640                if !field_unwrap && is_opt {
641                    apply_field_stmts.push(quote! {
642                        if let Some(p) = partial.#field_name {
643                            if let Some(ref mut v) = self.#field_name {
644                                matchmaker_partial::Apply::apply(v, p);
645                            } else {
646                                self.#field_name = Some(matchmaker_partial::from(p));
647                            }
648                        }
649                    });
650                    merge_field_stmts.push(quote! {
651                        match (&mut self.#field_name, other.#field_name) {
652                            (Some(s), Some(o)) => matchmaker_partial::Merge::merge(s, o),
653                            (t @ None, Some(o)) => *t = Some(o),
654                            _ => {}
655                        }
656                    });
657                    clear_field_stmts.push(quote! { self.#field_name = None; });
658                } else if field_unwrap && is_opt {
659                    // Unwrapped recursive, base is Option
660                    apply_field_stmts.push(quote! {
661                        if let Some(ref mut v) = self.#field_name {
662                            matchmaker_partial::Apply::apply(v, partial.#field_name);
663                        } else {
664                            self.#field_name = Some(matchmaker_partial::from(partial.#field_name));
665                        }
666                    });
667                    merge_field_stmts.push(quote! { matchmaker_partial::Merge::merge(&mut self.#field_name, other.#field_name); });
668                    clear_field_stmts
669                        .push(quote! { matchmaker_partial::Merge::clear(&mut self.#field_name); });
670                } else {
671                    apply_field_stmts.push(quote! { matchmaker_partial::Apply::apply(&mut self.#field_name, partial.#field_name); });
672                    merge_field_stmts.push(quote! { matchmaker_partial::Merge::merge(&mut self.#field_name, other.#field_name); });
673                    clear_field_stmts
674                        .push(quote! { matchmaker_partial::Merge::clear(&mut self.#field_name); });
675                }
676
677                if let Some(field_ident) = &field.ident {
678                    let field_name_str = field_ident.to_string();
679                    let field_name_str =
680                        field_name_str.strip_prefix("r#").unwrap_or(&field_name_str);
681
682                    let set_target = if is_opt {
683                        quote! { self.#field_ident.get_or_insert_with(Default::default) }
684                    } else {
685                        quote! { &mut self.#field_ident }
686                    };
687
688                    if is_flattened {
689                        flattened_field_targets.push(set_target);
690                    } else {
691                        set_field_arms.push(quote! {
692                            #field_name_str #(| #field_aliases)* => {
693                                if tail.is_empty() {
694                                    return Err(matchmaker_partial::PartialSetError::EarlyEnd(head.clone()));
695                                }
696                                matchmaker_partial::Set::set(#set_target, tail, val)
697                            }
698                        });
699                    }
700                }
701            } else {
702                if field_unwrap {
703                    if is_opt {
704                        apply_field_stmts
705                            .push(quote! { self.#field_name = Some(partial.#field_name); });
706                    } else {
707                        apply_field_stmts.push(quote! { self.#field_name = partial.#field_name; });
708                    }
709                } else if !is_opt {
710                    apply_field_stmts.push(
711                        quote! { if let Some(v) = partial.#field_name { self.#field_name = v; } },
712                    );
713                } else {
714                    apply_field_stmts.push(
715                        quote! { if let Some(v) = partial.#field_name { self.#field_name = Some(v); } },
716                    );
717                }
718                merge_field_stmts.push(
719                    quote! { if other.#field_name.is_some() { self.#field_name = other.#field_name; } },
720                );
721                clear_field_stmts.push(quote! { self.#field_name = None; });
722
723                if let Some(field_ident) = &field.ident {
724                    let field_name_str = field_ident.to_string();
725                    let field_name_str =
726                        field_name_str.strip_prefix("r#").unwrap_or(&field_name_str);
727
728                    // Determine deserialization logic
729                    let set_logic = if let Some(custom_func) = custom_deserializer {
730                        // Logic: custom_func expects a Deserializer.
731                        // If the field is Option<T>, deserialize_with returns Option<T>, so we assign directly.
732                        // If the field is T, deserialize_with returns T, so we must wrap in Some().
733                        let assignment = if is_opt {
734                            quote! { self.#field_name = result; }
735                        } else {
736                            quote! { self.#field_name = Some(result); }
737                        };
738
739                        quote! {
740                            let deserializer = matchmaker_partial::SimpleDeserializer::from_slice(val);
741                            let result = #custom_func(deserializer)?;
742                            #assignment
743                        }
744                    } else {
745                        // Logic: generic deserialize helper returns the inner type T.
746                        // We always assign Some(T).
747                        let inner_ty = extract_inner_type_from_option(field_ty);
748                        quote! {
749                            let deserialized = matchmaker_partial::deserialize::<#inner_ty>(val)?;
750                            self.#field_name = Some(deserialized);
751                        }
752                    };
753
754                    set_field_arms.push(quote! {
755                        #field_name_str #(| #field_aliases)* => {
756                            if !tail.is_empty() {
757                                return Err(matchmaker_partial::PartialSetError::ExtraPaths(tail.to_vec()));
758                            }
759                            #set_logic
760                            Ok(())
761                        }
762                    });
763                }
764            }
765        }
766
767        find_idents_in_tokens(current_field_ty.clone(), &mut used_idents);
768        partial_field_defs
769            .push(quote! { #(#field_attrs_for_mirror)* #field_vis #field_name: #current_field_ty });
770    }
771
772    // --- 5. Nuanced Generics Handling ---
773    let mut partial_generics = input.generics.clone();
774    partial_generics.params = partial_generics
775        .params
776        .into_iter()
777        .filter(|param| match param {
778            syn::GenericParam::Type(t) => used_idents.contains(&t.ident),
779            syn::GenericParam::Lifetime(l) => used_idents.contains(&l.lifetime.ident),
780            syn::GenericParam::Const(c) => used_idents.contains(&c.ident),
781        })
782        .collect();
783
784    let (p_impl_generics, p_ty_generics, p_where_clause) = partial_generics.split_for_impl();
785
786    // --- 6. Optional Path Setter Implementation ---
787    let path_setter_impl = if generate_path_setter {
788        quote! {
789            impl #p_impl_generics matchmaker_partial::Set for #partial_name #p_ty_generics #p_where_clause {
790                fn set(&mut self, path: &[String], val: &[String]) -> Result<(), matchmaker_partial::PartialSetError> {
791                    let (head, tail) = path.split_first().ok_or_else(|| {
792                        matchmaker_partial::PartialSetError::EarlyEnd("root".to_string())
793                    })?;
794
795                    match head.as_str() {
796                        #(#set_field_arms)*
797                        _ => {
798                            #(
799                                match matchmaker_partial::Set::set(#flattened_field_targets, path, val) {
800                                    Err(matchmaker_partial::PartialSetError::Missing(_)) => {}
801                                    x => return x,
802                                }
803                            )*
804                            Err(matchmaker_partial::PartialSetError::Missing(head.clone()))
805                        }
806                    }
807                }
808            }
809        }
810    } else {
811        quote! {}
812    };
813
814    // --- 7. Conditional Merge/Clear auto-impl ---
815    let merge_impl = if enable_merge {
816        quote! {
817            impl #p_impl_generics matchmaker_partial::Merge for #partial_name #p_ty_generics #p_where_clause {
818                fn merge(&mut self, other: Self) {
819                    #(#merge_field_stmts)*
820                }
821
822                fn clear(&mut self) {
823                    #(#clear_field_stmts)*
824                }
825            }
826        }
827    } else {
828        quote! {}
829    };
830
831    let expanded = quote! {
832        #input
833
834        #(#final_attrs)*
835        #vis struct #partial_name #p_ty_generics #p_where_clause {
836            #(#partial_field_defs),*
837        }
838
839        impl #impl_generics matchmaker_partial::Apply for #name #ty_generics #where_clause {
840            type Partial = #partial_name #p_ty_generics;
841            fn apply(&mut self, partial: Self::Partial) {
842                #(#apply_field_stmts)*
843            }
844        }
845
846        #merge_impl
847
848        #path_setter_impl
849    };
850
851    TokenStream::from(expanded)
852}
853
854fn is_option(ty: &Type) -> bool {
855    if let Type::Path(tp) = ty {
856        tp.path.segments.last().is_some_and(|s| s.ident == "Option")
857    } else {
858        false
859    }
860}
861
862#[derive(PartialEq, Clone, Copy)]
863enum CollectionKind {
864    Vec,
865    HashSet,
866    BTreeSet,
867    HashMap,
868    BTreeMap,
869}
870
871fn get_collection_info(ty: &Type) -> Option<(CollectionKind, Vec<&Type>)> {
872    if let Type::Path(tp) = ty {
873        let last_seg = tp.path.segments.last()?;
874        let kind = if last_seg.ident == "Vec" {
875            CollectionKind::Vec
876        } else if last_seg.ident == "HashSet" {
877            CollectionKind::HashSet
878        } else if last_seg.ident == "BTreeSet" {
879            CollectionKind::BTreeSet
880        } else if last_seg.ident == "HashMap" {
881            CollectionKind::HashMap
882        } else if last_seg.ident == "BTreeMap" {
883            CollectionKind::BTreeMap
884        } else {
885            return None;
886        };
887
888        let mut inner_types = Vec::new();
889        if let PathArguments::AngleBracketed(args) = &last_seg.arguments {
890            for arg in &args.args {
891                if let GenericArgument::Type(inner_ty) = arg {
892                    inner_types.push(inner_ty);
893                }
894            }
895        }
896        Some((kind, inner_types))
897    } else {
898        None
899    }
900}
901
902/// Helper to get 'T' out of 'Option<T>' or return 'T' if it's not an Option.
903fn extract_inner_type_from_option(ty: &Type) -> &Type {
904    if let Type::Path(tp) = ty {
905        if let Some(last_seg) = tp.path.segments.last() {
906            if last_seg.ident == "Option" {
907                if let PathArguments::AngleBracketed(args) = &last_seg.arguments {
908                    if let Some(GenericArgument::Type(inner)) = args.args.first() {
909                        return inner;
910                    }
911                }
912            }
913        }
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}