Skip to main content

matchmaker_partial_macros/
lib.rs

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