syn-match 0.3.1

a macro for matching on syn paths
Documentation
# syn-match

A Rust procedural macro for pattern matching on `syn::Path` structures with binding capabilities.

## Overview

`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

## Basic Usage

```rust
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");
```

## Pattern Syntax

### 1. Exact Path Matching

```rust
let path: Path = syn::parse_quote!(String);
let result = path_match!(&path,
    String => "matched String",
    _ => "no match"
);
```

### 2. Optional Segments

Use `?` to make path segments optional:

```rust
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!
}
```

### 3. Variable Bindings

Bind path segments to variables using `$name`:

```rust
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");
```

### 4. Multi-Segment Bindings

Capture multiple segments using `$name*`:

```rust
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");
```

### 5. Optional Bindings

Capture segments that may or may not exist using `$name?`:

```rust
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");
```

### 6. Generic Arguments

Match and bind generic type arguments:

```rust
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");
```

### 7. Path Coercion in Generics

Use `::$name` to bind only path-type generic arguments:

```rust
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");
```

### 8. Associated Types

Match associated types in generic parameters:

```rust
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");
```

### 9. Lifetime Matching

Match and bind lifetimes using `$'name`:

```rust
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:

```rust
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.

### 10. Slice Type Matching

Match slice types in generic arguments:

```rust
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");
```

### 11. Multiple Patterns

Use `|` to match multiple patterns in a single arm:

```rust
let path: Path = syn::parse_quote!(HashMap);
let result = path_match!(&path,
    String | HashMap | Vec => "collection type",
    _ => "other"
);
assert_eq!(result, "collection type");
```

### 12. Wildcards

Use `_` to match any remaining patterns:

```rust
let path: Path = syn::parse_quote!(Something::Unknown);
let result = path_match!(&path,
    String => "string",
    Vec => "vector", 
    _ => "wildcard match"
);
assert_eq!(result, "wildcard match");
```

## Advanced Examples

### Complex Generic Matching

```rust
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");
```

### Nested Generic Patterns

```rust
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");
```

### Multiple Bindings with Wildcards

```rust
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, ?>");
```