borrowscope_macro/
lib.rs

1//! # BorrowScope Procedural Macros
2//!
3//! This crate provides the `#[trace_borrow]` attribute macro that instruments
4//! Rust code to track ownership and borrowing operations at runtime.
5//!
6//! ## Quick Start
7//!
8//! ```ignore
9//! use borrowscope_macro::trace_borrow;
10//! use borrowscope_runtime::*;
11//!
12//! #[trace_borrow]
13//! fn example() {
14//!     let x = String::from("hello");  // New event
15//!     let y = &x;                      // Borrow event
16//!     let z = x;                       // Move event
17//! }                                    // Drop events
18//!
19//! fn main() {
20//!     reset();
21//!     example();
22//!     println!("{:?}", get_events());
23//! }
24//! ```
25//!
26//! ## Attribute Options
27//!
28//! ### Presets
29//!
30//! | Attribute | Description |
31//! |-----------|-------------|
32//! | `#[trace_borrow]` | Standard tracking (recommended) |
33//! | `#[trace_borrow(quiet)]` | Ownership only (new, move, drop, borrow) |
34//! | `#[trace_borrow(verbose)]` | All tracking including noisy features |
35//!
36//! ### Feature Selection
37//!
38//! | Attribute | Description |
39//! |-----------|-------------|
40//! | `#[trace_borrow(skip = "loops,branches")]` | Skip specific feature groups |
41//! | `#[trace_borrow(only = "ownership")]` | Enable only specified feature groups |
42//!
43//! ### Filtering & Sampling (Performance)
44//!
45//! | Attribute | Description |
46//! |-----------|-------------|
47//! | `#[trace_borrow(filter = "data*")]` | Only track variables matching glob pattern |
48//! | `#[trace_borrow(sample = 0.1)]` | Track ~10% of operations (probabilistic) |
49//!
50//! ### Conditional Compilation
51//!
52//! | Attribute | Description |
53//! |-----------|-------------|
54//! | `#[trace_borrow(debug_only)]` | Only track in debug builds |
55//! | `#[trace_borrow(release_only)]` | Only track in release builds |
56//! | `#[trace_borrow(feature = "tracing")]` | Only track when cargo feature enabled |
57//!
58//! ## Feature Groups
59//!
60//! Use these group names with `skip` or `only` options:
61//!
62//! | Group | Aliases | Description |
63//! |-------|---------|-------------|
64//! | `ownership` | - | Variable creation, moves, drops, borrows |
65//! | `smart_pointers` | `pointers` | Rc, Arc, RefCell, Cell operations |
66//! | `loops` | - | for, while, loop tracking |
67//! | `branches` | - | if/else, match tracking |
68//! | `control_flow` | `control` | break, continue, return |
69//! | `try` | - | ? operator |
70//! | `methods` | - | clone, lock, unwrap |
71//! | `async` | - | async blocks, await |
72//! | `unsafe` | - | unsafe blocks, raw pointers, transmute |
73//! | `expressions` | `exprs` | struct, tuple, array, range, cast |
74//! | `functions` | `fn` | Function entry/exit (disabled by default) |
75//!
76//! ## Filtering
77//!
78//! Filter which variables are tracked using glob patterns:
79//!
80//! ```ignore
81//! #[trace_borrow(filter = "data*")]      // Track vars starting with "data"
82//! #[trace_borrow(filter = "*_count")]    // Track vars ending with "_count"
83//! #[trace_borrow(filter = "user_?")]     // Track user_1, user_2, etc.
84//! ```
85//!
86//! **Pattern syntax:**
87//! - `*` matches zero or more characters
88//! - `?` matches exactly one character
89//!
90//! **Note:** Filtering is applied at compile-time. No tracking code is generated
91//! for variables that don't match the pattern, resulting in zero overhead.
92//!
93//! ## Sampling
94//!
95//! Reduce tracking overhead by only recording a percentage of operations:
96//!
97//! ```ignore
98//! #[trace_borrow(sample = 0.1)]   // Track ~10% of operations
99//! #[trace_borrow(sample = 0.5)]   // Track ~50% of operations
100//! #[trace_borrow(sample = 1.0)]   // Track 100% (same as no sampling)
101//! ```
102//!
103//! **Use cases:**
104//! - High-frequency loops where full tracking is too expensive
105//! - Production monitoring with minimal overhead
106//! - Statistical analysis where sampling is acceptable
107//!
108//! **Note:** Sampling uses a fast PRNG (xorshift64) for minimal overhead.
109//! The decision is made at runtime for each tracking call.
110//!
111//! ## Conditional Compilation
112//!
113//! Control when tracking code is included:
114//!
115//! ```ignore
116//! // Only in debug builds (recommended for development)
117//! #[trace_borrow(debug_only)]
118//! fn dev_function() { }
119//!
120//! // Only in release builds (for production monitoring)
121//! #[trace_borrow(release_only)]
122//! fn prod_function() { }
123//!
124//! // Only when cargo feature is enabled
125//! #[trace_borrow(feature = "tracing")]
126//! fn optional_tracing() { }
127//! ```
128//!
129//! **Generated code:**
130//! - `debug_only` → `#[cfg(debug_assertions)]`
131//! - `release_only` → `#[cfg(not(debug_assertions))]`
132//! - `feature = "x"` → `#[cfg(feature = "x")]`
133//!
134//! ## Combining Options
135//!
136//! Multiple options can be combined:
137//!
138//! ```ignore
139//! // Debug-only, quiet mode
140//! #[trace_borrow(debug_only, quiet)]
141//!
142//! // Filter + sampling for high-performance tracking
143//! #[trace_borrow(filter = "user*", sample = 0.1)]
144//!
145//! // Feature-gated with specific groups
146//! #[trace_borrow(feature = "trace", only = "ownership,smart_pointers")]
147//!
148//! // Skip noisy features, debug only
149//! #[trace_borrow(debug_only, skip = "loops,branches,expressions")]
150//! ```
151//!
152//! ## Tracked Operations
153//!
154//! ### Basic Ownership (`ownership` group)
155//!
156//! | Code Pattern | Event |
157//! |--------------|-------|
158//! | `let x = value;` | `New` |
159//! | `let y = &x;` | `Borrow` |
160//! | `let y = &mut x;` | `Borrow` (mutable) |
161//! | `let y = x;` (move) | `Move` |
162//! | Scope exit | `Drop` |
163//!
164//! ### Smart Pointers (`smart_pointers` group)
165//!
166//! | Code Pattern | Event |
167//! |--------------|-------|
168//! | `Rc::new(v)` | `RcNew` |
169//! | `Rc::clone(&rc)` | `RcClone` |
170//! | `Arc::new(v)` | `ArcNew` |
171//! | `Arc::clone(&arc)` | `ArcClone` |
172//! | `Box::new(v)` | `BoxNew` |
173//! | `Box::pin(v)` | `PinNew` |
174//! | `RefCell::new(v)` | `RefCellNew` |
175//! | `refcell.borrow()` | `RefCellBorrow` |
176//! | `refcell.borrow_mut()` | `RefCellBorrowMut` |
177//! | `Cell::new(v)` | `CellNew` |
178//! | `cell.get()` | `CellGet` |
179//! | `cell.set(v)` | `CellSet` |
180//!
181//! ### Loops (`loops` group)
182//!
183//! | Code Pattern | Event |
184//! |--------------|-------|
185//! | `for`/`while`/`loop` entry | `LoopEnter` |
186//! | Each iteration | `LoopIteration` |
187//! | Loop end | `LoopExit` |
188//!
189//! ### Branches (`branches` group)
190//!
191//! | Code Pattern | Event |
192//! |--------------|-------|
193//! | `if`/`else` | `Branch` |
194//! | `match` entry | `MatchEnter` |
195//! | Match arm taken | `MatchArm` |
196//! | Match end | `MatchExit` |
197//!
198//! ### Control Flow (`control_flow` group)
199//!
200//! | Code Pattern | Event |
201//! |--------------|-------|
202//! | `break` | `Break` |
203//! | `continue` | `Continue` |
204//! | `return` | `Return` |
205//!
206//! ### Other Groups
207//!
208//! | Group | Code Patterns | Events |
209//! |-------|---------------|--------|
210//! | `try` | `expr?` | `Try` |
211//! | `methods` | `.clone()`, `.lock()`, `.unwrap()` | `Clone`, `Lock`, `Unwrap` |
212//! | `async` | `async { }`, `.await` | `AsyncBlockEnter/Exit`, `AwaitStart/End` |
213//! | `unsafe` | `unsafe { }`, `*ptr`, `transmute` | `UnsafeBlockEnter/Exit`, `RawPtrDeref`, `Transmute` |
214//! | `expressions` | structs, tuples, arrays, ranges, casts | `StructCreate`, `TupleCreate`, etc. |
215//! | `functions` | fn entry/exit | `FnEnter`, `FnExit` |
216//!
217//! ## Advanced Smart Pointer Tracking
218//!
219//! Beyond basic `Rc`, `Arc`, `RefCell`, and `Cell`, the macro tracks:
220//!
221//! ### Weak References
222//!
223//! | Code Pattern | Event |
224//! |--------------|-------|
225//! | `Rc::downgrade(&rc)` | `WeakNew` |
226//! | `Arc::downgrade(&arc)` | `WeakNewSync` |
227//! | `weak.upgrade()` | `WeakUpgrade` / `WeakUpgradeSync` |
228//! | `weak.clone()` | `WeakClone` / `WeakCloneSync` |
229//!
230//! ### Pin, Cow, OnceCell, MaybeUninit
231//!
232//! | Type | Operations |
233//! |------|------------|
234//! | `Box` | `Box::pin`, `Box::into_raw`, `Box::from_raw` |
235//! | `Pin<T>` | `Pin::new`, `Pin::into_inner` |
236//! | `Cow<T>` | `Cow::Borrowed`, `Cow::Owned`, `to_mut()` |
237//! | `OnceCell<T>` | `new()`, `set()`, `get()`, `get_or_init()` |
238//! | `OnceLock<T>` | `new()`, `set()`, `get()`, `get_or_init()` |
239//! | `MaybeUninit<T>` | `uninit()`, `new()`, `write()`, `assume_init()` |
240//!
241//! ## Concurrency Tracking
242//!
243//! | Code Pattern | Event |
244//! |--------------|-------|
245//! | `thread::spawn(...)` | `ThreadSpawn` |
246//! | `handle.join()` | `ThreadJoin` |
247//! | `mpsc::channel()` | `ChannelNew` |
248//! | `tx.send(v)` | `ChannelSend` |
249//! | `rx.recv()` | `ChannelRecv` |
250//! | `rx.try_recv()` | `ChannelTryRecv` |
251//!
252//! ## Expression Tracking (`expressions` group)
253//!
254//! | Code Pattern | Event |
255//! |--------------|-------|
256//! | `Point { x, y }` | `StructCreate` (with type name) |
257//! | `(a, b, c)` | `TupleCreate` (with arity) |
258//! | `[1, 2, 3]` | `ArrayCreate` (with length) |
259//! | `0..10` | `Range` (half_open) |
260//! | `0..=10` | `Range` (closed) |
261//! | `x as i64` | `TypeCast` (with target type) |
262//!
263//! ## Closure Tracking
264//!
265//! | Code Pattern | Event |
266//! |--------------|-------|
267//! | `\|x\| x + 1` | `ClosureCreate` (capture mode: ref) |
268//! | `move \|x\| x + 1` | `ClosureCreate` (capture mode: move) |
269//! | Captured variable | `ClosureCapture` (per variable) |
270//!
271//! ## Diagnostic Options
272//!
273//! For patterns that cannot be auto-detected, use diagnostic attributes:
274//!
275//! | Attribute | Description |
276//! |-----------|-------------|
277//! | `#[trace_borrow(warn)]` | Emit warnings for ambiguous patterns |
278//! | `#[trace_borrow(ffi = ["malloc"])]` | Declare known FFI functions |
279//! | `#[trace_borrow(unions = ["MyUnion"])]` | Declare known union types |
280//! | `#[trace_borrow(statics = ["GLOBAL"])]` | Declare known static variables |
281//!
282//! ## How It Works
283//!
284//! The macro transforms functions by:
285//!
286//! 1. **Parsing** the function into an AST using `syn`
287//! 2. **Walking** the AST with `OwnershipVisitor` that maintains:
288//!    - Unique IDs for each variable (for event correlation)
289//!    - Scope stack for LIFO drop ordering
290//!    - Type context (tracks which vars are Weak, Cow, OnceCell, etc.)
291//! 3. **Injecting** `borrowscope_runtime::track_*` calls
292//! 4. **Generating** drop calls at scope exits in reverse order
293//!
294//! ### ID-Based Correlation
295//!
296//! Each variable gets a unique ID, enabling correlation:
297//! - Borrows link to their owner's ID
298//! - Clones link to their source's ID
299//! - Moves link source and destination IDs
300//!
301//! ## Performance Tips
302//!
303//! 1. **Use `quiet` mode** for minimal overhead when you only need ownership tracking
304//! 2. **Use `filter`** to track only relevant variables (zero overhead for non-matching)
305//! 3. **Use `sample`** for high-frequency code paths
306//! 4. **Use `debug_only`** to eliminate all overhead in release builds
307//! 5. **Use `skip`** to disable noisy features like loops and branches
308//!
309//! ```ignore
310//! // Minimal overhead configuration
311//! #[trace_borrow(debug_only, quiet, filter = "important_*")]
312//! fn performance_critical() { }
313//! ```
314//!
315//! ## Common Patterns
316//!
317//! ```ignore
318//! // Development: full tracking, debug only
319//! #[trace_borrow(debug_only)]
320//! fn dev_function() { }
321//!
322//! // Learning: ownership only, cleaner output
323//! #[trace_borrow(quiet)]
324//! fn learning_example() { }
325//!
326//! // Production monitoring: sampled, feature-gated
327//! #[trace_borrow(feature = "monitoring", sample = 0.01)]
328//! fn production_function() { }
329//!
330//! // Debugging specific variables
331//! #[trace_borrow(filter = "suspect_*", verbose)]
332//! fn debug_specific() { }
333//! ```
334//!
335//! ## Limitations
336//!
337//! - **const fn**: Cannot be used (tracking requires runtime)
338//! - **extern fn**: Cannot be used (only Rust ABI supported)
339//! - **async fn**: Works but may not capture all ownership across await points
340//! - **unsafe fn**: Works but tracking cannot verify safety invariants
341//! - **Macros**: Variables created inside macro expansions may not be tracked
342//!
343//! ## Troubleshooting
344//!
345//! **No events recorded:**
346//! - Ensure `borrowscope_runtime` has `features = ["track"]` enabled
347//! - Call `reset()` before the traced function
348//! - Check if `debug_only` is set but running in release mode
349//!
350//! **Too many events:**
351//! - Use `quiet` mode or `only = "ownership"`
352//! - Use `skip = "loops,branches"` to reduce noise
353//! - Use `filter` to track specific variables
354//!
355//! **Performance issues:**
356//! - Use `sample = 0.1` or lower for high-frequency code
357//! - Use `debug_only` to disable in release builds
358//! - Use `filter` to reduce tracked variables
359
360mod best_practices;
361mod borrow_detection;
362mod codegen;
363mod config;
364mod examples;
365mod formatting;
366mod generic_handler;
367mod diagnostics;
368mod hygiene;
369mod optimized_transform;
370mod parser;
371mod pattern;
372mod smart_pointer;
373mod span_utils;
374mod transform_visitor;
375mod type_info;
376mod validation;
377mod visitor;
378
379use proc_macro::TokenStream;
380use proc_macro_error::{abort, proc_macro_error};
381use quote::quote;
382use syn::{parse_macro_input, visit_mut::VisitMut, ItemFn};
383use transform_visitor::OwnershipVisitor;
384
385/// Validate function before transformation
386fn validate_function(func: &ItemFn) {
387    // Check for const functions
388    if func.sig.constness.is_some() {
389        abort!(
390            func.sig.constness,
391            "const functions cannot be tracked";
392            help = "remove the `const` keyword to enable tracking";
393            note = "tracking requires runtime operations, but const functions are evaluated at compile time"
394        );
395    }
396
397    // Check for extern functions
398    if let Some(abi) = &func.sig.abi {
399        abort!(
400            abi,
401            "extern functions cannot be tracked";
402            help = "only Rust ABI functions can be tracked"
403        );
404    }
405
406    // Warn about async functions (they work but with limitations)
407    if func.sig.asyncness.is_some() {
408        // Note: We allow async but could add a warning in the future
409    }
410
411    // Warn about unsafe functions (they work but can't verify safety)
412    if func.sig.unsafety.is_some() {
413        // Note: We allow unsafe but tracking may be incomplete
414    }
415}
416
417/// Attribute macro to trace ownership and borrowing in a function.
418///
419/// This macro transforms a function to inject runtime tracking calls that record
420/// ownership transfers, borrows, drops, and other operations. The events can be
421/// retrieved using `borrowscope_runtime::get_events()`.
422///
423/// # Basic Usage
424///
425/// ```ignore
426/// use borrowscope_macro::trace_borrow;
427/// use borrowscope_runtime::*;
428///
429/// #[trace_borrow]
430/// fn example() {
431///     let x = String::from("hello");  // New event
432///     let y = &x;                      // Borrow event
433///     let z = x;                       // Move event
434/// }                                    // Drop events
435/// ```
436///
437/// # Attribute Options
438///
439/// ## `quiet` - Minimal tracking
440///
441/// Only tracks basic ownership: new, move, drop, borrow.
442///
443/// ```ignore
444/// #[trace_borrow(quiet)]
445/// fn minimal() {
446///     let x = vec![1, 2, 3];
447///     for i in &x { }  // Loop NOT tracked
448/// }
449/// ```
450///
451/// ## `verbose` - All tracking
452///
453/// Enables all tracking features (same as default currently).
454///
455/// ```ignore
456/// #[trace_borrow(verbose)]
457/// fn everything() { }
458/// ```
459///
460/// ## `skip` - Disable specific features
461///
462/// Comma-separated list of feature groups to disable.
463///
464/// ```ignore
465/// #[trace_borrow(skip = "loops,branches")]
466/// fn skip_noisy() {
467///     for i in 0..10 { }  // NOT tracked
468///     if true { }         // NOT tracked
469/// }
470/// ```
471///
472/// ## `only` - Enable only specific features
473///
474/// Comma-separated list of feature groups to enable (all others disabled).
475///
476/// ```ignore
477/// #[trace_borrow(only = "ownership,functions")]
478/// fn focused() {
479///     let x = 1;  // Tracked (ownership)
480///     // FnEnter/FnExit tracked (functions)
481/// }
482/// ```
483///
484/// # Feature Groups
485///
486/// | Group | Aliases | What it tracks |
487/// |-------|---------|----------------|
488/// | `ownership` | - | `let`, moves, drops, borrows |
489/// | `smart_pointers` | `pointers` | Rc, Arc, RefCell, Cell |
490/// | `loops` | - | for, while, loop |
491/// | `branches` | - | if/else, match |
492/// | `control_flow` | `control` | break, continue, return |
493/// | `try` | - | `?` operator |
494/// | `methods` | - | clone, lock, unwrap |
495/// | `async` | - | async blocks, await |
496/// | `unsafe` | - | unsafe blocks, raw pointers |
497/// | `expressions` | `exprs` | struct, tuple, array, range, cast |
498/// | `functions` | `fn` | Function entry/exit (off by default) |
499///
500/// # Conditional Compilation
501///
502/// | Option | Description |
503/// |--------|-------------|
504/// | `debug_only` | Only instrument in debug builds |
505/// | `release_only` | Only instrument in release builds |
506/// | `feature = "name"` | Only instrument when cargo feature is enabled |
507///
508/// # Limitations
509///
510/// - Cannot be used on `const fn` (tracking requires runtime)
511/// - Cannot be used on `extern` functions
512/// - Async functions work but may miss some ownership across await points
513#[proc_macro_attribute]
514#[proc_macro_error]
515pub fn trace_borrow(attr: TokenStream, item: TokenStream) -> TokenStream {
516    // Parse attribute arguments
517    let args = parse_macro_input!(attr as config::TraceArgs);
518
519    // Parse the input as a function
520    let input_fn = parse_macro_input!(item as ItemFn);
521
522    // Validate the function
523    validate_function(&input_fn);
524
525    // Clone for transformation
526    let mut transformed_fn = input_fn.clone();
527
528    // Transform the function body using OwnershipVisitor with config
529    let mut visitor = OwnershipVisitor::with_config(args.config.clone());
530    visitor.visit_item_fn_mut(&mut transformed_fn);
531
532    // Collect warnings
533    let warnings = visitor.take_warnings();
534
535    // Generate output based on conditional mode
536    let output = if args.config.conditional_mode.is_conditional() {
537        // Generate both versions with cfg attributes
538        let cfg_tokens = args.config.conditional_mode.cfg_tokens().unwrap();
539        let neg_cfg_tokens = match &args.config.conditional_mode {
540            config::ConditionalMode::DebugOnly => quote! { #[cfg(not(debug_assertions))] },
541            config::ConditionalMode::ReleaseOnly => quote! { #[cfg(debug_assertions)] },
542            config::ConditionalMode::Feature(name) => {
543                let feature_name = syn::LitStr::new(name, proc_macro2::Span::call_site());
544                quote! { #[cfg(not(feature = #feature_name))] }
545            }
546            config::ConditionalMode::Always => unreachable!(),
547        };
548
549        quote! {
550            #(#warnings)*
551
552            #cfg_tokens
553            #transformed_fn
554
555            #neg_cfg_tokens
556            #input_fn
557        }
558    } else {
559        // Always instrument
560        quote! {
561            #(#warnings)*
562
563            #transformed_fn
564        }
565    };
566
567    output.into()
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573    use syn::parse_quote;
574
575    fn transform_function(func: &mut ItemFn) {
576        let mut visitor = OwnershipVisitor::new();
577        visitor.visit_item_fn_mut(func);
578    }
579
580    #[test]
581    fn test_transform_simple_variable() {
582        let mut func: ItemFn = parse_quote! {
583            fn example() {
584                let x = 5;
585            }
586        };
587
588        transform_function(&mut func);
589        let output = quote! { #func }.to_string();
590
591        assert!(output.contains("track_new"));
592    }
593
594    #[test]
595    fn test_transform_borrow() {
596        let mut func: ItemFn = parse_quote! {
597            fn example() {
598                let x = 5;
599                let y = &x;
600            }
601        };
602
603        transform_function(&mut func);
604        let output = quote! { #func }.to_string();
605
606        assert!(output.contains("track_borrow"));
607    }
608
609    #[test]
610    fn test_transform_mut_borrow() {
611        let mut func: ItemFn = parse_quote! {
612            fn example() {
613                let mut x = 5;
614                let y = &mut x;
615            }
616        };
617
618        transform_function(&mut func);
619        let output = quote! { #func }.to_string();
620
621        assert!(output.contains("track_borrow_mut"));
622    }
623
624    #[test]
625    fn test_preserves_function_signature() {
626        let mut func: ItemFn = parse_quote! {
627            fn example(a: i32) -> i32 {
628                let x = a;
629                x
630            }
631        };
632
633        transform_function(&mut func);
634        let output = quote! { #func }.to_string();
635
636        assert!(output.contains("fn example"));
637        assert!(output.contains("a : i32"));
638        assert!(output.contains("-> i32"));
639    }
640
641    #[test]
642    fn test_preserves_generics() {
643        let mut func: ItemFn = parse_quote! {
644            fn example<T>(value: T) -> T {
645                value
646            }
647        };
648
649        transform_function(&mut func);
650        let output = quote! { #func }.to_string();
651
652        assert!(output.contains("fn example"));
653        assert!(output.contains("< T >"));
654    }
655
656    #[test]
657    fn test_no_transform_without_init() {
658        let mut func: ItemFn = parse_quote! {
659            fn example() {
660                let x;
661                x = 5;
662            }
663        };
664
665        transform_function(&mut func);
666        let output = quote! { #func }.to_string();
667
668        // Should not add tracking for uninitialized variables
669        assert!(!output.contains("track_new"));
670    }
671
672    #[test]
673    fn test_preserves_visibility() {
674        let mut func: ItemFn = parse_quote! {
675            pub fn example() {
676                let x = 5;
677            }
678        };
679
680        transform_function(&mut func);
681        let output = quote! { #func }.to_string();
682
683        assert!(output.contains("pub fn example"));
684    }
685
686    #[test]
687    fn test_conditional_mode_cfg_tokens() {
688        use config::ConditionalMode;
689
690        // debug_only
691        let mode = ConditionalMode::DebugOnly;
692        let tokens = mode.cfg_tokens().unwrap().to_string();
693        assert!(tokens.contains("debug_assertions"));
694
695        // release_only
696        let mode = ConditionalMode::ReleaseOnly;
697        let tokens = mode.cfg_tokens().unwrap().to_string();
698        assert!(tokens.contains("not"));
699        assert!(tokens.contains("debug_assertions"));
700
701        // feature
702        let mode = ConditionalMode::Feature("tracing".to_string());
703        let tokens = mode.cfg_tokens().unwrap().to_string();
704        assert!(tokens.contains("feature"));
705        assert!(tokens.contains("tracing"));
706    }
707}