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