# 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).