pass-lang 1.0.0

Optimization/transform pass manager with a plugin seam.
Documentation
# pass-lang v0.2.0 — The Pass Manager

**The core, and the hard part of the roadmap.** v0.2.0 turns the scaffold into a
working pass manager: a generic pipeline that orders passes, runs them over a unit
of compilation, iterates to a fixpoint, and reports what happened. The surface is
deliberately small — one trait, one manager, a report, an outcome, and an error —
and it is generic over the unit, so the manager binds to no concrete IR and wires
no first-party dependency.

## What is pass-lang?

The pass manager: it orders optimization and transform passes and runs them over a
unit of compilation, and it is the plugin seam capability crates register their
passes into. It is generic over the unit a pass rewrites — an intermediate
representation, a single function, a module, an abstract syntax tree, or a struct
bundling an IR with the diagnostics and analysis a pass needs. A consumer brings
the unit, implements one trait per transform, registers the transforms in order,
and runs the pipeline; the manager schedules and runs, and never inspects the unit
itself. It is one of the SEMA-tier crates of the `-lang` language-construction
family.

## What's new in 0.2.0

### `Pass<T>` — the plugin seam

A pass is a transform over a unit of type `T`: it names itself and rewrites the
unit in place, reporting an [`Outcome`](#outcome--the-honest-change-signal) or
failing with a `PassError`. The trait is two methods, object-safe, and `'static`,
so any crate can contribute a pass without the manager knowing its concrete type.

```rust
use pass_lang::{Outcome, Pass, PassError};

struct DropZeros;
impl Pass<Vec<i64>> for DropZeros {
    fn name(&self) -> &'static str { "drop-zeros" }
    fn run(&mut self, unit: &mut Vec<i64>) -> Result<Outcome, PassError> {
        let before = unit.len();
        unit.retain(|&x| x != 0);
        Ok(Outcome::from_changed(unit.len() != before))
    }
}
```

### `PassManager<T>` — the ordered pipeline

The manager holds passes in registration order — `add` is the plugin seam — and
runs them two ways. `run` makes a single sweep; `run_to_fixpoint` repeats the sweep
until a full pass changes nothing, or an iteration bound is reached. The bound
guarantees termination even when a pass oscillates, so a pipeline can be driven to
completion without the risk of an unbounded loop.

```rust
use pass_lang::PassManager;
# use pass_lang::{Outcome, Pass, PassError};
# struct DropZeros;
# impl Pass<Vec<i64>> for DropZeros {
#     fn name(&self) -> &'static str { "drop-zeros" }
#     fn run(&mut self, unit: &mut Vec<i64>) -> Result<Outcome, PassError> {
#         let before = unit.len();
#         unit.retain(|&x| x != 0);
#         Ok(Outcome::from_changed(unit.len() != before))
#     }
# }

let mut pm = PassManager::new();
pm.add(DropZeros);

let mut unit = vec![0, 1, 0, 2];
let report = pm.run_to_fixpoint(&mut unit, 16).unwrap();
assert_eq!(unit, vec![1, 2]);
assert!(report.converged());
```

### `Outcome` — the honest change signal

Every run reports `Changed` or `Unchanged`. The manager takes the value at its
word: a full sweep with no `Changed` is a fixpoint, and that is what stops the
loop. Reporting `Unchanged` after a mutation would break termination, so the
contract is explicit — keep it honest.

### `Report` — observability

A run returns a `Report`: every pass execution in order (`runs`), how many reported
a change (`changes`), how many sweeps ran (`iterations`), and whether the pipeline
settled (`converged`). It is the hook for logging and inspecting a pipeline, and
behind the `serde` feature it serializes.

### `PassError` — contained failure

A pass that cannot proceed returns a `PassError` carrying a human-readable reason,
never a panic. The manager stamps in the name of the failing pass and stops the
pipeline, so a caller always knows which pass halted the run and why. The reason is
a `Cow<'static, str>`, so a static message costs no allocation.

### Examples, benchmarks, and property tests

Two runnable examples ship: `expr_optimizer` (constant folding and algebraic
simplification driven to a fixpoint, including the overflow error path) and
`text_normalizer` (the same machinery over a `String`). Criterion benchmarks cover
`run` and `run_to_fixpoint`. Property tests hold the manager to the invariants in
`dev/DIRECTIVES.md`: determinism, fixpoint termination, fixpoint stability, and a
report that accounts for every pass that ran.

### Dependency wiring — none, by design

No first-party dependency is wired, and none is planned. A pass manager is a
generic scheduling engine; coupling it to one concrete IR would bind it to a single
layer and pull in a dependency the scheduler never reads. The manager is generic
over the unit instead — the same self-contained precedent the sibling SEMA crates
set. The reasoning is recorded in `dev/ROADMAP.md` under the anti-deferral rule.

## Breaking changes

**None.** v0.1.0 exposed no public API; everything in 0.2.0 is new surface.

## Verification

Run on Windows x86_64 and Linux (WSL2 Ubuntu), Rust stable 1.95 and the 1.85 MSRV;
identical commands run in the configured CI matrix:

```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo build --examples
cargo build --no-default-features
cargo +1.85 build --all-features
cargo deny check
cargo audit
```

All green. Counts at this tag:

- Default features: 27 unit + 4 integration + 3 property + 38 doctests. The
  doctests include every `rust` example in `README.md` and `docs/API.md`, wired
  into `cargo test` so the published examples cannot drift from the API.
- `--all-features`: adds 2 `serde` serialization tests.

Benchmark baselines (Linux, WSL2 Ubuntu, release profile): a single three-pass
sweep over a one-thousand-element unit runs in ~1.0 µs; a fixpoint that decrements
256 elements to zero across 33 sweeps runs in ~2.0 µs. The manager's own per-pass
overhead is a single indirect call — the figure is dominated by the work the passes
do, which is the intent.

## What's next

- **1.0.0 — API freeze.** Freeze the 0.2.0 surface as the 1.0 contract, mark
  `docs/API.md` stable with a SemVer promise, and confirm the full test and
  benchmark suite on all three platforms. Analysis passes with cached, invalidatable
  results and inter-pass dependency declaration are additive later-minor work, not a
  1.0 requirement.

## Installation

```toml
[dependencies]
pass-lang = "0.2"
```

MSRV: Rust 1.85.

## Documentation

- [README]https://github.com/jamesgober/pass-lang/blob/main/README.md
- [API Reference]https://github.com/jamesgober/pass-lang/blob/main/docs/API.md
- [CHANGELOG]https://github.com/jamesgober/pass-lang/blob/main/CHANGELOG.md

---

**Full diff:** [`v0.1.0...v0.2.0`](https://github.com/jamesgober/pass-lang/compare/v0.1.0...v0.2.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/pass-lang/blob/main/CHANGELOG.md#020---2026-06-30).