A Rust procedural macro for pattern matching on syn::Path structures with binding capabilities.
syn-match provides a path_match! macro that allows you to pattern match against Rust path expressions (like std::collections::HashMap or Option<String>) with support for:
- Exact path matching
- Optional path segments
- Variable bindings
- Generic argument matching
- Wildcard patterns
- Multi-segment bindings
- Associated type matching
- Lifetime matching
use syn_match::path_match;
use syn::Path;
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
std::collections::HashMap => "found HashMap",
std::vec::Vec => "found Vec",
_ => "something else"
);
assert_eq!(result, "found HashMap");let path: Path = syn::parse_quote!(String);
let result = path_match!(&path,
String => "matched String",
_ => "no match"
);Use ? to make path segments optional:
let path1: Path = syn::parse_quote!(std::str::String);
let path2: Path = syn::parse_quote!(str::String);
let path3: Path = syn::parse_quote!(String);
for path in [&path1, &path2, &path3] {
let result = path_match!(path,
std?::str?::String => "matched",
_ => "no match"
);
assert_eq!(result, "matched"); // All match!
}Bind path segments to variables using $name:
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
std::$module::$ty => format!("{}::{}", module.ident, ty.ident),
_ => "no match".to_string()
);
assert_eq!(result, "collections::HashMap");Capture multiple segments using $name*:
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
$prefix*::HashMap => {
prefix.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("::")
},
_ => "no match".to_string()
);
assert_eq!(result, "std::collections");Capture segments that may or may not exist using $name?:
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
std::$middle?::HashMap => {
if let Some(seg) = middle {
format!("Found middle: {}", seg.ident)
} else {
"No middle segment".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "Found middle: collections");Match and bind generic type arguments:
let path: Path = syn::parse_quote!(Option<String>);
let result = path_match!(&path,
Option<$inner> => {
if let syn::GenericArgument::Type(syn::Type::Path(type_path)) = inner {
type_path.path.segments.last().unwrap().ident.to_string()
} else {
"not a path".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "String");Use ::$name to bind only path-type generic arguments:
let path: Path = syn::parse_quote!(Vec<String>);
let result = path_match!(&path,
Vec<::$ty> => ty.segments.last().unwrap().ident.to_string(),
_ => "no match".to_string()
);
assert_eq!(result, "String");
// Won't match non-path types:
let path2: Path = syn::parse_quote!(Vec<[u8]>);
let result2 = path_match!(&path2,
Vec<::$ty> => "matched path",
_ => "no match"
);
assert_eq!(result2, "no match");Match associated types in generic parameters:
let path: Path = syn::parse_quote!(Future<Output = String>);
let result = path_match!(&path,
Future<Output = $output> => {
if let syn::Type::Path(type_path) = output {
format!("Future output: {}", type_path.path.segments.last().unwrap().ident)
} else {
"not a path".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "Future output: String");Match and bind lifetimes using $'name:
let path: Path = syn::parse_quote!(Cow<'static, str>);
let result = path_match!(&path,
Cow<$'lt, str> => format!("lifetime: {}", lt.ident),
_ => "no match".to_string()
);
assert_eq!(result, "lifetime: static");Use '_ for lifetime wildcards:
let path: Path = syn::parse_quote!(Cow<'a, str>);
let result = path_match!(&path,
Cow<'_, str> => "matched any lifetime",
_ => "no match"
);
assert_eq!(result, "matched any lifetime");Note: Lifetime bindings do not support optional patterns ($'name?). Only regular lifetime bindings ($'name) and wildcards ('_) are supported.
Match slice types in generic arguments:
let path: Path = syn::parse_quote!(std::borrow::Cow<foo, [u8]>);
let result = path_match!(&path,
std?::borrow?::Cow<_, [$elem]> => elem.to_token_stream().to_string(),
_ => "no match".to_string()
);
assert_eq!(result, "u8");Use | to match multiple patterns in a single arm:
let path: Path = syn::parse_quote!(HashMap);
let result = path_match!(&path,
String | HashMap | Vec => "collection type",
_ => "other"
);
assert_eq!(result, "collection type");Use _ to match any remaining patterns:
let path: Path = syn::parse_quote!(Something::Unknown);
let result = path_match!(&path,
String => "string",
Vec => "vector",
_ => "wildcard match"
);
assert_eq!(result, "wildcard match");let path: Path = syn::parse_quote!(Result<std::collections::HashMap, io::Error>);
let result = path_match!(&path,
Result<std::$module::$ty, io::Error> => {
format!("Result with {}::{}", module.ident, ty.ident)
},
_ => "no match".to_string()
);
assert_eq!(result, "Result with collections::HashMap");let path: Path = syn::parse_quote!(Option<Outer<Other<More<Yet<String>>>>>);
let result = path_match!(&path,
Option<Outer<Other<More<Yet<$inner>>>>> => {
if let syn::GenericArgument::Type(syn::Type::Path(type_path)) = inner {
type_path.path.segments.last().unwrap().ident.to_string()
} else {
"not a path".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "String");let path: Path = syn::parse_quote!(Result<String, Error>);
let result = path_match!(&path,
$_package*::Result<$ok> | $_package*::Result<$ok, _> => {
if let syn::GenericArgument::Type(syn::Type::Path(p)) = ok {
format!("Result<{}, ?>", p.path.segments.last().unwrap().ident)
} else {
"?".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "Result<String, ?>");