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