syn_match/
lib.rs

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
34/// ```
35/// foo::bar | hello::world => {}
36/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37/// ```
38struct 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
61/// ```
62/// foo::bar<baz> | hello::world => {}
63/// ^^^^^^^^^^^^^   ^^^^^^^^^^^^^
64/// ```
65enum PathPattern {
66  Path {
67    segments: Vec<SegmentPattern>,
68    fully_qualified: bool,
69  },
70  Wildcard,
71}
72
73/// ```
74/// foo::$ty::bar<baz> => {}
75/// ^^^  ^^^  ^^^^^^^^
76/// ```
77enum SegmentPattern {
78  /// ```
79  /// foo
80  /// ```
81  Required(SegmentMatcher),
82  /// ```
83  /// foo?
84  /// ```
85  Optional(SegmentMatcher),
86  /// ```
87  /// $ty
88  /// ```
89  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              //
244            } 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              //
279            } 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              //
324            } 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              //
349            } 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
447/// ```
448/// foo::bar<baz, foo> => {}
449///         ^^^^^^^^^^
450/// ```
451struct ArgumentPatterns(Vec<GenericArgumentPattern>);
452
453/// ```
454/// foo::bar<baz, foo> => {}
455///          ^^^  ^^^
456/// ```
457enum GenericArgumentPattern {
458  Argument(Box<SegmentMatcher>),
459  Wildcard(bool),
460  Binding(Span, bool, Ident),
461  AssociatedType(Span, Ident, AssociatedTypes),
462  PathPattern(Vec<SegmentPattern>),
463}
464
465/// ```
466/// foo::bar<Output = baz> => {}
467///                   ^^^
468/// ```
469enum AssociatedTypes {
470  /// ```
471  /// _
472  /// ```
473  Wildcard,
474  /// ```
475  /// $ty
476  /// ```
477  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        // wildcard
489        input.parse::<Token![_]>()?;
490        args.push(GenericArgumentPattern::Wildcard(false));
491      } else if input.peek(syn::Lifetime) {
492        // lifetime wildcard
493        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        // binding
504        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        // slice
508        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        // path coercion
535        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          // associated type
557          input.parse::<Token![=]>()?;
558
559          let value = if input.peek(Token![_]) {
560            // wildcard
561            input.parse::<Token![_]>()?;
562            AssociatedTypes::Wildcard
563          } else if input.peek(Token![$]) {
564            // binding
565            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          // path
587          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                                // TODO: Handle complex path patterns in nested generics
995                              }
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                            //
1043                          } 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                            //
1055                          } 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                        // For now, just check the associated type exists
1103                        // TODO: Could extend this to check the specific type
1104                        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, // TODO: Handle other segment patterns
1150                        }
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}