min-specialization 0.2.1

Experimental implementation of specialization
Documentation
# `min-specialization` โ€” Audit & Resolution

Audit of the `#[specialization]` proc-macro (`src/lib.rs`, `src/normalize.rs`,
`src/substitute.rs`), originally at commit `918cfcc`, plus the fixes applied on top.
Verified on `rustc 1.90.0` (stable) and `nightly` Miri (x86_64-unknown-linux-gnu).
Every finding was reproduced empirically; reproducers and regression tests live in
`tests/` (see [Tests](#tests)).

## How the crate works

For a default (`default fn`) impl and one or more concrete specializing impls, the
macro rewrites each default method into a runtime dispatcher that selects a branch
by comparing type identity and then reinterprets `self`/args/return for that branch.

## Direct answer to "does it handle all Rust syntax, or error cleanly when it can't?"

Originally **no on both counts** โ€” unsupported constructs produced a killed compiler
(`SIGABRT`), macro panics, or silently-wrong code. **After the fixes**, every
unsupported construct now produces a clean, spanned diagnostic, and several
previously-unsupported constructs are now handled correctly.

| Construct | Before | After |
|---|---|---|
| nested associated-type bound (`Item: Iterator<Item = u8>`) | ๐Ÿ’ฅ `SIGABRT`, compiler killed | โœ… clean error "nested associated type bindings are not supported" |
| assoc-type bound in param position (`impl<X: Iterator<Item=u8>>`) | ๐Ÿ’ฅ macro panic | โœ… **supported** (normalized like a `where` clause) |
| `mut`/tuple/wildcard argument patterns | ๐Ÿ’ฅ macro panic / `E0642` | โœ… **supported** (dispatch correct) |
| generic methods (`fn f<G>()`, `fn f<const N: usize>()`) | โ˜ ๏ธ opaque `E0282` | โœ… **supported** (generics forwarded as a turbofish) |
| overriding a method absent from the default impl | โ˜ ๏ธ silently dropped | โœ… clean error "โ€ฆ not defined in the default impl" |
| overlapping specializations (`impl Tr for i32` twice) | โ˜ ๏ธ silently accepted, nondeterministic winner | โœ… clean error "conflicting specializations" |
| default impl with no matching special | โ˜ ๏ธ leaked `default` โ†’ `E0658` | โœ… emitted as an ordinary impl |
| assoc `type`/`const` in a special impl | โœ… clean error | โœ… clean error (unchanged) |

## Findings & resolutions

Severity: ๐Ÿ”ด critical ยท ๐ŸŸ  high ยท ๐ŸŸก medium ยท โšช low โ€” Status: โœ… fixed ยท ๐ŸŸข improved ยท ๐Ÿ“ documented limitation

### Soundness

- ๐Ÿ”ด **S1/S2 โ€” function-pointer-address type identity + size/align-only `transmute`** โ†’
  โœ… **FIXED.** Dispatch now compares a **lifetime-erased `TypeId`**
  (`src/lib.rs`, `__min_specialization_type_id`): the `TypeId` of the type with its
  lifetimes erased, obtained through a trait object whose existential lifetime is
  widened to `'static` (the value is a ZST `PhantomData`, so no non-`'static` data
  is accessed). `TypeId` is collision-free, so there are **no** identical-code-folding
  false positives (S1) and **no** false negatives (S3); after a match the two types
  are identical up to lifetimes, so the transmute is an identity conversion (S2).
  *Crucially this needs no `'static` bound, so borrowed types like `&str` are still
  specializable* โ€” verified in debug, `--release`, and Miri, including on genuinely
  borrowed (non-`'static`) values.
- ๐ŸŸ  **S3 โ€” false negatives (specializations silently skipped under Miri / split
  codegen)** โ†’ โœ… **FIXED** by the same change (`TypeId` equality is reflexive).
- ๐ŸŸก **S4 โ€” release-only `assert_eq!` size/align guard** โ†’ ๐ŸŸข the assertions are now
  pure (always-true) defense-in-depth, since the matched types are provably identical.
  Specializing on a *concrete* lifetime (e.g. `impl Tr for Cell<&'static str>`) is the
  one case the lifetime-erased identity cannot police; ๐Ÿ“ like real `min_specialization`,
  do not specialize on a specific lifetime.

### Robustness โ€” panics/aborts โ†’ clean diagnostics

- ๐Ÿ”ด **P1 โ€” `std::process::abort()` (SIGABRT) on nested assoc-type bounds** โ†’
  โœ… **FIXED.** The unsafe `catch_unwind`/`process::abort()` machinery in
  `src/normalize.rs` is gone (replaced with a safe `mem::take`); nested bindings now
  `abort!` with a spanned message.
- ๐ŸŸ  **P2 โ€” panic on assoc-type bound in parameter position** โ†’ โœ… **FIXED + supported**
  (`normalize_generic_param` now delegates to the `where`-clause normalizer).
- ๐ŸŸ  **P3/P4 โ€” macro panic / `E0642` on `mut`/tuple argument patterns** โ†’ โœ… **FIXED**:
  outer dispatcher arguments are freshly named and forwarded; inner-trait *declarations*
  are made pattern-free while the implementing method keeps the original patterns/body.
- ๐ŸŸก **P5 โ€” generic methods โ†’ opaque `E0282`** โ†’ โœ… **FIXED + supported**: a method's
  own type/const generic parameters are forwarded to the inner dispatch as an explicit
  turbofish, which pins them across the transmute (verified debug/release/Miri).
- ๐ŸŸก **P6 โ€” `parse2(...).unwrap()` catch-all panic** โ†’ โœ… now `abort!`s with the parse
  error, a span, and a "please report" note.
- ๐ŸŸก **P7 โ€” `_ => panic!()` on unsupported `where`-predicate** โ†’ โœ… spanned `abort!`.
- โšช **P8 โ€” `or_insert_with(|| unreachable!())`** โ†’ โœ… removed (dead after the rewrite).

### Algorithm & determinism

- ๐Ÿ”ด **A1/A6 โ€” non-reproducible builds / nondeterministic dispatch** (`HashSet<ItemImpl>`
  + `HashMap` + `min_by_key` tie-break) โ†’ โœ… **FIXED**: impls are kept in source order
  (`Vec`s); ties go to the first matching default. Codegen is now byte-for-byte
  deterministic (verified across repeated expansions).
- ๐Ÿ”ด **A2/A3 โ€” no overlap detection** โ†’ ๐ŸŸข **IMPROVED**: literally overlapping
  specializations (same trait + same self type) are now rejected with a clean error.
  ๐Ÿ“ a full specialization *lattice* (e.g. ordering `Vec<i32>` before `Vec<T>`) is still
  not modelled (A5).
- ๐ŸŸก **A4 โ€” silent dedup of identical impls** โ†’ โœ… no longer deduped (now distinct
  impls โ†’ a normal `E0119` if truly identical).

### Feature gaps

- ๐ŸŸ  **F1 โ€” specialization must restate the default's bounds** โ†’ ๐ŸŸข **IMPROVED**: no
  longer leaks `default`/`E0658`; surfaces as a clean `E0119` coherence error. ๐Ÿ“
  inferring/relaxing bound restatement remains a limitation.
- ๐ŸŸ  **F3 โ€” specialized method absent from the default impl silently dropped** โ†’
  โœ… clean error.
- โœ… **Lifetime-bearing specializations** (`E0261` on the specialization's own
  lifetimes) โ†’ **FIXED + supported**: a trait lifetime parameter (`trait Tr<'a>`), a
  specialized self type carrying a lifetime (`impl<'a> .. for Foo<'a>`), and a trait
  type-parameter fixed to a borrowed type (`impl<'a> Tr<&'a str> for ..`) all work.
  The dispatcher erases lifetimes to `'static` for the (lifetime-agnostic) type-id
  check and uses inferred (`'_`) lifetimes at the call so the transmute stays an
  identity; the inner impl declares self-type lifetimes and promotes method-only
  lifetimes to method-level generics.
- โœ… **Generic associated types (GATs)** in the specialized trait โ†’ supported (no
  extra code beyond the lifetime/generic-method handling above): a GAT with a
  lifetime parameter (`type Ref<'a>`), a type parameter (`type Wrap<X>`), or both
  with `where` bounds, declared by the default impl and with the *method* overridden
  in specializations. Verified debug/release/Miri. (Overriding the GAT itself in a
  specialization falls under the assoc-type-specialization limitation below.)
- ๐Ÿ“ const-generic specialization, fully-generic special impls (`impl<U> Tr for Vec<U>`),
  a reference *blanket* default specialized by a concrete reference
  (`impl<'a, T> Tr for &'a T` + `impl Tr for &'a str`), impl-level `default impl`, and
  `default type`/`default const` remain unsupported (compile errors, not silent
  miscompiles).

### Testing & CI

- ๐Ÿ”ด **T1 โ€” integration tests ran zero `#[test]`s** โ†’ โœ… `tests/runtime_dispatch.rs`
  asserts real dispatch (debug/release/Miri).
- ๐ŸŸก **CI** โ†’ ๐ŸŸข `.github/workflows/rust.yml` now also runs `--release`, `clippy`, and a
  Miri job.

## Tests

| File | Purpose |
|---|---|
| `tests/runtime_dispatch.rs` | correct dispatch incl. `&str`, `mut`/tuple/wildcard args; passes debug/release/Miri |
| `tests/audit_soundness.rs` | soundness regression: distinct same-layout types never confused; sound `&str` specialization |
| `tests/compile_fail.rs` + `tests/compile_fail/` (`trybuild`) | every unsupported construct emits a clean, spanned diagnostic |

```sh
cargo test
cargo test --release
cargo +nightly miri test --test audit_soundness --test runtime_dispatch
```