Skip to main content

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(Vec<SegmentPattern>),
67  Wildcard,
68}
69
70/// ```
71/// foo::$ty::bar<baz> => {}
72/// ^^^  ^^^  ^^^^^^^^
73/// ```
74enum SegmentPattern {
75  /// ```
76  /// foo
77  /// ```
78  Required(SegmentMatcher),
79  /// ```
80  /// foo?
81  /// ```
82  Optional(SegmentMatcher),
83  /// ```
84  /// $ty
85  /// ```
86  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              //
241            } 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              //
276            } 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              //
321            } 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              //
346            } 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
432/// ```
433/// foo::bar<baz, foo> => {}
434///         ^^^^^^^^^^
435/// ```
436struct ArgumentPatterns(Vec<GenericArgumentPattern>);
437
438/// ```
439/// foo::bar<baz, foo> => {}
440///          ^^^  ^^^
441/// ```
442enum GenericArgumentPattern {
443  Argument(Box<SegmentMatcher>),
444  Wildcard(bool),
445  Binding(Span, bool, Ident),
446  AssociatedType(Span, Ident, AssociatedTypes),
447  PathPattern(Vec<SegmentPattern>),
448}
449
450/// ```
451/// foo::bar<Output = baz> => {}
452///                   ^^^
453/// ```
454enum AssociatedTypes {
455  /// ```
456  /// _
457  /// ```
458  Wildcard,
459  /// ```
460  /// $ty
461  /// ```
462  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        // wildcard
474        input.parse::<Token![_]>()?;
475        args.push(GenericArgumentPattern::Wildcard(false));
476      } else if input.peek(syn::Lifetime) {
477        // lifetime wildcard
478        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        // binding
489        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        // slice
493        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        // path coercion
520        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          // associated type
542          input.parse::<Token![=]>()?;
543
544          let value = if input.peek(Token![_]) {
545            // wildcard
546            input.parse::<Token![_]>()?;
547            AssociatedTypes::Wildcard
548          } else if input.peek(Token![$]) {
549            // binding
550            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          // path
572          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                                // TODO: Handle complex path patterns in nested generics
976                              }
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                            //
1024                          } 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                            //
1036                          } 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                        // For now, just check the associated type exists
1084                        // TODO: Could extend this to check the specific type
1085                        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, // TODO: Handle other segment patterns
1131                        }
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}