1extern crate proc_macro;
2
3use proc_macro2::Span;
4use proc_macro2::TokenStream;
5use quote::quote;
6use syn::Expr;
7use syn::Ident;
8use syn::Result;
9use syn::Token;
10use syn::parse::Parse;
11use syn::parse::ParseStream;
12use syn::parse_macro_input;
13use syn::punctuated::Punctuated;
14
15struct Body {
16 path: Expr,
17 arms: Vec<MatchArm>,
18}
19
20impl Parse for Body {
21 fn parse(input: ParseStream) -> Result<Self> {
22 let path = input.parse()?;
23 input.parse::<Token![,]>()?;
24
25 let mut arms = Vec::new();
26 while !input.is_empty() {
27 arms.push(input.parse()?);
28 }
29
30 Ok(Body { path, arms })
31 }
32}
33
34struct MatchArm {
39 patterns: Punctuated<PathPattern, Token![|]>,
40 _fat_arrow: Token![=>],
41 body: Expr,
42 _comma: Option<Token![,]>,
43}
44
45impl Parse for MatchArm {
46 fn parse(input: ParseStream) -> Result<Self> {
47 let patterns = Punctuated::parse_separated_nonempty(input)?;
48 let _fat_arrow = input.parse()?;
49 let body = input.parse()?;
50 let _comma = input.parse().ok();
51
52 Ok(MatchArm {
53 patterns,
54 _fat_arrow,
55 body,
56 _comma,
57 })
58 }
59}
60
61enum PathPattern {
66 Path {
67 segments: Vec<SegmentPattern>,
68 fully_qualified: bool,
69 },
70 Wildcard,
71}
72
73enum SegmentPattern {
78 Required(SegmentMatcher),
82 Optional(SegmentMatcher),
86 Binding {
90 span: Span,
91 lifetime: bool,
92 name: Ident,
93 mode: SegmentPatternBindingMode,
94 },
95}
96
97#[derive(Clone)]
98enum SegmentPatternBindingMode {
99 Normal,
100 Multi,
101 Optional,
102}
103
104struct SegmentMatcher {
105 ident: Ident,
106 args: Option<ArgumentPatterns>,
107}
108
109impl SegmentPattern {
110 fn parse_binding(input: ParseStream) -> Result<Self> {
111 input.parse::<Token![$]>()?;
112
113 let (lifetime, mut name) = if input.peek(syn::Lifetime) {
114 let lt: syn::Lifetime = input.parse()?;
115 (true, lt.ident)
116 } else {
117 (false, input.parse()?)
118 };
119
120 let mode = if input.peek(Token![*]) {
121 input.parse::<Token![*]>()?;
122 SegmentPatternBindingMode::Multi
123 } else if input.peek(Token![?]) {
124 input.parse::<Token![?]>()?;
125 SegmentPatternBindingMode::Optional
126 } else {
127 SegmentPatternBindingMode::Normal
128 };
129
130 let span = name.span();
131 name.set_span(Span::call_site());
132
133 Ok(SegmentPattern::Binding {
134 span,
135 lifetime,
136 name,
137 mode,
138 })
139 }
140
141 fn parse_required_or_optional(input: ParseStream) -> Result<Self> {
142 let ident: Ident = input.parse()?;
143
144 let args = if input.peek(Token![<]) {
145 Some(input.parse()?)
146 } else {
147 None
148 };
149
150 let optional = if input.peek(Token![?]) {
151 input.parse::<Token![?]>()?;
152 true
153 } else {
154 false
155 };
156
157 let matcher = SegmentMatcher { ident, args };
158
159 Ok(if optional {
160 SegmentPattern::Optional(matcher)
161 } else {
162 SegmentPattern::Required(matcher)
163 })
164 }
165}
166
167fn parse_path_segments(input: ParseStream) -> Result<Vec<SegmentPattern>> {
168 let mut segments = Vec::new();
169
170 loop {
171 if input.peek(Token![$]) {
172 segments.push(SegmentPattern::parse_binding(input)?);
173 } else {
174 segments.push(SegmentPattern::parse_required_or_optional(input)?);
175 }
176
177 if input.peek(Token![::]) {
178 input.parse::<Token![::]>()?;
179 } else {
180 break;
181 }
182 }
183
184 Ok(segments)
185}
186
187fn parse_generic_binding(input: ParseStream) -> Result<(Span, bool, Ident)> {
188 input.parse::<Token![$]>()?;
189
190 let (lifetime, mut name) = if input.peek(syn::Lifetime) {
191 let lt: syn::Lifetime = input.parse()?;
192 (true, lt.ident)
193 } else {
194 (false, input.parse()?)
195 };
196
197 let span = name.span();
198 name.set_span(Span::call_site());
199
200 Ok((span, lifetime, name))
201}
202
203#[derive(Clone)]
204enum GenericArgCheck {
205 Lifetime,
206 PathType,
207 AssocType(String),
208 Any,
209}
210
211#[derive(Clone)]
212enum BindingAction {
213 BindArg(Ident),
214 BindLifetime(Ident),
215 BindAssocType(Ident),
216 BindPath(Ident),
217 None,
218}
219
220fn generate_generic_arg_check(
221 check: &GenericArgCheck,
222 action: &BindingAction,
223 arg_idx: usize,
224 args_var: &str,
225) -> TokenStream {
226 match check {
227 GenericArgCheck::Lifetime => {
228 match action {
229 BindingAction::BindArg(name) => {
230 let args_ident = Ident::new(args_var, Span::call_site());
231 quote! {
232 if let Some(syn::GenericArgument::Lifetime(__arg)) = #args_ident.get(#arg_idx) {
233 #name = Some(__arg);
234 } else {
235 break false;
236 }
237 }
238 }
239 BindingAction::None => {
240 let args_ident = Ident::new(args_var, Span::call_site());
241 quote! {
242 if let Some(syn::GenericArgument::Lifetime(_)) = #args_ident.get(#arg_idx) {
243 } else {
245 break false;
246 }
247 }
248 }
249 _ => unreachable!("Invalid binding action for lifetime check"),
250 }
251 }
252 GenericArgCheck::PathType => {
253 match action {
254 BindingAction::BindArg(name) => {
255 let args_ident = Ident::new(args_var, Span::call_site());
256 quote! {
257 if let Some(__arg) = #args_ident.get(#arg_idx) {
258 #name = Some(__arg);
259 } else {
260 break false;
261 }
262 }
263 }
264 BindingAction::BindPath(name) => {
265 let args_ident = Ident::new(args_var, Span::call_site());
266 quote! {
267 if let Some(syn::GenericArgument::Type(syn::Type::Path(__type_path))) = #args_ident.get(#arg_idx) {
268 #name = Some(&__type_path.path);
269 } else {
270 break false;
271 }
272 }
273 }
274 BindingAction::None => {
275 let args_ident = Ident::new(args_var, Span::call_site());
276 quote! {
277 if let Some(syn::GenericArgument::Type(syn::Type::Path(_))) = #args_ident.get(#arg_idx) {
278 } else {
280 break false;
281 }
282 }
283 }
284 _ => unreachable!("Invalid binding action for path type check"),
285 }
286 }
287 GenericArgCheck::AssocType(ident_str) => {
288 match action {
289 BindingAction::BindAssocType(name) => {
290 let args_ident = Ident::new(args_var, Span::call_site());
291 quote! {
292 if let Some(syn::GenericArgument::AssocType(__assoc_type)) = #args_ident.get(#arg_idx)
293 && __assoc_type.ident == #ident_str
294 {
295 #name = Some(&__assoc_type.ty);
296 } else {
297 break false;
298 }
299 }
300 }
301 BindingAction::BindLifetime(name) => {
302 let args_ident = Ident::new(args_var, Span::call_site());
303 quote! {
304 if let Some(syn::GenericArgument::AssocType(__assoc_type)) = #args_ident.get(#arg_idx)
305 && __assoc_type.ident == #ident_str
306 && let syn::Type::Path(__assoc_path) = &__assoc_type.ty
307 && let Some(__assoc_seg) = __assoc_path.path.segments.first()
308 && let syn::PathArguments::AngleBracketed(__assoc_angle_args) = &__assoc_seg.arguments
309 && let Some(syn::GenericArgument::Lifetime(__lt)) = __assoc_angle_args.args.first()
310 {
311 #name = Some(__lt);
312 } else {
313 break false;
314 }
315 }
316 }
317 BindingAction::None => {
318 let args_ident = Ident::new(args_var, Span::call_site());
319 quote! {
320 if let Some(syn::GenericArgument::AssocType(__assoc_type)) = #args_ident.get(#arg_idx)
321 && __assoc_type.ident == #ident_str
322 {
323 } else {
325 break false;
326 }
327 }
328 }
329 _ => unreachable!("Invalid binding action for associated type check"),
330 }
331 }
332 GenericArgCheck::Any => {
333 match action {
334 BindingAction::BindArg(name) => {
335 let args_ident = Ident::new(args_var, Span::call_site());
336 quote! {
337 if let Some(__arg) = #args_ident.get(#arg_idx) {
338 #name = Some(__arg);
339 } else {
340 break false;
341 }
342 }
343 }
344 BindingAction::None => {
345 let args_ident = Ident::new(args_var, Span::call_site());
346 quote! {
347 if #args_ident.get(#arg_idx).is_some() {
348 } else {
350 break false;
351 }
352 }
353 }
354 _ => unreachable!("Invalid binding action for any check"),
355 }
356 }
357 }
358}
359
360fn generate_wildcard_check(
361 lifetime: bool,
362 arg_idx: usize,
363) -> Option<TokenStream> {
364 if lifetime {
365 Some(generate_generic_arg_check(
366 &GenericArgCheck::Lifetime,
367 &BindingAction::None,
368 arg_idx,
369 "__args",
370 ))
371 } else {
372 None
373 }
374}
375
376fn parse_path_pattern_in_generic(
377 input: ParseStream,
378 first_ident: Ident,
379) -> Result<Vec<SegmentPattern>> {
380 let mut path_segments = Vec::new();
381 path_segments.push(SegmentPattern::Required(SegmentMatcher {
382 ident: first_ident,
383 args: None,
384 }));
385
386 loop {
387 if input.peek(Token![$]) {
388 let (span, lifetime, name) = parse_generic_binding(input)?;
389 path_segments.push(SegmentPattern::Binding {
390 span,
391 lifetime,
392 name,
393 mode: SegmentPatternBindingMode::Normal,
394 });
395
396 if input.peek(Token![::]) {
397 input.parse::<Token![::]>()?;
398 } else {
399 break;
400 }
401 } else {
402 let ident: Ident = input.parse()?;
403
404 let args = if input.peek(Token![<]) {
405 Some(input.parse()?)
406 } else {
407 None
408 };
409
410 path_segments
411 .push(SegmentPattern::Required(SegmentMatcher { ident, args }));
412
413 if input.peek(Token![::]) {
414 input.parse::<Token![::]>()?;
415 } else {
416 break;
417 }
418 }
419 }
420
421 Ok(path_segments)
422}
423
424impl Parse for PathPattern {
425 fn parse(input: ParseStream) -> Result<Self> {
426 if input.peek(Token![_]) {
427 input.parse::<Token![_]>()?;
428 return Ok(PathPattern::Wildcard);
429 }
430
431 let fully_qualified = if input.peek(Token![::]) {
432 input.parse::<Token![::]>()?;
433 true
434 } else {
435 false
436 };
437
438 let segments = parse_path_segments(input)?;
439
440 Ok(PathPattern::Path {
441 segments,
442 fully_qualified,
443 })
444 }
445}
446
447struct ArgumentPatterns(Vec<GenericArgumentPattern>);
452
453enum GenericArgumentPattern {
458 Argument(Box<SegmentMatcher>),
459 Wildcard(bool),
460 Binding(Span, bool, Ident),
461 AssociatedType(Span, Ident, AssociatedTypes),
462 PathPattern(Vec<SegmentPattern>),
463}
464
465enum AssociatedTypes {
470 Wildcard,
474 Binding(Span, bool, Ident),
478 Argument(Box<SegmentMatcher>),
479}
480
481impl Parse for ArgumentPatterns {
482 fn parse(input: ParseStream) -> Result<Self> {
483 input.parse::<Token![<]>()?;
484 let mut args = Vec::new();
485
486 loop {
487 if input.peek(Token![_]) {
488 input.parse::<Token![_]>()?;
490 args.push(GenericArgumentPattern::Wildcard(false));
491 } else if input.peek(syn::Lifetime) {
492 let lt: syn::Lifetime = input.parse()?;
494 if lt.ident == "_" {
495 args.push(GenericArgumentPattern::Wildcard(true));
496 } else {
497 return Err(syn::Error::new(
498 lt.span(),
499 "expected '_ for lifetime wildcard",
500 ));
501 }
502 } else if input.peek(Token![$]) {
503 let (span, lifetime, name) = parse_generic_binding(input)?;
505 args.push(GenericArgumentPattern::Binding(span, lifetime, name));
506 } else if input.peek(syn::token::Bracket) {
507 let content;
509 syn::bracketed!(content in input);
510
511 let slice_ident = Ident::new("__slice__", input.span());
512 let mut element_args = Vec::new();
513
514 if content.peek(Token![$]) {
515 let (span, lifetime, name) = parse_generic_binding(&content)?;
516 element_args
517 .push(GenericArgumentPattern::Binding(span, lifetime, name));
518 } else {
519 let element_ident: Ident = content.parse()?;
520 let element_matcher = SegmentMatcher {
521 ident: element_ident,
522 args: None,
523 };
524 element_args
525 .push(GenericArgumentPattern::Argument(Box::new(element_matcher)));
526 }
527
528 let slice_matcher = SegmentMatcher {
529 ident: slice_ident,
530 args: Some(ArgumentPatterns(element_args)),
531 };
532 args.push(GenericArgumentPattern::Argument(Box::new(slice_matcher)));
533 } else if input.peek(Token![::]) {
534 input.parse::<Token![::]>()?;
536
537 if input.peek(Token![$]) {
538 let (span, lifetime, name) = parse_generic_binding(input)?;
539 let path_segments = vec![SegmentPattern::Binding {
540 span,
541 lifetime,
542 name,
543 mode: SegmentPatternBindingMode::Normal,
544 }];
545 args.push(GenericArgumentPattern::PathPattern(path_segments));
546 } else {
547 let first_ident: Ident = input.parse()?;
548 let path_segments =
549 parse_path_pattern_in_generic(input, first_ident)?;
550 args.push(GenericArgumentPattern::PathPattern(path_segments));
551 }
552 } else {
553 let first_ident: Ident = input.parse()?;
554
555 if input.peek(Token![=]) {
556 input.parse::<Token![=]>()?;
558
559 let value = if input.peek(Token![_]) {
560 input.parse::<Token![_]>()?;
562 AssociatedTypes::Wildcard
563 } else if input.peek(Token![$]) {
564 let (span, lifetime, name) = parse_generic_binding(input)?;
566 AssociatedTypes::Binding(span, lifetime, name)
567 } else {
568 let value_ident: Ident = input.parse()?;
569 let value_matcher = SegmentMatcher {
570 ident: value_ident,
571 args: if input.peek(Token![<]) {
572 Some(input.parse()?)
573 } else {
574 None
575 },
576 };
577 AssociatedTypes::Argument(Box::new(value_matcher))
578 };
579
580 args.push(GenericArgumentPattern::AssociatedType(
581 first_ident.span(),
582 first_ident,
583 value,
584 ));
585 } else if input.peek(Token![::]) {
586 input.parse::<Token![::]>()?;
588
589 let path_segments =
590 parse_path_pattern_in_generic(input, first_ident.clone())?;
591 args.push(GenericArgumentPattern::PathPattern(path_segments));
592 } else {
593 let matcher = SegmentMatcher {
594 ident: first_ident,
595 args: if input.peek(Token![<]) {
596 Some(input.parse()?)
597 } else {
598 None
599 },
600 };
601 args.push(GenericArgumentPattern::Argument(Box::new(matcher)));
602 }
603 }
604
605 if input.peek(Token![,]) {
606 input.parse::<Token![,]>()?;
607 } else {
608 break;
609 }
610 }
611
612 input.parse::<Token![>]>()?;
613 Ok(ArgumentPatterns(args))
614 }
615}
616
617struct PathMatcher {
618 path_expr: Expr,
619 fully_qualified_check: Option<TokenStream>,
620 length_check: TokenStream,
621 segment_checks: Vec<TokenStream>,
622 binding_names: Vec<(Span, bool, Ident, SegmentPatternBindingMode)>,
623}
624
625fn generate_path_matcher(
626 path_expr: &Expr,
627 patterns: &Punctuated<PathPattern, Token![|]>,
628) -> Vec<PathMatcher> {
629 let mut out = Vec::new();
630
631 for pattern in patterns {
632 match pattern {
633 PathPattern::Wildcard => {}
634 PathPattern::Path {
635 segments,
636 fully_qualified,
637 } => {
638 let mut binding_names = Vec::new();
639 for seg in segments {
640 match seg {
641 SegmentPattern::Binding {
642 span,
643 lifetime,
644 name,
645 mode,
646 } => {
647 binding_names.push((
648 *span,
649 *lifetime,
650 name.clone(),
651 mode.clone(),
652 ));
653 }
654 SegmentPattern::Required(matcher)
655 | SegmentPattern::Optional(matcher) => {
656 fn handle_segment_matcher(
657 binding_names: &mut Vec<(
658 Span,
659 bool,
660 Ident,
661 SegmentPatternBindingMode,
662 )>,
663 matcher: &SegmentMatcher,
664 ) {
665 if let Some(args) = &matcher.args {
666 for arg in &args.0 {
667 match arg {
668 GenericArgumentPattern::Binding(span, lifetime, name) => {
669 binding_names.push((
670 *span,
671 *lifetime,
672 name.clone(),
673 SegmentPatternBindingMode::Normal,
674 ));
675 }
676 GenericArgumentPattern::Argument(arg) => {
677 handle_segment_matcher(binding_names, arg);
678 }
679 GenericArgumentPattern::AssociatedType(_, _, value) => {
680 match value {
681 AssociatedTypes::Binding(span, lifetime, name) => {
682 binding_names.push((
683 *span,
684 *lifetime,
685 name.clone(),
686 SegmentPatternBindingMode::Normal,
687 ));
688 }
689 AssociatedTypes::Argument(arg) => {
690 handle_segment_matcher(binding_names, arg);
691 }
692 AssociatedTypes::Wildcard => {}
693 }
694 }
695 GenericArgumentPattern::Wildcard(_) => {}
696 GenericArgumentPattern::PathPattern(path_segments) => {
697 for seg in path_segments {
698 match seg {
699 SegmentPattern::Binding {
700 span,
701 lifetime,
702 name,
703 mode,
704 } => {
705 binding_names.push((
706 *span,
707 *lifetime,
708 name.clone(),
709 mode.clone(),
710 ));
711 }
712 _ => {}
713 }
714 }
715 }
716 }
717 }
718 }
719 }
720
721 handle_segment_matcher(&mut binding_names, matcher);
722 }
723 }
724 }
725
726 let mut required_segments = Vec::new();
727 let mut optional_segments = Vec::new();
728 let mut has_multi_binding = false;
729
730 for seg in segments {
731 match seg {
732 SegmentPattern::Required(matcher) => {
733 required_segments.push(matcher)
734 }
735 SegmentPattern::Optional(matcher) => {
736 optional_segments.push(matcher)
737 }
738 SegmentPattern::Binding { mode, .. } => {
739 if matches!(mode, SegmentPatternBindingMode::Multi) {
740 has_multi_binding = true;
741 }
742 }
743 }
744 }
745
746 let min_len = segments
747 .iter()
748 .filter(|s| {
749 matches!(
750 s,
751 SegmentPattern::Required(_)
752 | SegmentPattern::Binding {
753 mode: SegmentPatternBindingMode::Normal,
754 ..
755 }
756 )
757 })
758 .count();
759
760 let max_len = if has_multi_binding {
761 None
762 } else {
763 Some(segments.len())
764 };
765
766 let mut segment_checks = Vec::new();
767
768 let mut required_segment_names = std::collections::HashSet::new();
769
770 for seg in segments.iter() {
771 if let SegmentPattern::Required(matcher) = seg {
772 required_segment_names.insert(matcher.ident.to_string());
773 }
774 }
775
776 for (seg_idx, seg) in segments.iter().enumerate() {
777 match seg {
778 SegmentPattern::Binding { name, mode, .. } => {
779 match mode {
780 SegmentPatternBindingMode::Multi => {
781 let required_after = segments
782 .iter()
783 .skip(seg_idx)
784 .filter(|s| {
785 matches!(
786 s,
787 SegmentPattern::Required(_)
788 | SegmentPattern::Binding {
789 mode: SegmentPatternBindingMode::Normal,
790 ..
791 }
792 )
793 })
794 .count();
795
796 let check = quote! {
797 let __end_idx = __segments.len() - #required_after;
798 if __idx > __end_idx {
799 break false;
800 }
801 #name = Some(__segments.iter().skip(__idx).take(__end_idx - __idx).cloned().collect::<syn::punctuated::Punctuated<_, syn::Token![::]>>());
802 __idx = __end_idx;
803 };
804 segment_checks.push(check);
805 }
806 SegmentPatternBindingMode::Normal => {
807 let check = quote! {
808 if __idx >= __segments.len() {
809 break false;
810 }
811 #name = Some(&__segments[__idx]);
812 __idx += 1;
813 };
814 segment_checks.push(check);
815 }
816 SegmentPatternBindingMode::Optional => {
817 let required_after = segments
818 .iter()
819 .skip(seg_idx)
820 .filter(|s| {
821 matches!(
822 s,
823 SegmentPattern::Required(_)
824 | SegmentPattern::Binding {
825 mode: SegmentPatternBindingMode::Normal,
826 ..
827 }
828 )
829 })
830 .count();
831
832 let check = quote! {
833 let __min_needed = __idx + #required_after;
834 if __idx < __segments.len() && __segments.len() > __min_needed {
835 #name = Some(&__segments[__idx]);
836 __idx += 1;
837 } else {
838 #name = None;
839 }
840 };
841 segment_checks.push(check);
842 }
843 }
844 continue;
845 }
846 _ => {}
847 }
848
849 let (matcher, is_optional) = match seg {
850 SegmentPattern::Required(m) => (m, false),
851 SegmentPattern::Optional(m) => (m, true),
852 SegmentPattern::Binding { .. } => unreachable!(),
853 };
854
855 fn handle_segment_matcher(
856 matcher: &SegmentMatcher,
857 is_optional: bool,
858 required_names: &std::collections::HashSet<String>,
859 segments_after_current: &[SegmentPattern],
860 ) -> TokenStream {
861 let seg_ident = &matcher.ident;
862 let seg_ident_str = seg_ident.to_string();
863
864 let name_check = quote! {
865 __seg.ident == #seg_ident_str
866 };
867
868 let args_check = if let Some(ArgumentPatterns(arg_patterns)) =
869 &matcher.args
870 {
871 let mut arg_checks = Vec::new();
872 for (arg_idx, arg_pattern) in arg_patterns.iter().enumerate() {
873 match arg_pattern {
874 GenericArgumentPattern::Wildcard(lifetime) => {
875 if let Some(check) =
876 generate_wildcard_check(*lifetime, arg_idx)
877 {
878 arg_checks.push(check);
879 }
880 }
881 GenericArgumentPattern::Argument(segment_matcher) => {
882 fn generate_nested_arg_check(
883 segment_matcher: &SegmentMatcher,
884 arg_var: &str,
885 depth: usize,
886 ) -> TokenStream {
887 let arg_ident = &segment_matcher.ident;
888 let arg_ident_str = arg_ident.to_string();
889 let arg_var_ident =
890 Ident::new(arg_var, Span::call_site());
891
892 if let Some(ArgumentPatterns(nested_arg_patterns)) =
893 &segment_matcher.args
894 {
895 let mut nested_arg_checks = Vec::new();
896 for (nested_arg_idx, nested_arg_pattern) in
897 nested_arg_patterns.iter().enumerate()
898 {
899 match nested_arg_pattern {
900 GenericArgumentPattern::Binding(
901 _span,
902 lifetime,
903 name,
904 ) => {
905 let check = if *lifetime {
906 GenericArgCheck::Lifetime
907 } else {
908 GenericArgCheck::Any
909 };
910 let action = BindingAction::BindArg(name.clone());
911
912 nested_arg_checks.push(
913 generate_generic_arg_check(
914 &check,
915 &action,
916 nested_arg_idx,
917 "__nested_args",
918 ),
919 );
920 }
921 GenericArgumentPattern::Wildcard(_) => {}
922 GenericArgumentPattern::Argument(
923 inner_segment_matcher,
924 ) => {
925 let nested_arg_var =
926 format!("__nested_arg_{}", depth);
927 let inner_check = generate_nested_arg_check(
928 inner_segment_matcher,
929 &nested_arg_var,
930 depth + 1,
931 );
932 let nested_arg_var_ident =
933 Ident::new(&nested_arg_var, Span::call_site());
934 nested_arg_checks.push(quote! {
935 if let Some(#nested_arg_var_ident) = __nested_args.get(#nested_arg_idx) {
936 #inner_check
937 } else {
938 break false;
939 }
940 });
941 }
942 GenericArgumentPattern::AssociatedType(
943 _span,
944 ident,
945 value,
946 ) => {
947 let ident_str = ident.to_string();
948 let check =
949 GenericArgCheck::AssocType(ident_str.clone());
950
951 let action = match value {
952 AssociatedTypes::Wildcard => {
953 BindingAction::None
954 }
955 AssociatedTypes::Binding(_, lifetime, name) => {
956 if *lifetime {
957 BindingAction::BindLifetime(name.clone())
958 } else {
959 BindingAction::BindAssocType(name.clone())
960 }
961 }
962 AssociatedTypes::Argument(_type_matcher) => {
963 BindingAction::None
964 }
965 };
966
967 nested_arg_checks.push(
968 generate_generic_arg_check(
969 &check,
970 &action,
971 nested_arg_idx,
972 "__nested_args",
973 ),
974 );
975 }
976 GenericArgumentPattern::PathPattern(
977 path_segments,
978 ) => {
979 if path_segments.len() == 1
980 && let SegmentPattern::Binding { name, .. } =
981 &path_segments[0]
982 {
983 let action =
984 BindingAction::BindPath(name.clone());
985 nested_arg_checks.push(
986 generate_generic_arg_check(
987 &GenericArgCheck::PathType,
988 &action,
989 nested_arg_idx,
990 "__nested_args",
991 ),
992 );
993 } else {
994 }
996 }
997 }
998 }
999
1000 let nested_arg_count = nested_arg_patterns.len();
1001 if arg_ident_str == "__slice__" {
1002 let slice_checks = nested_arg_patterns
1003 .iter()
1004 .filter_map(|pattern| match pattern {
1005 GenericArgumentPattern::Binding(
1006 _span,
1007 _lifetime,
1008 name,
1009 ) => Some(quote! {
1010 #name = Some(__slice_type.elem.as_ref());
1011 }),
1012 _ => None,
1013 });
1014 quote! {
1015 if let syn::GenericArgument::Type(syn::Type::Slice(__slice_type)) = #arg_var_ident {
1016 #(#slice_checks)*
1017 } else {
1018 break false;
1019 }
1020 }
1021 } else {
1022 quote! {
1023 if
1024 let syn::GenericArgument::Type(syn::Type::Path(__nested_type_path)) = #arg_var_ident
1025 && let Some(__nested_seg) = __nested_type_path.path.segments.last()
1026 && __nested_seg.ident == #arg_ident_str
1027 && let syn::PathArguments::AngleBracketed(__nested_angle_args) = &__nested_seg.arguments
1028 {
1029 let __nested_args = &__nested_angle_args.args;
1030 if __nested_args.len() != #nested_arg_count {
1031 break false;
1032 }
1033 #(#nested_arg_checks)*
1034 } else {
1035 break false;
1036 }
1037 }
1038 }
1039 } else if arg_ident_str == "__slice__" {
1040 quote! {
1041 if let syn::GenericArgument::Type(syn::Type::Slice(__slice_type)) = #arg_var_ident {
1042 } else {
1044 break false;
1045 }
1046 }
1047 } else {
1048 quote! {
1049 if
1050 let syn::GenericArgument::Type(syn::Type::Path(__nested_type_path)) = #arg_var_ident
1051 && let Some(__nested_seg) = __nested_type_path.path.segments.last()
1052 && __nested_seg.ident == #arg_ident_str
1053 {
1054 } else {
1056 break false;
1057 }
1058 }
1059 }
1060 }
1061
1062 let nested_check =
1063 generate_nested_arg_check(segment_matcher, "__arg", 0);
1064
1065 arg_checks.push(quote! {
1066 if let Some(__arg) = __args.get(#arg_idx) {
1067 #nested_check
1068 } else {
1069 break false;
1070 }
1071 });
1072 }
1073 GenericArgumentPattern::Binding(_, lifetime, name) => {
1074 let check = if *lifetime {
1075 GenericArgCheck::Lifetime
1076 } else {
1077 GenericArgCheck::Any
1078 };
1079 let action = BindingAction::BindArg(name.clone());
1080 arg_checks.push(generate_generic_arg_check(
1081 &check, &action, arg_idx, "__args",
1082 ));
1083 }
1084 GenericArgumentPattern::AssociatedType(
1085 _span,
1086 ident,
1087 value,
1088 ) => {
1089 let ident_str = ident.to_string();
1090 let check = GenericArgCheck::AssocType(ident_str.clone());
1091
1092 let action = match value {
1093 AssociatedTypes::Wildcard => BindingAction::None,
1094 AssociatedTypes::Binding(_, lifetime, name) => {
1095 if *lifetime {
1096 BindingAction::BindLifetime(name.clone())
1097 } else {
1098 BindingAction::BindAssocType(name.clone())
1099 }
1100 }
1101 AssociatedTypes::Argument(_type_matcher) => {
1102 BindingAction::None
1105 }
1106 };
1107
1108 arg_checks.push(generate_generic_arg_check(
1109 &check, &action, arg_idx, "__args",
1110 ));
1111 }
1112 GenericArgumentPattern::PathPattern(path_segments) => {
1113 if path_segments.len() == 1
1114 && let SegmentPattern::Binding { name, .. } =
1115 &path_segments[0]
1116 {
1117 let action = BindingAction::BindPath(name.clone());
1118 arg_checks.push(generate_generic_arg_check(
1119 &GenericArgCheck::PathType,
1120 &action,
1121 arg_idx,
1122 "__args",
1123 ));
1124 } else {
1125 let path_checks: Vec<_> = path_segments.iter().filter_map(|seg| {
1126 match seg {
1127 SegmentPattern::Required(matcher) => {
1128 let ident = &matcher.ident;
1129 let ident_str = ident.to_string();
1130 Some(quote! {
1131 if __path_seg_idx >= __path_segments.len() {
1132 break false;
1133 }
1134 if __path_segments[__path_seg_idx].ident != #ident_str {
1135 break false;
1136 }
1137 __path_seg_idx += 1;
1138 })
1139 }
1140 SegmentPattern::Binding { name, .. } => {
1141 Some(quote! {
1142 if __path_seg_idx >= __path_segments.len() {
1143 break false;
1144 }
1145 #name = Some(&__path_segments[__path_seg_idx]);
1146 __path_seg_idx += 1;
1147 })
1148 }
1149 _ => None, }
1151 }).collect();
1152
1153 arg_checks.push(quote! {
1154 if let Some(syn::GenericArgument::Type(syn::Type::Path(__type_path))) = __args.get(#arg_idx) {
1155 let __path_segments = &__type_path.path.segments;
1156 let mut __path_seg_idx = 0;
1157 let mut __path_matched = true;
1158
1159 __path_matched = loop {
1160 #(#path_checks)*
1161 break __path_matched;
1162 };
1163
1164 if !__path_matched || __path_seg_idx != __path_segments.len() {
1165 break false;
1166 }
1167 } else {
1168 break false;
1169 }
1170 });
1171 }
1172 }
1173 }
1174 }
1175
1176 let arg_count = arg_patterns.len();
1177 Some(quote! {
1178 if let syn::PathArguments::AngleBracketed(__angle_args) = &__seg.arguments {
1179 let __args = &__angle_args.args;
1180 if __args.len() != #arg_count {
1181 break false;
1182 }
1183 #(#arg_checks)*
1184 } else {
1185 break false;
1186 }
1187 })
1188 } else {
1189 None
1190 };
1191
1192 if is_optional {
1193 let seg_name = seg_ident_str.clone();
1194 let has_named_conflict = required_names.contains(&seg_name);
1195
1196 let remaining_required = segments_after_current
1197 .iter()
1198 .filter(|s| {
1199 matches!(
1200 s,
1201 SegmentPattern::Required(_)
1202 | SegmentPattern::Binding {
1203 mode: SegmentPatternBindingMode::Normal,
1204 ..
1205 }
1206 )
1207 })
1208 .count();
1209
1210 if has_named_conflict || remaining_required > 0 {
1211 quote! {
1212 let __remaining_segments = __segments.len() - __idx;
1213 let __remaining_required = #remaining_required;
1214
1215 if __remaining_segments > __remaining_required && __idx < __segments.len() {
1216 let __seg = &__segments[__idx];
1217 if #name_check {
1218 #args_check
1219 __idx += 1;
1220 }
1221 }
1222 }
1223 } else {
1224 quote! {
1225 if __idx < __segments.len() {
1226 let __seg = &__segments[__idx];
1227 if #name_check {
1228 #args_check
1229 __idx += 1;
1230 }
1231 }
1232 }
1233 }
1234 } else {
1235 quote! {
1236 if __idx >= __segments.len() {
1237 break false;
1238 }
1239 let __seg = &__segments[__idx];
1240 if !(#name_check) {
1241 break false;
1242 }
1243 #args_check
1244 __idx += 1;
1245 }
1246 }
1247 }
1248
1249 segment_checks.push(handle_segment_matcher(
1250 matcher,
1251 is_optional,
1252 &required_segment_names,
1253 &segments[seg_idx + 1..],
1254 ));
1255 }
1256
1257 let fully_qualified_check = if *fully_qualified {
1258 Some(quote! {
1259 if !__path.leading_colon.is_some() {
1260 __matched = false;
1261 }
1262 })
1263 } else {
1264 None
1265 };
1266
1267 let length_check = match max_len {
1268 Some(max) if min_len == max => {
1269 quote! {
1270 if __segments.len() != #min_len {
1271 __matched = false;
1272 }
1273 }
1274 }
1275 Some(max) => {
1276 quote! {
1277 if __segments.len() < #min_len || __segments.len() > #max {
1278 __matched = false;
1279 }
1280 }
1281 }
1282 None => {
1283 quote! {
1284 if __segments.len() < #min_len {
1285 __matched = false;
1286 }
1287 }
1288 }
1289 };
1290
1291 out.push(PathMatcher {
1292 path_expr: path_expr.clone(),
1293 fully_qualified_check,
1294 length_check,
1295 segment_checks,
1296 binding_names,
1297 })
1298 }
1299 }
1300 }
1301
1302 out
1303}
1304
1305#[proc_macro]
1306pub fn path_match(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1307 let Body { path, arms } = parse_macro_input!(input as Body);
1308
1309 let wildcard_arm = arms.last().filter(|arm| {
1310 arm
1311 .patterns
1312 .first()
1313 .is_some_and(|pattern| matches!(pattern, PathPattern::Wildcard))
1314 });
1315
1316 if wildcard_arm.is_none() {
1317 return syn::Error::new(
1318 Span::call_site(),
1319 "path_match! requires a wildcard arm `_ => ...` as the last arm",
1320 )
1321 .to_compile_error()
1322 .into();
1323 }
1324
1325 let wildcard_body = &wildcard_arm.unwrap().body;
1326 let non_wildcard_arms = &arms[..arms.len() - 1];
1327
1328 for arm in non_wildcard_arms {
1329 if arm
1330 .patterns
1331 .iter()
1332 .any(|pattern| matches!(pattern, PathPattern::Wildcard))
1333 {
1334 return syn::Error::new(
1335 Span::call_site(),
1336 "wildcard pattern `_` must be the last arm",
1337 )
1338 .to_compile_error()
1339 .into();
1340 }
1341 }
1342
1343 let mut match_checks = Vec::new();
1344
1345 for arm in non_wildcard_arms {
1346 let path_matchers = generate_path_matcher(&path, &arm.patterns);
1347 for PathMatcher {
1348 path_expr,
1349 fully_qualified_check,
1350 length_check,
1351 segment_checks,
1352 binding_names,
1353 } in path_matchers
1354 {
1355 match_checks.push((
1356 path_expr,
1357 fully_qualified_check,
1358 length_check,
1359 segment_checks,
1360 binding_names,
1361 &arm.body,
1362 ));
1363 }
1364 }
1365
1366 let arms_code = match_checks.into_iter().map(
1367 |(path_expr, fq_check, len_check, seg_checks, binding_names, body)| {
1368 let spanless_binding_names = binding_names
1369 .iter()
1370 .map(|(_, _, name, _)| name.clone())
1371 .collect::<Vec<_>>();
1372 let binding_extractions =
1373 binding_names
1374 .into_iter()
1375 .map(|(span, _lifetime, name, mode)| {
1376 let mut name_in_some = name.clone();
1377 name_in_some.set_span(span);
1378
1379 match mode {
1380 SegmentPatternBindingMode::Optional => {
1381 quote! {
1382 let #name_in_some = #name;
1383 }
1384 }
1385 _ => {
1386 quote! {
1387 let #name_in_some = #name.unwrap();
1388 }
1389 }
1390 }
1391 });
1392
1393 quote! {
1394 {
1395 let __path = #path_expr;
1396 let __segments = &__path.segments;
1397 let mut __idx = 0;
1398 let mut __matched = true;
1399
1400 #(let mut #spanless_binding_names: Option<_> = None;)*
1401
1402 #fq_check
1403
1404 if __matched {
1405 #len_check
1406 }
1407
1408 if __matched {
1409 __matched = 'outer: loop {
1410 #(#seg_checks)*
1411 break __matched;
1412 }
1413 }
1414
1415 if __matched && __idx != __segments.len() {
1416 __matched = false;
1417 }
1418
1419 if __matched {
1420 #(#binding_extractions)*
1421 return #body;
1422 }
1423 }
1424 }
1425 },
1426 );
1427
1428 let expanded = quote! {
1429 (|| {
1430 #(#arms_code)*
1431
1432 #wildcard_body
1433 })()
1434 };
1435
1436 proc_macro::TokenStream::from(expanded)
1437}