Skip to main content

assert_struct_macros/
lib.rs

1//! Procedural macro implementation for assert-struct.
2//!
3//! This crate provides the procedural macro implementation for the `assert-struct` crate.
4//! Users should use the main `assert-struct` crate which re-exports this macro.
5//!
6//! # Architecture Overview
7//!
8//! The macro transformation happens in three phases:
9//!
10//! 1. **Parse** (`parse.rs`): Tokenize the macro input into a Pattern AST
11//! 2. **Expand** (`expand.rs`): Transform patterns into assertion code
12//! 3. **Execute**: Generated code runs the actual assertions
13//!
14//! # Key Design Decisions
15//!
16//! - **Pattern enum**: Unified abstraction for all pattern types (struct, tuple, slice, etc.)
17//! - **Explicit patterns**: Require explicit operators (e.g., `Some(== my_var)` not `Some(my_var)`)
18//! - **Dual-path optimization**: String literal regexes compile at expansion time
19//! - **Native Rust syntax**: Use match expressions for ranges, slices, and enums
20//!
21//! See the main `assert-struct` crate for documentation and examples.
22
23use proc_macro::TokenStream;
24
25mod expand;
26mod parse;
27mod pattern;
28
29use pattern::Pattern;
30
31// Root-level struct that tracks the assertion
32struct AssertStruct {
33    value: syn::Expr,
34    pattern: Pattern,
35}
36
37/// Structural assertion macro for testing complex data structures.
38///
39/// This procedural macro generates efficient runtime assertions that check structural patterns
40/// against actual values, providing detailed error messages when assertions fail. The macro
41/// transforms pattern-based syntax into optimized comparison code at compile time.
42///
43/// See the [crate-level documentation](crate) for comprehensive guides and learning examples.
44/// This documentation serves as a complete specification reference.
45///
46/// # Syntax Specification
47///
48/// ```text
49/// assert_struct!(expression, TypePattern);
50///
51/// TypePattern ::= TypeName '{' FieldPatternList '}'
52///              | '_' '{' FieldPatternList '}'  // Wildcard pattern
53/// FieldPatternList ::= (FieldPattern ',')* ('..')?
54/// FieldPattern ::= FieldName ':' Pattern
55///              | FieldName FieldOperation ':' Pattern
56/// FieldOperation ::= ('*')+ | ('.' Identifier '(' ArgumentList? ')')
57/// Pattern ::= Value | ComparisonPattern | RangePattern | RegexPattern
58///          | EnumPattern | TuplePattern | SlicePattern | NestedPattern
59/// ```
60///
61/// # Complete Pattern Reference
62///
63/// ## Basic Value Patterns
64///
65/// | Pattern | Syntax | Description | Constraints |
66/// |---------|--------|-------------|-------------|
67/// | **Exact Value** | `field: value` | Direct equality comparison | Must implement `PartialEq` |
68/// | **String Literal** | `field: "text"` | String comparison (no `.to_string()` needed) | String or &str fields |
69/// | **Explicit Equality** | `field: == value` | Same as exact value but explicit | Must implement `PartialEq` |
70/// | **Inequality** | `field: != value` | Not equal comparison | Must implement `PartialEq` |
71///
72/// ## Comparison Patterns
73///
74/// | Pattern | Syntax | Description | Constraints |
75/// |---------|--------|-------------|-------------|
76/// | **Greater Than** | `field: > value` | Numeric greater than | Must implement `PartialOrd` |
77/// | **Greater Equal** | `field: >= value` | Numeric greater or equal | Must implement `PartialOrd` |
78/// | **Less Than** | `field: < value` | Numeric less than | Must implement `PartialOrd` |
79/// | **Less Equal** | `field: <= value` | Numeric less or equal | Must implement `PartialOrd` |
80///
81/// ## Range Patterns
82///
83/// | Pattern | Syntax | Description | Constraints |
84/// |---------|--------|-------------|-------------|
85/// | **Inclusive Range** | `field: start..=end` | Value in inclusive range | Must implement `PartialOrd` |
86/// | **Exclusive Range** | `field: start..end` | Value in exclusive range | Must implement `PartialOrd` |
87/// | **Range From** | `field: start..` | Value greater or equal to start | Must implement `PartialOrd` |
88/// | **Range To** | `field: ..end` | Value less than end | Must implement `PartialOrd` |
89/// | **Range Full** | `field: ..` | Matches any value | No constraints |
90///
91/// ## String Pattern Matching
92///
93/// | Pattern | Syntax | Description | Constraints |
94/// |---------|--------|-------------|-------------|
95/// | **Regex Literal** | `field: =~ r"pattern"` | Regular expression match | Requires `regex` feature, `String`/`&str` |
96/// | **Like Trait** | `field: =~ expression` | Custom pattern matching | Must implement `Like<T>` |
97///
98/// ## Field Operations
99///
100/// | Operation | Syntax | Description | Constraints |
101/// |-----------|--------|-------------|-------------|
102/// | **Dereference** | `*field: pattern` | Dereference smart pointer | Must implement `Deref` |
103/// | **Multiple Deref** | `**field: pattern` | Multiple dereference | Must implement `Deref` (nested) |
104/// | **Method Call** | `field.method(): pattern` | Call method and match result | Method must exist and return compatible type |
105/// | **Method with Args** | `field.method(args): pattern` | Call method with arguments | Method must exist with compatible signature |
106/// | **Tuple Method** | `(index.method(): pattern, _)` | Method on tuple element | Valid index, method exists |
107///
108/// ## Enum Patterns
109///
110/// | Pattern | Syntax | Description | Constraints |
111/// |---------|--------|-------------|-------------|
112/// | **Option Some** | `field: Some(pattern)` | Match Some variant with inner pattern | `Option<T>` field |
113/// | **Option None** | `field: None` | Match None variant | `Option<T>` field |
114/// | **Result Ok** | `field: Ok(pattern)` | Match Ok variant with inner pattern | `Result<T, E>` field |
115/// | **Result Err** | `field: Err(pattern)` | Match Err variant with inner pattern | `Result<T, E>` field |
116/// | **Unit Variant** | `field: EnumType::Variant` | Match unit enum variant | Enum with unit variant |
117/// | **Tuple Variant** | `field: EnumType::Variant(patterns...)` | Match tuple enum variant | Enum with tuple variant |
118/// | **Struct Variant** | `field: EnumType::Variant { fields... }` | Match struct enum variant | Enum with struct variant |
119///
120/// ## Wildcard Struct Patterns
121///
122/// | Pattern | Syntax | Description | Constraints |
123/// |---------|--------|-------------|-------------|
124/// | **Wildcard Struct** | `value: _ { fields... }` | Match struct without naming type | Always partial; `..` is optional |
125/// | **Nested Wildcard** | `_ { field: _ { ... } }` | Nested anonymous structs | Avoids importing nested types |
126///
127/// ## Collection Patterns
128///
129/// | Pattern | Syntax | Description | Constraints |
130/// |---------|--------|-------------|-------------|
131/// | **Exact Slice** | `field: [pattern, pattern, ...]` | Match exact slice elements | `Vec<T>` or slice |
132/// | **Partial Head** | `field: [pattern, ..]` | Match prefix elements | `Vec<T>` or slice |
133/// | **Partial Tail** | `field: [.., pattern]` | Match suffix elements | `Vec<T>` or slice |
134/// | **Head and Tail** | `field: [pattern, .., pattern]` | Match first and last | `Vec<T>` or slice |
135/// | **Empty Slice** | `field: []` | Match empty collection | `Vec<T>` or slice |
136///
137/// ## Tuple Patterns
138///
139/// | Pattern | Syntax | Description | Constraints |
140/// |---------|--------|-------------|-------------|
141/// | **Exact Tuple** | `field: (pattern, pattern, ...)` | Match all tuple elements | Tuple type |
142/// | **Wildcard Element** | `field: (pattern, _, pattern)` | Ignore specific elements | Tuple type |
143/// | **Indexed Method** | `field: (0.method(): pattern, _)` | Method call on tuple element | Valid index |
144///
145/// # Parameters
146///
147/// - **`expression`**: Any expression that evaluates to a struct instance. The expression is
148///   borrowed, not consumed, so the value remains available after the assertion.
149/// - **`TypeName`**: The struct type name. Must exactly match the runtime type of the expression.
150/// - **`{ fields }`**: Pattern specification for struct fields. Can be partial (with `..`) or exhaustive.
151///
152/// # Runtime Behavior
153///
154/// ## Evaluation Semantics
155///
156/// - **Non-consuming**: The macro borrows the value, leaving it available after the assertion
157/// - **Expression evaluation**: The expression is evaluated exactly once before pattern matching
158/// - **Short-circuit evaluation**: Patterns are evaluated left-to-right, failing fast on first mismatch
159/// - **Field order independence**: Fields can be specified in any order in the pattern
160/// - **Type requirements**: All fields must have types compatible with their patterns
161///
162/// ## Pattern Matching Rules
163///
164/// ### Exhaustive vs Partial Matching
165/// - **Without `..`**: All struct fields must be specified in the pattern (exhaustive)
166/// - **With `..`**: Only specified fields are checked (partial matching)
167/// - **Multiple `..`**: Compilation error - only one rest pattern allowed per struct
168/// - **Wildcard structs (`_`)**: Always partial — `..` is never required and may be omitted
169///
170/// ### Field Operation Precedence
171/// Field operations are applied in left-to-right order:
172/// ```text
173/// **field.method().other_method(): pattern
174/// // Equivalent to: ((*(*field)).method()).other_method()
175/// ```
176///
177/// ### String Literal Handling
178/// - String literals (`"text"`) automatically work with `String` and `&str` fields
179/// - No `.to_string()` conversion needed in patterns
180/// - Comparison uses `PartialEq` implementation
181///
182/// # Panics
183///
184/// The macro panics (causing test failure) when:
185///
186/// ## Pattern Mismatches
187/// - **Value mismatch**: Expected value doesn't equal actual value
188/// - **Comparison failure**: Comparison operator condition fails (e.g., `>`, `<`)
189/// - **Range mismatch**: Value outside specified range
190/// - **Enum variant mismatch**: Different enum variant than expected
191/// - **Collection length mismatch**: Slice pattern length differs from actual length
192/// - **None/Some mismatch**: Expected `Some` but got `None`, or vice versa
193/// - **Ok/Err mismatch**: Expected `Ok` but got `Err`, or vice versa
194///
195/// ## Method Call Failures
196/// - **Method panic**: Called method itself panics during execution
197/// - **Argument evaluation panic**: Method arguments panic during evaluation
198///
199/// ## Regex Failures (when `regex` feature enabled)
200/// - **Invalid regex**: Malformed regular expression pattern
201/// - **Regex evaluation panic**: Regex engine encounters error
202///
203/// ## Runtime Type Issues
204/// **Note**: Type mismatches are caught at compile time, not runtime.
205///
206/// # Compilation Errors
207///
208/// ## Field Validation
209/// - **Nonexistent field**: Field doesn't exist on the struct type
210/// - **Missing fields**: Required fields not specified (without `..`)
211/// - **Duplicate fields**: Same field specified multiple times
212/// - **Invalid field operations**: Operations not supported by field type
213///
214/// ## Type Compatibility
215/// - **Type mismatch**: Pattern type incompatible with field type
216/// - **Trait requirements**: Field doesn't implement required traits (`PartialEq`, `PartialOrd`, etc.)
217/// - **Method signatures**: Method doesn't exist or has incompatible signature
218/// - **Deref constraints**: Field type doesn't implement `Deref` for dereference operations
219///
220/// ## Syntax Validation
221/// - **Invalid syntax**: Malformed pattern syntax
222/// - **Invalid operators**: Unsupported operator for field type
223/// - **Invalid ranges**: Malformed range expressions
224/// - **Invalid regex syntax**: Invalid regex literal (when using raw strings)
225/// - **Multiple rest patterns**: More than one `..` in same struct pattern
226///
227/// ## Feature Requirements
228/// - **Missing regex feature**: Using `=~ r"pattern"` without `regex` feature enabled
229/// - **Like trait not implemented**: Using `=~ expr` where `Like` trait not implemented
230///
231/// # Edge Cases and Limitations
232///
233/// ## Method Call Constraints
234/// - **Return type compatibility**: Method return type must be compatible with pattern type
235/// - **Argument evaluation**: Method arguments are evaluated before the method call
236/// - **No generic method inference**: Generic methods may require explicit type annotations
237/// - **Tuple indexing bounds**: Tuple method calls require valid index at compile time
238///
239/// ## Collection Pattern Limitations
240/// - **Fixed length patterns**: Slice patterns without `..` require exact length match
241/// - **Nested pattern complexity**: Deeply nested slice patterns may impact compile time
242/// - **Memory usage**: Large literal slice patterns increase binary size
243///
244/// ## Smart Pointer Behavior
245/// - **Multiple deref levels**: Each `*` adds one deref level, must match pointer nesting
246/// - **Deref coercion**: Standard Rust deref coercion rules apply
247/// - **Ownership semantics**: Dereferencing borrows the pointed-to value
248///
249/// ## Performance Considerations
250/// - **Compile time**: Complex nested patterns increase compilation time
251/// - **Runtime overhead**: Pattern matching is zero-cost for simple patterns
252/// - **Error message generation**: Error formatting only occurs on failure
253///
254/// # Feature Dependencies
255///
256/// ## Regex Feature (`regex`)
257/// - **Default**: Enabled by default
258/// - **Required for**: `=~ r"pattern"` syntax with string literals
259/// - **Disable with**: `default-features = false` in Cargo.toml
260/// - **Alternative**: Use `Like` trait with pre-compiled regex or custom patterns
261///
262/// ## Like Trait Extension
263/// - **No feature required**: Always available
264/// - **Custom implementations**: Implement `Like<T>` for custom pattern matching
265/// - **Regex integration**: Built-in implementations for regex when feature enabled
266///
267/// # Error Message Format
268///
269/// When assertions fail, the macro generates structured error messages with:
270///
271/// ## Error Components
272/// - **Error type**: Specific failure category (value mismatch, comparison failure, etc.)
273/// - **Field path**: Complete path to the failing field (e.g., `response.user.profile.age`)
274/// - **Source location**: File name and line number of the assertion
275/// - **Actual value**: The value that was found
276/// - **Expected pattern**: The pattern that was expected to match
277/// - **Pattern context**: Visual representation showing where the failure occurred
278///
279/// ## Error Types
280/// - **value mismatch**: Direct equality comparison failed
281/// - **comparison mismatch**: Comparison operator condition failed (`>`, `<`, etc.)
282/// - **range mismatch**: Value outside specified range
283/// - **regex mismatch**: Regex pattern didn't match
284/// - **enum variant mismatch**: Wrong enum variant
285/// - **slice mismatch**: Collection length or element pattern failure
286/// - **method call error**: Method call or result pattern failure
287///
288/// ## Pattern Context Display
289/// Complex patterns show visual context with failure highlighting:
290/// ```text
291/// assert_struct! failed:
292///
293///    | Response { user: User { profile: Profile {
294/// comparison mismatch:
295///   --> `response.user.profile.age` (tests/api.rs:45)
296///    |         age: > 18,
297///    |              ^^^^^ actual: 17
298///    | } } }
299/// ```
300///
301/// ## Method Call Errors
302/// Method calls in field paths are clearly indicated:
303/// ```text
304/// comparison mismatch:
305///   --> `data.items.len()` (tests/collections.rs:23)
306///   actual: 3
307///   expected: > 5
308/// ```
309///
310/// # Quick Reference Examples
311///
312/// ```rust
313/// # use assert_struct::assert_struct;
314/// # #[derive(Debug)]
315/// # struct Example { value: i32, name: String, items: Vec<i32> }
316/// # let example = Example { value: 42, name: "test".to_string(), items: vec![1, 2] };
317/// // Basic pattern matching
318/// assert_struct!(example, Example {
319///     value: 42,                    // Exact equality
320///     name: != "other",             // Inequality
321///     items.len(): >= 2,            // Method call with comparison
322///     ..                            // Partial matching
323/// });
324/// ```
325///
326/// # See Also
327///
328/// - **Learning Guide**: See the [crate-level documentation](crate) for comprehensive examples
329/// - **Real-World Examples**: Check the `examples/` directory for practical usage patterns
330/// - **Like Trait**: Implement custom pattern matching with the `Like` trait
331#[proc_macro]
332pub fn assert_struct(input: TokenStream) -> TokenStream {
333    // Parse the input
334    let assert = match syn::parse(input) {
335        Ok(assert) => assert,
336        Err(err) => return TokenStream::from(err.to_compile_error()),
337    };
338
339    // Expand to output code
340    let expanded = expand::expand(&assert);
341
342    TokenStream::from(expanded)
343}