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