type-lang 1.0.0

Type representation, unification, and inference scaffolding.
Documentation
# type-lang v1.0.0 — Stable

**The public API is frozen.** v1.0.0 ratifies the surface built in `0.2.0` — the
type representation, inference variables, and first-order unification — as the `1.0`
contract, with a recorded Semantic Versioning promise. It is not a feature release:
the only behavioural change is a soundness fix found in pre-release review, alongside
hardening of the unifier's variable handling and a documentation pass. From here, the
public surface will not change in a breaking way before `2.0`.

## What is type-lang?

The type-system substrate of a compiler front-end. It owns the type representation
and the unification/substitution machinery, and nothing else — it does no parsing and
renders no diagnostics. It assigns types to a syntax tree the caller walks, and its
structured errors map cleanly onto a rendered diagnostic in the caller's own type
names. It is one of the semantic crates of the `-lang` language-construction family.

## The stability promise

As of `1.0.0` the public API in [`docs/API.md`](https://github.com/jamesgober/type-lang/blob/main/docs/API.md)
is stable and follows [Semantic Versioning](https://semver.org):

- No public item is **removed or changed in a breaking way** within `1.x`. Breaking
  changes wait for `2.0`.
- New functionality arrives in **minor** releases and is additive. `TypeError` is
  `#[non_exhaustive]`, so a new failure variant is a minor change, not a breaking one.
- Bug fixes, documentation, and internal optimisation are **patch** releases. The
  internal representation — how the substitution is stored, whether variable
  resolution is path-compressed — is not part of the contract and may change in a
  patch without affecting any documented behaviour.
- The **MSRV** is Rust `1.85`; raising it is a minor change, never a patch.
- The **`serde` wire format** of `Type`, `TyVar`, `TyCon`, and `Unifier` is part of
  the contract within `1.x`.

## What changed since 0.2.0

### Soundness fix — an unseen variable now binds soundly

`Unifier::unify` is documented to leave its two arguments resolving to the same type
on success. A pre-release review found one path that broke that promise: binding a
variable the unifier had not minted (one carried in from elsewhere) silently did
nothing and still returned `Ok`, so unification reported success while leaving the
two types unequal.

The fix makes the variable space total: a `Unifier` tracks bindings across the whole
32-bit id range, treats an id it has not seen as an unbound variable, and binds it —
growing its table if needed — when unification requires it. The postcondition now
holds for every input, and a regression test covers the case.

```rust
use type_lang::{TyCon, Type, Unifier};

const INT: TyCon = TyCon::new(0);

let mut origin = Unifier::new();
let v = origin.fresh(); // minted by `origin`

let mut other = Unifier::new(); // has no variables of its own
other.unify(&Type::var(v), &Type::con(INT)).expect("binds soundly");

// unify's postcondition holds: both sides resolve to the same type.
assert_eq!(other.resolve(&Type::var(v)), Type::con(INT));
```

In ordinary single-unifier use — every variable taken from the same unifier's
`fresh` — no index is ever out of range and nothing about the behaviour changes.

### Hardening and documentation

`Unifier::fresh` now asserts the 32-bit variable-id bound in debug builds, and the
`TyVar` documentation describes the id-space contract the API actually upholds rather
than one it does not. The engineering directives were corrected to match the
implementation: unification and the occurs check are iterative (so the unification
work does not grow the call stack with term depth), and no unimplemented performance
mechanism is claimed.

## Breaking changes

**None to the public API.** Every type, method, and signature from `0.2.0` is
unchanged. The soundness fix alters behaviour only for an input that previously
produced an incorrect result, so no correct program depending on `0.2.0` regresses.

## Verification

```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 +1.85 build --all-features
cargo audit
cargo deny check
```

All green on Linux (WSL2 Ubuntu) and Windows x86_64, Rust stable 1.95 and MSRV 1.85.
Counts at this tag: 33 unit and integration tests plus 19 doctests on default
features; 36 plus 19 with `--all-features` (the serde round-trips). The property
suite covers soundness, symmetry, idempotent resolution, reflexivity, and the occurs
check; the `criterion` suite shows unification scaling with term size as expected.

## What's next

The `1.x` line is maintenance and additive growth: documentation, internal
optimisation (path compression is a candidate, and an internal-only change under the
SemVer promise), and any additive helpers a real consumer proves it needs. Wiring
`ast-lang`, `symbol-lang`, and `diag-lang` remains the consumer's to make until a
pass inside this crate needs them — see `dev/ROADMAP.md`.

## Installation

```toml
[dependencies]
type-lang = "1.0"
```

MSRV: Rust 1.85.

## Documentation

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

---

**Full diff:** [`v0.2.0...v1.0.0`](https://github.com/jamesgober/type-lang/compare/v0.2.0...v1.0.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/type-lang/blob/main/CHANGELOG.md#100---2026-06-29).