Gradual types for Nix.
Add types where they help, keep the rest dynamic, and ship plain .nix with zero runtime.
tynix is a gradual type system and tooling stack for Nix. It compiles .tynix to .nix, provides static checking, and emits .d.tynix declaration files. It has no runtime and is intentionally limited to complementing existing Nix semantics rather than replacing them.
- Add types to
.nixwithout breaking its syntax or culture - Allow ambient declarations for existing
.nixfiles - Adopt the parts of the TypeScript strategy that worked
- incremental adoption
- semantic preservation
- strong editor tooling
- Be expressive enough for type-level programming
- parametric polymorphism
- higher-kinded types
- conditional types
infer-style type decomposition
- Use structural subtyping and
dynamic,unknown, andanyas the core gradual typing tools
.tynix- source files with type annotations
- compiled to
.nix
.d.tynix- declaration-only ambient files
- used to type existing
.nixmodules and external code
.nix- runtime artifact
tynixdoes not change its behavior
type Option a = { _tag :: "some"; value :: a; } | { _tag :: "none"; };
declare "./legacy/default.nix" {
mkPkg :: { name :: String; version :: String; } -> Derivation;
};
let
some :: forall a. a -> Option a;
some = value: { _tag = "some"; inherit value; };
labelOf = pkg: { label = pkg.name; };
legacy = import ./legacy/default.nix;
in
{ name ? "hello", version, ... }@args:
{
package = legacy.mkPkg { inherit name version; };
tag = some (labelOf args).label;
}
tynix check infers labelOf :: forall t0. { name :: t0; ... } %1 -> { label :: t0; }
(a row-polymorphic record argument), types the file's argument as
{ name? :: String; version :: String; ... }, and checks the call to the
untyped legacy/default.nix against its declaration. Derivation comes from
the built-in prelude that types every Nix builtin. After compilation, type
information is erased and only ordinary Nix code remains.
- Getting Started
- Tutorial
- Editor Setup
- Adopting tynix (Migration)
- Troubleshooting
- Language Reference
- Grammar
- Language Design
- Type System
- Architecture
- Roadmap
- Contributing
- Governance
- Security Policy
- Support Matrix
- CI/CD Hardening
- Diagnostic Codes
tynix is now in its first integrated toolchain release.
- Haskell monorepo for parser, checker, compiler, emitter, CLI, and LSP
- Nix-based development environment
pnpm-managed editor tooling- VS Code, Cursor, VSCodium, Zed, Neovim, and Helix integrations, installed with
tynix ide install <editor>and diagnosed withtynix doctor - full Nix expression syntax: attrset patterns with defaults and
@binders,inherit (src), nested and dynamic attribute paths,ordefaults, every operator including/,->,|>and<|,<nixpkgs>and interpolated paths; compiled output parses to the same AST as the source undernix-instantiate --parse - Hindley-Milner let-polymorphism, row-polymorphic open records
(
{ a :: T; ... }), optional fields (name? :: T),AttrsOfdictionaries, and rigidforallsignatures - gradual typing with ambient declarations, HKT support, indexed
Vec/Matrix/Tensor, and heterogeneousTuple - numeric singleton/primitive support via
Float,Number,Nat,Range, andUnit - explicit
expr as Typecasts for widening, narrowing, and gradual-boundary assertions - TypeScript-style checker directives via
# @tynix-ignoreand# @tynix-expected - diagnostics with exact source spans (
line:col: [CODE] message), naming the missing or ill-typed field of a record - a typed prelude for every Nix builtin, embedded in the binary, so
builtins.*and globals such astoStringandmapare checked with no setup - project bootstrapping via
tynix init,tynix scaffold, andtynix.config.tynix - shipped declaration files for
builtins,flake.nix, andtynix.config.tynix - bundled declaration packs under
registry/for workspace files and popular Nix ecosystem surfaces
Unannotated Nix code is accepted gradually: dependencies injected through an
attrset pattern ({ lib, fetchFromGitHub, ... }:) stay dynamic instead of
being pinned to their first use, and about 98% of a sample of unannotated
nixpkgs files type-check as they are.
On Linux and macOS, install the prebuilt CLI and language server with:
curl -fsSL https://tynix.dev/install.sh | shThe installer picks the archive for your platform (Linux x64/arm64, macOS
arm64/x64), verifies its SHA-256 checksum, and installs tynix and tynix-lsp
into ~/.tynix/bin. The binaries do not depend on Nix. Pin a release with
curl -fsSL https://tynix.dev/install.sh | TYNIX_VERSION=0.5.0 sh, choose
another directory with TYNIX_INSTALL_DIR, and remove everything with
curl -fsSL https://tynix.dev/install.sh | sh -s -- --uninstall.
With Nix, use the flake instead:
nix profile install github:ubugeeei-prod/tynix # tynix + tynix-lsp
nix run github:ubugeeei-prod/tynix -- check ./main.tynixThe flake also provides overlays.default and NixOS / nix-darwin / Home Manager
modules (programs.tynix.enable = true;). See
docs/getting-started.md for details.
You can also download the archives (tynix-<version>-<target>.tar.gz, with
.sha256 checksums and build provenance attestations) from GitHub Releases.
See docs/support-matrix.md for the per-platform
tier table. Windows users: the CLI is not tested on Windows. Use WSL2 and
run the install script there.
Quick verification:
tynix --version
tynix-lsp --version
tynix check ./examples/main.tynix
tynix check-project ./examplesOne command installs the editor extension and writes the editor config:
tynix ide install vscode # or: cursor, vscodium, zed, neovim, helix
tynix ide install zed --global --dry-run # preview user-level changes
tynix ide list # supported editors and what is detected
tynix doctor # check tynix / tynix-lsp / project / editor wiringSettings are merged, never clobbered, and re-running is a no-op. See docs/editors.md for what each editor gets and for every flag.
For local development, enter the reproducible shell first:
nix develop
nix flake check --accept-flake-config
pnpm run check
vp run workspace:check
vp cli
vp idenix flake check now exercises the published flake outputs, version metadata,
smoke-tests the built tynix / tynix-lsp binaries, runs the Haskell package
test suites, and validates the dogfood/example corpus with the packaged CLI.
pnpm run check is the conventional npm-compatible entrypoint and delegates to
the full workspace verification suite, including editor integrations.
vp run workspace:check is the direct task-runner entrypoint.
vp cli installs the local tynix / tynix-lsp toolchain into your active Nix
profile. vp ide reuses that toolchain install, packages the VS Code
extension, and installs the local Zed extension when its support directory is
available.
The repository includes reusable .d.tynix packs under registry/ so projects
can vendor common declarations instead of rewriting ambient files from scratch.
Workspace-oriented packs live under registry/workspace/:
registry/workspace/builtins.d.tynixregistry/workspace/flake.d.tynixregistry/workspace/tynix.config.d.tynix
Ecosystem alias packs live under registry/ecosystem/:
registry/ecosystem/nixpkgs-lib.d.tynixregistry/ecosystem/nixpkgs-pkgs.d.tynixregistry/ecosystem/flake-ecosystem.d.tynixregistry/ecosystem/community-flakes.d.tynix
Typical usage is to copy the pack you want into your declaration directory and
reuse its aliases from local declare blocks:
declare "./flake-utils.nix" { default :: NixFlakeUtilsFlake; };
declare "./devenv.nix" { default :: DevenvFlake; };
declare "./treefmt-nix.nix" { default :: TreefmtNixFlake; };
Projects can also point tynix.config.tynix at external pack files or
directories directly:
{
declarationPacks = [
../vendor/tynix/registry/ecosystem
../vendor/tynix/registry/workspace
];
}
When a configured pack comes from registry/workspace/, tynix rebases its
ambient declare targets to your project root so upstream workspace packs can
be used without copying them into the repo first.
The workspace packs assume they live under registry/workspace/ so their
relative declare targets resolve back to the project root.
The repository ships a larger sample set under examples/.
It includes basic language features, gradual typing examples, indexed container
samples, and legacy interop fixtures that can be checked with one command:
tynix check-project ./examplesSee CHANGELOG.md for the release history.
The primary distribution channel is GitHub Releases. Tagged releases publish
prebuilt tynix and tynix-lsp archives for supported platforms together with
checksums, plus a packaged VS Code .vsix extension. When marketplace tokens
are configured, the same tag also publishes the extension to VS Code
Marketplace and Open VSX.
The flake also exports installable packages and runnable apps:
nix build github:ubugeeei-prod/tynix#tynix
nix run github:ubugeeei-prod/tynix#tynix -- check ./main.tynix
nix run github:ubugeeei-prod/tynix#tynix-lsp
nix flake check github:ubugeeei-prod/tynix --accept-flake-configSee RELEASING.md for the release flow.