borrowscope-macro 0.1.1

Procedural macros for BorrowScope ownership tracking
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# BorrowScope Macro

> Procedural macros for automatic instrumentation of Rust ownership and borrowing

[![Crates.io](https://img.shields.io/crates/v/borrowscope-macro.svg)](https://crates.io/crates/borrowscope-macro)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)

## Introduction

BorrowScope Macro is a procedural macro crate that automatically instruments Rust code to track ownership transfers, borrows, and memory operations at runtime. It works in conjunction with [borrowscope-runtime](../borrowscope-runtime) to provide visibility into Rust's ownership system without requiring manual instrumentation.

The `#[trace_borrow]` attribute macro transforms your functions by injecting tracking calls at key points: variable creation, borrowing, moves, drops, and smart pointer operations. This enables runtime analysis of ownership flow, which is invaluable for learning Rust, debugging complex ownership scenarios, and understanding how your code interacts with Rust's memory model.

## Purpose and Motivation

Rust's ownership and borrowing system is powerful but operates entirely at compile time, making it invisible during execution. While the borrow checker prevents memory errors, developers often struggle to understand *why* certain patterns work or fail, especially when dealing with:

- Complex ownership chains across function boundaries
- Smart pointer interactions (`Rc`, `Arc`, `RefCell`, `Cell`)
- Interior mutability patterns
- Unsafe code blocks and raw pointer operations

BorrowScope Macro addresses this by making ownership operations observable. Instead of manually adding tracking calls throughout your code, you simply annotate functions with `#[trace_borrow]`, and the macro handles the instrumentation automatically. This approach:

1. **Reduces boilerplate** - No need to wrap every variable creation or borrow manually
2. **Ensures consistency** - All trackable operations are instrumented uniformly
3. **Preserves semantics** - The transformed code behaves identically to the original
4. **Enables tooling** - The generated events can be visualized, analyzed, or exported

## Features

### Basic Ownership Tracking

| Operation | Description |
|-----------|-------------|
| Variable creation | Tracks `let x = value` statements |
| Immutable borrows | Tracks `&x` references |
| Mutable borrows | Tracks `&mut x` references |
| Moves | Tracks ownership transfers |
| Drops | Tracks variables going out of scope (LIFO order) |

### Smart Pointer Support

| Type | Operations Tracked |
|------|-------------------|
| `Rc<T>` | Creation (`Rc::new`), cloning (`Rc::clone`) |
| `Arc<T>` | Creation (`Arc::new`), cloning (`Arc::clone`) |
| `Box<T>` | Creation (`Box::new`), `Box::pin`, `Box::into_raw`, `Box::from_raw` |
| `RefCell<T>` | Creation, `borrow()`, `borrow_mut()` |
| `Cell<T>` | Creation, `get()`, `set()` |
| `Weak<T>` | `Rc::downgrade`, `Arc::downgrade`, `upgrade()`, `clone()` |
| `Pin<T>` | `Pin::new`, `Pin::into_inner` |
| `Cow<T>` | `Cow::Borrowed`, `Cow::Owned`, `to_mut()` |
| `OnceCell<T>` | `new()`, `set()`, `get()`, `get_or_init()` |
| `OnceLock<T>` | `new()`, `set()`, `get()`, `get_or_init()` |
| `MaybeUninit<T>` | `uninit()`, `new()`, `write()`, `assume_init()`, `assume_init_read()`, `assume_init_drop()` |

### Concurrency Tracking

| Operation | Description |
|-----------|-------------|
| `thread::spawn` | Tracks thread creation with handle ID |
| `JoinHandle::join` | Tracks thread join operations |
| `mpsc::channel` | Tracks channel creation (sender and receiver) |
| `Sender::send` | Tracks messages sent through channels |
| `Receiver::recv` | Tracks blocking receive operations |
| `Receiver::try_recv` | Tracks non-blocking receive attempts |

### Expression Tracking

| Expression | Description |
|------------|-------------|
| Struct creation | Tracks `Point { x, y }` with type name |
| Tuple creation | Tracks `(a, b, c)` with arity |
| Array creation | Tracks `[1, 2, 3]` with length |
| Range expressions | Tracks `0..10` and `0..=10` with range type |
| Type casts | Tracks `x as i64` with target type |

### Closure Tracking

| Operation | Description |
|-----------|-------------|
| Closure creation | Tracks closure with capture mode (`move` or `ref`) |
| Variable capture | Tracks each captured variable and how it's captured |

### Unsafe Code Tracking

| Operation | Description |
|-----------|-------------|
| Unsafe blocks | Entry and exit tracking with unique block IDs |
| Raw pointer casts | Tracks `as *const T` and `as *mut T` conversions |
| `transmute` calls | Detects `std::mem::transmute` usage |

### Additional Features

- **Accurate source locations** - Uses `file!()` and `line!()` macros for precise location reporting
- **Scope-aware drop ordering** - Maintains correct LIFO drop order across nested scopes
- **Closure support** - Tracks captured variables in closures
- **Generic function support** - Works with generic type parameters and lifetimes
- **Async tracking** - Tracks async blocks and await expressions

### Control Flow Tracking

| Operation | Description |
|-----------|-------------|
| Loops | Tracks `for`, `while`, `loop` entry, iterations, and exit |
| Match expressions | Tracks match entry, which arm was taken, and exit |
| If/else branches | Tracks which branch was taken |
| Return statements | Tracks early returns |
| Try operator (`?`) | Tracks error propagation points |

### Method Call Tracking

| Method | Description |
|--------|-------------|
| `.clone()` | Tracks clone operations |
| `.lock()`, `.try_lock()` | Tracks Mutex lock acquisition |
| `.read()`, `.write()` | Tracks RwLock operations |
| `.unwrap()`, `.expect()` | Tracks Option/Result unwrapping |

## Usage

Add both crates to your `Cargo.toml`:

```toml
[dependencies]
# Check crates.io for the most recent version numbers
borrowscope-runtime = { version = "0.1", features = ["track"] }
borrowscope-macro = "0.1"
```

Annotate functions you want to trace:

```rust
use borrowscope_macro::trace_borrow;
use borrowscope_runtime::*;

#[trace_borrow]
fn example() {
    let data = vec![1, 2, 3];      // track_new called
    let reference = &data;          // track_borrow called
    println!("{:?}", reference);
}                                   // track_drop called for data

fn main() {
    reset();  // Clear previous tracking data
    example();
    
    // Export events as JSON
    let events = get_events();
    println!("{}", serde_json::to_string_pretty(&events).unwrap());
}
```

### Smart Pointer Example

```rust
use borrowscope_macro::trace_borrow;
use std::rc::Rc;
use std::cell::RefCell;

#[trace_borrow]
fn smart_pointer_example() {
    // Rc tracking
    let shared = Rc::new(42);           // track_rc_new
    let clone1 = Rc::clone(&shared);    // track_rc_clone
    
    // RefCell tracking
    let cell = RefCell::new(100);       // track_refcell_new
    let guard = cell.borrow();          // track_refcell_borrow
    let mut_guard = cell.borrow_mut();  // track_refcell_borrow_mut
}
```

### Unsafe Code Example

```rust
use borrowscope_macro::trace_borrow;

#[trace_borrow]
fn unsafe_example() {
    let x = 42;
    let ptr = &x as *const i32;  // track_raw_ptr
    
    unsafe {                      // track_unsafe_block_enter
        let _val = *ptr;
    }                             // track_unsafe_block_exit
}
```

### Control Flow Example

```rust
use borrowscope_macro::trace_borrow;

#[trace_borrow]
fn control_flow_example() -> Result<i32, &'static str> {
    // Loop tracking
    for i in 0..3 {              // track_loop_enter, track_loop_iteration (x3), track_loop_exit
        println!("{}", i);
    }
    
    // Match tracking
    let x = 42;
    let result = match x {       // track_match_enter
        0 => "zero",             // track_match_arm if taken
        _ => "other",            // track_match_arm if taken
    };                           // track_match_exit
    
    // Branch tracking
    if x > 0 {                   // track_branch("then")
        println!("positive");
    } else {                     // track_branch("else")
        println!("non-positive");
    }
    
    // Try operator tracking
    let value = some_fallible_fn()?;  // track_try
    
    Ok(value)
}
```

### Method Call Example

```rust
use borrowscope_macro::trace_borrow;
use std::sync::Mutex;

#[trace_borrow]
fn method_call_example() {
    let data = vec![1, 2, 3];
    let cloned = data.clone();        // track_clone
    
    let mutex = Mutex::new(42);
    let guard = mutex.lock().unwrap(); // track_lock, track_unwrap
    
    let option: Option<i32> = Some(42);
    let value = option.unwrap();       // track_unwrap
}
```

### Advanced Smart Pointer Example

```rust
use borrowscope_macro::trace_borrow;
use std::rc::{Rc, Weak};
use std::borrow::Cow;
use std::cell::OnceCell;

#[trace_borrow]
fn advanced_smart_pointers() {
    // Weak reference tracking
    let strong = Rc::new(42);
    let weak: Weak<i32> = Rc::downgrade(&strong);  // track_weak_new
    let weak2 = weak.clone();                       // track_weak_clone
    if let Some(val) = weak.upgrade() {             // track_weak_upgrade
        println!("{}", val);
    }
    
    // Cow tracking
    let cow: Cow<str> = Cow::Borrowed("hello");     // track_cow_borrowed
    let owned: Cow<str> = Cow::Owned(String::new()); // track_cow_owned
    
    // OnceCell tracking
    let cell: OnceCell<i32> = OnceCell::new();      // track_once_cell_new
    cell.set(42).ok();                               // track_once_cell_set
    let val = cell.get();                            // track_once_cell_get
}
```

### Concurrency Example

```rust
use borrowscope_macro::trace_borrow;
use std::sync::mpsc;
use std::thread;

#[trace_borrow]
fn concurrency_example() {
    // Channel tracking
    let (tx, rx) = mpsc::channel();  // track_channel
    
    // Thread tracking
    let handle = thread::spawn(move || {  // track_thread_spawn
        tx.send(42).unwrap();              // track_channel_send
    });
    
    let received = rx.recv().unwrap();     // track_channel_recv
    handle.join().unwrap();                // track_thread_join
}
```

## Attribute Options

### Presets

| Attribute | Description |
|-----------|-------------|
| `#[trace_borrow]` | Standard tracking (all features except function entry/exit) |
| `#[trace_borrow(quiet)]` | Ownership only (new, move, drop, borrow) |
| `#[trace_borrow(verbose)]` | All tracking features enabled |

### Feature Selection

| Attribute | Description |
|-----------|-------------|
| `#[trace_borrow(skip = "loops,branches")]` | Disable specific feature groups |
| `#[trace_borrow(only = "ownership")]` | Enable only specified groups (disable all others) |

### Feature Groups

| Group | Aliases | Description |
|-------|---------|-------------|
| `ownership` | - | Variable creation, moves, drops, borrows |
| `smart_pointers` | `pointers` | Rc, Arc, RefCell, Cell, Weak, Pin, Cow, OnceCell, MaybeUninit |
| `loops` | - | for, while, loop tracking |
| `branches` | - | if/else, match tracking |
| `control_flow` | `control` | break, continue, return |
| `try` | - | `?` operator |
| `methods` | - | clone, lock, unwrap |
| `async` | - | async blocks, await |
| `unsafe` | - | unsafe blocks, raw pointers, transmute |
| `expressions` | `exprs` | struct, tuple, array, range, cast |
| `functions` | `fn` | Function entry/exit (disabled by default) |

### Filtering

Track only variables matching a glob pattern:

```rust
#[trace_borrow(filter = "data*")]      // Track vars starting with "data"
#[trace_borrow(filter = "*_count")]    // Track vars ending with "_count"
#[trace_borrow(filter = "user_?")]     // Track user_1, user_2, etc.
```

Pattern syntax:
- `*` matches zero or more characters
- `?` matches exactly one character

Filtering is applied at compile-time—no tracking code is generated for non-matching variables.

### Sampling

Reduce overhead by tracking only a percentage of operations:

```rust
#[trace_borrow(sample = 0.1)]   // Track ~10% of operations
#[trace_borrow(sample = 0.5)]   // Track ~50% of operations
```

### Conditional Compilation

| Attribute | Description |
|-----------|-------------|
| `#[trace_borrow(debug_only)]` | Only track in debug builds |
| `#[trace_borrow(release_only)]` | Only track in release builds |
| `#[trace_borrow(feature = "tracing")]` | Only track when cargo feature enabled |

### Diagnostic Options

| Attribute | Description |
|-----------|-------------|
| `#[trace_borrow(warn)]` | Emit warnings for ambiguous patterns |
| `#[trace_borrow(ffi = ["malloc", "free"])]` | Declare known FFI functions (suppresses warnings) |
| `#[trace_borrow(unions = ["MyUnion"])]` | Declare known union types (suppresses warnings) |
| `#[trace_borrow(statics = ["GLOBAL"])]` | Declare known static variables (suppresses warnings) |

### Combining Options

```rust
#[trace_borrow(debug_only, quiet)]
#[trace_borrow(filter = "user*", sample = 0.1)]
#[trace_borrow(feature = "trace", only = "ownership,smart_pointers")]
#[trace_borrow(debug_only, skip = "loops,branches,expressions")]
```

## How It Works

The macro transforms your function by:

1. **Parsing** the function into an Abstract Syntax Tree (AST)
2. **Walking** the AST with an `OwnershipVisitor` that tracks:
   - Variable IDs for correlation
   - Scope stack for LIFO drop ordering
   - Variable types (Weak, Cow, OnceCell, etc.) for context-aware tracking
3. **Injecting** tracking calls at appropriate points
4. **Generating** drop calls at scope exits in reverse declaration order

Each variable gets a unique ID, enabling correlation between events (e.g., linking a borrow to its owner).

### Example Transformation

Input:
```rust
#[trace_borrow]
fn example() {
    let data = vec![1, 2, 3];
    let r = &data;
}
```

Output (simplified):
```rust
fn example() {
    let data = borrowscope_runtime::track_new_with_id(1, "data", "file.rs:2", vec![1, 2, 3]);
    let r = borrowscope_runtime::track_borrow_with_id(2, 1, "borrow", "file.rs:3", false, &data);
    borrowscope_runtime::track_drop("r");
    borrowscope_runtime::track_drop("data");
}
```

## Limitations

### Const Functions Cannot Be Traced

Const functions are evaluated at compile time by the Rust compiler, which fundamentally conflicts with runtime tracking. When a function is marked `const`, the compiler may evaluate it during compilation rather than at runtime, meaning any tracking calls we inject would never execute. Furthermore, const contexts have strict restrictions on what operations are permitted—they cannot call non-const functions, and our tracking functions are inherently non-const as they modify global state.

The macro will emit a compile-time error if you attempt to use `#[trace_borrow]` on a const function, with a helpful message explaining that tracking requires runtime operations.

### Extern Functions Cannot Be Traced

Functions with non-Rust ABIs (such as `extern "C"`) cannot be traced because they must conform to foreign calling conventions. Injecting tracking calls would alter the function's behavior and potentially break FFI compatibility. These functions are often called from C code or other languages that expect specific memory layouts and calling semantics that our instrumentation would violate.

### Raw Pointer Dereference Tracking

While the macro can track raw pointer *creation* (the `as *const T` and `as *mut T` cast operations), it cannot track raw pointer *dereferences* (`*ptr`). This limitation exists because Rust's dereference operator (`*`) is syntactically identical for raw pointers and types implementing the `Deref` trait. At macro expansion time, we only have access to the Abstract Syntax Tree (AST), not type information. When we see `*x`, we cannot determine whether `x` is a raw pointer requiring unsafe dereference tracking, or a smart pointer like `Box<T>` or `Rc<T>` that safely implements `Deref`.

Distinguishing between these cases would require type information from the compiler, which is not available to procedural macros. This is a fundamental limitation of Rust's macro system, which operates purely on syntax before type checking occurs.

### FFI Call Tracking

The macro cannot automatically detect and track calls to foreign functions (FFI). When you call a function like `libc::malloc()` or any other extern function, the macro sees only a path expression followed by arguments—syntactically identical to any other function call. Determining whether a function is declared as `extern "C"` requires access to the function's declaration, which may be in a different crate, a system library, or generated by a build script.

Procedural macros operate on a single item at a time (in our case, a function body) and have no mechanism to query declarations from other modules or crates. This information is only available during later compilation stages when the compiler has resolved all names and types.

### Union Field Access Tracking

Accessing fields of a union type is an unsafe operation in Rust because the compiler cannot guarantee which variant is currently valid. However, the macro cannot detect union field access because the syntax `value.field` is identical for structs and unions. Without type information, we cannot distinguish between a safe struct field access and an unsafe union field access.

This limitation means that while we track entry and exit from `unsafe` blocks (where union access must occur), we cannot specifically identify which operations within those blocks are union field accesses versus other unsafe operations.

### Unsafe Function Call Tracking

Similar to FFI calls, the macro cannot detect calls to functions declared as `unsafe fn`. The call syntax `some_function(args)` is identical whether the function is safe or unsafe. Determining the safety requirement of a function requires access to its signature, which may be defined anywhere in the dependency graph.

While all calls to unsafe functions must occur within `unsafe` blocks (which we do track), we cannot distinguish an unsafe function call from a safe function call that happens to be inside an unsafe block for other reasons.

### Static and Const Variable Tracking

Static variables (`static` and `static mut`) and const items (`const`) cannot be tracked for two distinct reasons:

**Declaration tracking is impossible:** Static and const declarations are module-level items, not local variables within function bodies. The `#[trace_borrow]` attribute is designed for function instrumentation and does not have visibility into module-level declarations. Tracking static initialization would require a separate macro approach, such as a `#[trace_static]` attribute for static declarations or a module-level `#[trace_module]` macro.

**Access tracking is impossible:** Even when code inside a traced function accesses a static variable, the macro cannot detect this. When we see an expression like `SOME_STATIC`, it is syntactically identical to accessing a local variable, a const, or even calling a function. Without type information, we cannot determine that `SOME_STATIC` refers to a static variable rather than any other kind of binding.

The runtime library provides `track_static_init`, `track_static_access`, and `track_const_eval` functions, but these cannot be automatically invoked by the macro. Users who need static tracking must manually instrument their code using these runtime functions directly.

### Async Tracking

The macro tracks async blocks and await expressions:

```rust
#[trace_borrow]
async fn fetch_data() {
    let data = async { compute() }.await;  // async block tracked
    let result = some_future.await;         // await point tracked
}
```

What the macro tracks:
- `async { ... }` blocks - entry and exit with unique block IDs
- `.await` expressions - start and end with future name extraction
- Variable creation and drops within async function bodies
- Borrows and moves

What the macro cannot track (compiler-generated):
- Future creation (the implicit `impl Future` generated by the compiler)
- Poll invocations (handled by the async runtime, not user code)
- State transitions across await points
- Waker and context interactions

Note: Async functions are transformed by the compiler into state machines after macro expansion. The macro sees the original syntax but cannot observe the generated `Poll` implementation or state machine transitions.

### Type-Dependent Behavior Detection

Several Rust patterns have behavior that depends on types rather than syntax:

- **Drop order for struct fields** - The order fields are dropped depends on declaration order, not usage
- **Implicit dereferencing** - Method calls may auto-deref through multiple layers
- **Deref coercions** - `&String` automatically coerces to `&str` in many contexts
- **Move vs Copy semantics** - Whether assignment moves or copies depends on whether the type implements `Copy`

The macro cannot detect or track these behaviors because they are determined by the type system after macro expansion. We track explicit operations visible in the syntax, but implicit compiler-inserted operations remain invisible to our instrumentation.

## Technical Background

### Analyzer Integration (Semantic Type Resolution)

The `borrowscope-macro` currently uses syntactic pattern matching (heuristics) to detect smart pointer types and ownership operations. While effective for common patterns like `Rc::new(...)` or `Arc::clone(&x)`, this approach cannot detect type aliases, factory functions, or method-syntax clones because procedural macros execute before type resolution in Rust's compilation pipeline.

The `borrowscope-analyzer` is a companion static analysis tool (currently under development, not yet published to crates.io) that leverages rust-analyzer's semantic analysis to extract complete type information. By running the analyzer as a pre-build step, it generates a `.borrowscope/type-info.json` file containing resolved types for every variable in your project. The macro then consumes this data at compile time, enabling accurate semantic classification instead of relying on heuristics.

This two-phase approach bridges the fundamental gap between macro expansion (no type info) and type checking (full type info), allowing BorrowScope to correctly track ownership even for complex patterns that syntactic detection cannot handle.

**How it works:**

1. Run `cargo run -p borrowscope-analyzer -- /path/to/project` to generate `.borrowscope/type-info.json`
2. The macro automatically loads this file at compile time
3. For each variable, it checks the analyzer's `initializer_kind` classification first
4. Falls back to syntactic detection if no analyzer data is available

**Benefits of analyzer integration:**

| Scenario | Syntactic Only | With Analyzer |
|----------|----------------|---------------|
| `type MyRc<T> = Rc<T>; let x = MyRc::new(1);` | ❌ Not detected | ✅ `rc_new` |
| `fn make_rc() -> Rc<i32>; let x = make_rc();` | ❌ Not detected | ✅ `rc_new` |
| `let x = some_rc.clone();` (method syntax) | ❌ Not detected | ✅ `rc_clone` |
| `let x: Rc<_> = other.into();` | ❌ Not detected | ✅ `rc_new` |

**Supported initializer kinds (78 semantic categories):**

- Smart pointers: `rc_new`, `rc_clone`, `arc_new`, `arc_clone`, `box_new`, `weak_new`, `weak_downgrade`
- Interior mutability: `refcell_new`, `cell_new`, `mutex_new`, `rwlock_new`, `once_cell_new`
- Guards: `mutex_lock`, `rwlock_read`, `rwlock_write`, `refcell_borrow`, `refcell_borrow_mut`
- Collections: `vec_new`, `vec_macro`, `string_new`, `hashmap_new`, etc.
- User types: `user_struct`, `user_enum`, `user_union`
- And many more...

**Disambiguation:**

The analyzer tracks function context and declaration index, enabling accurate lookup even when:
- Multiple variables share the same name (shadowing)
- The same name appears in different functions
- Variables are reassigned within a function

```rust
fn example() {
    let x = Rc::new(1);  // decl_index=0, function="example"
    let x = Rc::new(2);  // decl_index=1, function="example" (shadowed)
}
```

Procedural macros in Rust operate during an early phase of compilation, after parsing but before type checking. At this stage, the compiler has constructed an Abstract Syntax Tree (AST) representing the syntactic structure of the code, but has not yet:

1. Resolved names to their definitions
2. Inferred or checked types
3. Determined trait implementations
4. Validated borrow checker rules

This means procedural macros can see *what* code looks like syntactically, but not *what it means* semantically. A macro sees that you wrote `x.foo()`, but cannot know whether `foo` is a method on `x`'s type, a method from a trait, or will fail to compile entirely.

BorrowScope Macro works within these constraints by focusing on syntactic patterns that reliably indicate ownership operations:

- `let` bindings always create new variables
- `&` and `&mut` always create references
- `unsafe { }` blocks are syntactically distinct
- `as *const T` casts are syntactically identifiable
- Known function names like `Rc::new` or `transmute` can be pattern-matched

For operations that require type information, the only solutions would be:
- **Compiler plugins** (unstable, nightly-only)
- **External analysis tools** (like rust-analyzer integration)
- **Explicit user annotations** (additional attributes marking specific operations)

The current design prioritizes stability and usability on stable Rust, accepting these limitations in exchange for a tool that works reliably across the Rust ecosystem.

### Manual Instrumentation for Undetectable Patterns

For patterns that the macro cannot auto-detect (FFI calls, union field access, static variables), you can use the tracking functions from `borrowscope-runtime` directly. See:

- **[Limitations Guide](docs/LIMITATIONS.md)** - Detailed documentation with code examples
- **[Manual Tracking Example](examples/manual_tracking.rs)** - Complete runnable example demonstrating manual instrumentation

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](../LICENSE) for details.