matchmaker_partial_macros/
lib.rs1use 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; 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 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; } else if path.is_ident("derive") {
52 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 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 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 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; } 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 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 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 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 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 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; }
260
261 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; }
297 }
298
299 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 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 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 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 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 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 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 let set_logic = if let Some(custom_func) = custom_deserializer {
730 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 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 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 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 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
902fn 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}