# How It Works

## The Big Picture

You write:

```rust
#[redline(latency = "< 1ms", allocs = 0)]
fn handler(data: &[u8]) -> u64 { ... }
```

The Rust compiler expands `#[redline(...)]` before type checking. Redline's proc-macro runs, walks the function's syntax tree, counts operations, and either emits the function unchanged (all good) or emits `compile_error!` (bound violated).

## Step by Step

### 1. Attribute Parsing (`parse.rs`)

The attribute tokens `latency = "< 1ms", allocs = 0` are parsed into:

```
RedlineAttr {
    constraints: [
        Latency(LatencyBound { nanos: 1_000_000 }),
        Allocs(0),
    ]
}
```

Units are converted to base units: time to nanoseconds, size to bytes, throughput to bytes/sec.

### 2. AST Walking (`analyze.rs`)

The function body is a tree of expressions and statements. The analyzer walks every node:

```
fn handler(data: &[u8]) -> u64 {
    let mut sum: u64 = 0;          // Local: 8 bytes stack
    for i in 0..100 {              // ForLoop: 100 iterations
        sum += data[i] as u64;     //   Binary(+=): 1ns, Index: 1ns
    }
    sum                            // Return: 0ns
}
```

The walk produces:

```
FnCost {
    latency_ns: 8 + 100 * 2 + 1 = 209,  // stack + loop + branch
    allocs: 0,
    syscalls: 0,
    stack_bytes: 8,
}
```

### 3. Constraint Check

Each constraint is checked against the computed cost:

```
latency: 209ns < 1_000_000ns (1ms)  -> OK
allocs:  0 <= 0                      -> OK
```

All pass. The function is emitted unchanged.

### 4. When It Fails

```rust
#[redline(allocs = 0)]
fn bad() -> String {
    String::from("oops")  // Call to String::from -> allocs += 1
}
```

Walk produces `FnCost { allocs: 1, ... }`. Check: `1 > 0`. Violation.

The macro emits:

```rust
compile_error!("redline: function `bad` has 1 allocation(s), limit is 0");
fn bad() -> String { ... }  // still emitted so other errors work
```

## What Gets Counted

| AST Node | What Happens |
|----------|-------------|
| `Expr::Call` | Check function path against known allocators, syscalls, network |
| `Expr::MethodCall` | Check method name against known patterns |
| `Expr::Macro` | Check macro name (println, vec, format, etc.) |
| `Expr::ForLoop` | Extract range literal, multiply body cost by iterations |
| `Expr::While` / `Expr::Loop` | Assume 1000 iterations (pessimistic) |
| `Expr::If` | Analyze both arms, take worst-case |
| `Expr::Match` | Analyze all arms, take worst-case |
| `Expr::Closure` | Walk the closure body |
| `Expr::Binary` | 1ns (ALU) |
| Everything else | 0ns (conservative: free) |

## Why Proc-Macro?

Proc-macros run before type checking. This means:

**Upside:** No compiler fork needed. Works with stable Rust. Ships as a normal crate.

**Downside:** No type information. We can't tell if `.get()` is called on a HashMap or a slice. We match on names, which is approximate.

The tradeoff is worth it. A proc-macro that works today on stable Rust is more useful than a compiler plugin that requires nightly and custom builds.
