Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nix-ast

A Nix library that parses, inspects, transforms, and evaluates Nix Abstract Syntax Trees (AST) using Haskell’s hnix parser.

nix-ast exposes a full Nix AST API directly in Nix itself (via IFD, Import From Derivation) and uses hnix’s Haskell parser and evaluator internally. You can parse any Nix expression into a structured AST, traverse and transform it with pure Nix functions, render it back to source code, and even evaluate it. All from within your Nix expressions.


Features

FeatureDescription
ParseParse any Nix expression (file, string, or stdin) into a structured, typed AST
RenderConvert an AST back to formatted, importable Nix code
EvalEvaluate ASTs using hnix’s evaluator and get the results as JSON values
toASTConvert native Nix values (ints, strings, lists, attrsets) to AST nodes (pure, no IFD)
fromASTConvert an AST back to a native Nix value, the inverse of toAST (pure, no IFD)
Pattern MatchingType-safe tag-based dispatch with match ast { Tag = handler; _ = fallback; }
TraversalRecursive transform, rewrite, universe, children/rebuild, and contexts
Type-checked ConstructorsAll mk* builders validate argument types at runtime with descriptive error messages
CLI Toolnix-ast eval / nix-ast parse / nix-ast render for shell/pipe workflows

Quick Start

Using the Nix Library (Flake)

Add nix-ast to your flake inputs:

inputs.nix-ast.url = "github:z1-0/nix-ast";

The library exposes three main API groups via inputs.nix-ast.lib:

APIDescription
lib.parse lib.render lib.evalIFD-based: bridge to hnix’s parser/evaluator
lib.toAST lib.fromASTPure Nix: convert between Nix values and ASTs
lib.match lib.syntax lib.traversalWork with AST nodes after construction
# Parse -> Transform -> Render
asts = lib.parse pkgs [./config.nix];
transformed = lib.traversal.transform (node: ...) (builtins.head asts);
outPaths = lib.render pkgs [transformed];
config = import (builtins.head outPaths);

# Parse -> Evaluate (skip rendering)
result = lib.eval pkgs asts;

CLI

# Parse from expression string
nix-ast parse --expr '{ x = 1; }' > ast.json

# Parse files from stdin (one path per line)
echo ./config.nix | nix-ast parse > ast.json

# Render AST back to Nix
nix-ast render --json '{"tag":"Set","recursive":false,"bindings":[...]}'

# Render batch to output directory
nix-ast render < asts.json --out-dir ./out

# Evaluate AST using hnix
nix-ast eval --json '{"tag":"Constant","contents":{"tag":"Int","contents":42}}'

# Pipe workflow: parse -> eval
nix-ast parse --expr '{ x = 1 + 2; }' | nix-ast eval

Architecture

┌─────────────────────────────────────────────────────────┐
│                     Nix (your flake)                     │
│                                                         │
│  lib.parse   lib.render   lib.eval                      │
│       │           │           │                         │
│       ▼           ▼           ▼                         │
│  ┌──────────────────────────────┐                       │
│  │  IFD: nix-ast CLI (Haskell)  │  <- hnix parser/eval  │
│  └──────────────────────────────┘                       │
│                                                         │
│  lib.toAST  lib.fromAST  (pure Nix, no IFD)             │
│  lib.match  lib.syntax  lib.traversal                   │
└─────────────────────────────────────────────────────────┘

parse/render/eval go through Haskell via IFD. toAST/fromAST/match/syntax/traversal run in pure Nix on the AST values.

Installation

# Run directly without installing
nix run github:z1-0/nix-ast -- parse --expr '{ x = 1; }'

# Or add to your flake inputs
inputs.nix-ast.url = "github:z1-0/nix-ast";

Binary Cache

Use the Cachix cache to skip building from source.

nix.settings = {
  substituters = [ "https://z1-0.cachix.org" ];
  trusted-public-keys = [ "z1-0.cachix.org-1:FS7lPgL0StRBOPrlu0RgdCL7LafUI23+U6Iivdw5QK8=" ];
};

Learn more

  • API Reference: parse, render, eval, construct, match, and traverse AST nodes
  • AST Reference: every node type with fields, JSON representation, and examples
    • Expr: 19 expression constructors
    • Atom: primitive constants (Bool, Int, Float, Null, Uri)
    • Binding: Inherit, NamedVar
    • KeyName: StaticKey, DynamicKey
    • Params: Param, ParamSet
    • String: DoubleQuoted, Indented
    • Antiquoted: Plain, Antiquoted, EscapedNewline
  • CLI Reference: nix-ast eval / nix-ast parse / nix-ast render

License

BSD-3-Clause: see LICENSE for details.

API Reference

Functions for parsing, constructing, matching, and transforming Nix AST nodes.

Overview

The API is organized into three layers:

LayerFunctionsDescription
IFD bridgeparse, render, evalBridge to the Haskell parser/evaluator via Import From Derivation
Value conversiontoAST, fromASTPure Nix conversion between Nix values and AST nodes
AST toolsmatch, syntax, traversalPattern matching, constructors, and tree operations

Pages

PageDescription
Core Functionsparse, render, eval, toAST, fromAST
syntax: Constructors & Predicatesmk* builders and is* tag checks for all node types
match: Pattern MatchingType-safe tag dispatch with match ast { ... }
traversal: Tree Operationschildren, rebuild, transform, universe, contexts

How IFD Works

parse, render, and eval use pkgs.runCommand to create a derivation that:

  1. Serializes the input (paths, ASTs) to a JSON file via pkgs.writeText
  2. Runs the nix-ast CLI binary inside the derivation, piping the JSON file to stdin
  3. Captures stdout as the derivation output
  4. Reads back the output with builtins.readFile + builtins.fromJSON

These functions have IFD (Import From Derivation) semantics: they work in nix build and nix eval but are unavailable in restricted evaluation modes like nix-instantiate --eval.

Quick Example

# Parse, transform, and render
asts = lib.parse pkgs [./file.nix];
transformed = lib.traversal.transform (node:
  if lib.syntax.isConstant node && lib.syntax.isInt node.contents then
    lib.syntax.mkConstant (lib.syntax.mkInt (node.contents.contents * 2))
  else node
) (builtins.head asts);
outPaths = lib.render pkgs [transformed];
builtins.head outPaths

See also CLI Reference for the nix-ast eval / nix-ast parse / nix-ast render command-line tool.

AST Reference

Complete reference for every node type in the Nix AST, organized by type hierarchy as defined in Types.hs.

Quick Overview

The AST is a sum type with 19 expression constructors and supporting types for atoms, bindings, keys, parameters, strings, and antiquoted parts. Every node has a tag field (a string identifying the constructor) plus constructor-specific fields.

Node Types

TypeDescription
ExprThe main expression tree, with 19 constructors
AtomPrimitive constant values (Bool, Float, Int, Null, Uri)
VarNameIdentifier name (simple string)
AttrPathNon-empty list of KeyName for attribute paths
BindingLeft-hand side of attribute bindings (Inherit, NamedVar)
KeyNameAttribute path components (StaticKey, DynamicKey)
OperatorsBinary and unary operators
ParamsFunction parameter definitions (Param, ParamSet)
StringString literal contents (DoubleQuoted, Indented)
AntiquotedString parts with interpolation

JSON Representation

Every node is serializable to/from JSON. The tag field identifies the constructor:

{ "tag": "Constant", "contents": { "tag": "Int", "contents": 42 } }

This is the format used by the CLI for nix-ast parse output and nix-ast render input.

Quick Start

# Parse Nix code into AST
asts = lib.parse pkgs [./example.nix];
ast = builtins.head asts;

# Inspect the expression type using match
lib.match ast {
  Set = { recursive, bindings }: "attribute set";
  List = { contents }: "list of ${toString (builtins.length contents)} items";
  _ = _: "other expression";
};

# Transform all integer constants: double their value
transformed = lib.traversal.transform (node:
  if node.tag == "Constant" && node.contents.tag == "Int"
  then lib.syntax.mkConstant (lib.syntax.mkInt (node.contents.contents * 2))
  else node
) ast;

# Render back to Nix code
outPaths = lib.render pkgs [transformed];
builtins.head outPaths

CLI

nix-ast - Nix AST tool

Usage: nix-ast COMMAND [-v|--version]

  nix-ast: Parse and generate Nix expressions

Available options:
  -h,--help                Show this help text
  -v,--version             Show version

Available commands:
  eval                     Evaluate AST JSON and output result
  parse                    Parse Nix expression to AST JSON
  render                   Render AST JSON to Nix source

The CLI has three subcommands: eval, parse, render. Each operates in two modes:

  • Direct mode: supply input via --expr or --json flag for single-item operation
  • Batch mode: pipe a JSON array through stdin for bulk processing (one item per array element)

When stdin is a TTY and no direct flag is given, the CLI shows the subcommand’s help text instead of hanging.

Eval

Evaluate AST values and output the results as JSON. This is the CLI equivalent of nix eval but operates on AST JSON rather than source code.

Usage: nix-ast eval [--json JSON]

  Evaluate AST JSON and output result

Available options:
  --json JSON              AST in JSON format
  -h,--help                Show this help text

Modes:

ModeFlagInput sourceOutput
Direct--json JSONSingle AST as JSON stringSingle JSON value to stdout
Batch (stdin)(none)[AST] JSON array on stdin[a] JSON array to stdout

How it works: Each AST is deserialized, evaluated using the built-in evaluator with basic filesystem effects, and the result is serialized to JSON.

Error handling: Parse errors, conversion errors, evaluation errors, and IO exceptions are all caught and reported with appropriate prefixes (“Decode error”, “Conversion error”, “Eval error”).

Examples

# Evaluate a single AST from a JSON string
nix-ast eval --json '{"tag":"Set","recursive":false,"bindings":[{"tag":"NamedVar","attrPath":[{"tag":"StaticKey","contents":"x"}],"value":{"tag":"Constant","contents":{"tag":"Int","contents":1}}}]}'

# Pipe ASTs from a file (JSON array, one element per line in the array)
cat asts.json | nix-ast eval

# Pipe from parse: parse then evaluate
nix-ast parse --expr '{ x = 1 + 2; }' | nix-ast eval

Parse

Parse Nix source code into AST JSON.

Usage: nix-ast parse [--expr EXPR]

  Parse Nix expression to AST JSON

Available options:
  --expr EXPR              Nix expression string
  -h,--help                Show this help text

Modes:

ModeFlagInput sourceOutput
Direct--expr EXPRSingle Nix expression stringSingle AST JSON to stdout
Batch (stdin)(none)[FilePath] JSON array on stdin[AST] JSON array to stdout

How it works:

  • Direct mode: The expression string is parsed into an AST, converted to our Expr type, and serialized to JSON.
  • Batch mode: Stdin is read as a JSON array of file paths. Each file is read concurrently using mapConcurrently with a semaphore limiting concurrency to 50. Each file is parsed separately; if any file fails, the entire command exits with an error.

Examples

# Parse a single expression from string
nix-ast parse --expr '{ x = 1; }' > ast.json

# Parse multiple files from stdin (one path per JSON array element)
echo '["/path/to/a.nix", "/path/to/b.nix"]' | nix-ast parse > asts.json

# Pipe from jq for dynamic path selection
nix build 2>&1 | grep "error:" | jq -R -s -c 'split("\n") | map(select(length > 0))' | nix-ast parse

Output Format

The AST is a JSON object with a tag field identifying the node type, plus type-specific fields:

{
  "tag": "Set",
  "recursive": false,
  "bindings": [
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "x" }],
      "value": {
        "tag": "Constant",
        "contents": { "tag": "Int", "contents": 1 }
      }
    }
  ]
}

For a complete reference of all node types with their JSON shapes, see the AST Reference.

Render

Convert AST JSON back to formatted Nix source code.

Usage: nix-ast render [--json JSON] [--out-dir DIR]

  Render AST JSON to Nix source

Available options:
  --json JSON              AST in JSON format
  --out-dir DIR            Output directory for rendered files (default: stdout)
  -h,--help                Show this help text

Modes:

ModeFlagInput sourceOutput
Direct--json JSONSingle AST as JSON stringFormatted Nix source to stdout
Batch (stdin)(none), no --out-dir[AST] JSON array on stdin[Text] JSON array to stdout
Batch to disk(none), --out-dir DIR[AST] JSON array on stdinFiles 0.nix, 1.nix, … in DIR

How it works: Each AST is deserialized and pretty-printed back to formatted Nix source code.

  • Direct mode (--json only, no --out-dir): Single AST → single line of Nix source to stdout.
  • Batch to stdout (stdin only, no --out-dir): Array of ASTs → JSON array of Nix source strings to stdout.
  • Batch to disk (stdin + --out-dir): Array of ASTs → individual files named <index>.nix in the specified directory. Files are numbered sequentially starting from 0.
  • Forbidden combination: --json + --out-dir is not supported and raises an error.

Examples

# Render a single AST from string to stdout
nix-ast render --json '{"tag":"Set","recursive":false,"bindings":[...]}'

# Pipe from parse: parse then render back to source
nix-ast parse --expr '{ x = 1; }' | nix-ast render

# Render batch to individual files
cat asts.json | nix-ast render --out-dir ./rendered
# Creates: ./rendered/0.nix, ./rendered/1.nix, ...

# Render batch to stdout (as JSON array of strings)
cat asts.json | nix-ast render

Output Format

{
  x = 1;
}

For list/multiline outputs, each AST is pretty-printed independently.

Shell Workflow Examples

# Parse, transform via jq, and render back
nix-ast parse --expr '{ x = 1; }' \
  | jq '.bindings[0].value.contents.contents *= 2' \
  | nix-ast render

# Evaluate and query results
nix-ast parse --expr '{ inherit (builtins) map filter; }' | nix-ast eval | jq 'keys'

# Batch process multiple files
echo '["file1.nix", "file2.nix", "file3.nix"]' \
  | nix-ast parse \
  | nix-ast render --out-dir ./out

Core Functions

The four main functions (parse, render, eval, toAST, fromAST) bridge between Nix source code, AST values, and evaluated Nix values.

All IFD-based functions (parse, render, eval) require a pkgs argument (a Nixpkgs instance). They serialize inputs to JSON, invoke the nix-ast CLI inside a derivation, and parse the output back.

parse

Read .nix files from disk and return their AST representations. Uses nix-ast parse under the hood.

parse :: pkgs -> [Path] -> [AST]

How it works: Serializes file paths to JSON, runs nix-ast parse < paths.json in a derivation, then parses the JSON output back into Nix values.

Parameters:

ParameterTypeDescription
pkgspkgsNixpkgs instance (provides runCommand and stdenv for IFD)
paths[Path]List of paths to .nix files to parse

Returns: [AST]. A list of AST nodes, one per input file, in the same order.

Example:

# Parse a single file
asts = lib.parse pkgs [./config.nix];

# Parse multiple files
asts = lib.parse pkgs [./config.nix ./default.nix ./modules/*.nix];

# Access the first AST
ast = builtins.head asts;

Error behavior: If a file doesn’t exist or contains invalid Nix syntax, the derivation fails at build time with an error message indicating which file and the parser error.

render

Convert AST values to .nix files on disk. The inverse of parse.

render :: pkgs -> [AST] -> [Path]

How it works: Serializes ASTs to JSON, pipes them through nix-ast render --out-dir $out, which writes each AST to a file named by its index (0.nix, 1.nix, etc.) inside the derivation output directory.

Parameters:

ParameterTypeDescription
pkgspkgsNixpkgs instance (for IFD)
asts[AST]List of AST nodes to render

Returns: [Path]. List of store paths to generated .nix files. The i-th path corresponds to the i-th input AST.

Example:

# Render a single AST to file
outPaths = lib.render pkgs [ast];
configFile = builtins.head outPaths;
config = import configFile;

# Render multiple ASTs
outPaths = lib.render pkgs [ast1, ast2, ast3];
imported = map import outPaths;

Error behavior: If an AST is structurally invalid (e.g., missing required fields), the conversion to internal representation fails with a descriptive error.

eval

Evaluate AST values and return the results as JSON values.

eval :: pkgs -> [AST] -> [a]

How it works: Serializes ASTs to JSON, pipes them through nix-ast eval in a derivation. The CLI deserializes each AST, evaluates it, and outputs the results as JSON.

Parameters:

ParameterTypeDescription
pkgspkgsNixpkgs instance (for IFD)
asts[AST]List of AST nodes to evaluate

Returns: [a]. List of evaluated Nix values, serialized to JSON-compatible Nix values (atoms, lists, attrsets).

Constraints: Only JSON-serializable values are supported. Functions and derivations in the evaluation result cause errors.

Example:

# Evaluate a simple expression
asts = lib.parse pkgs [./expr.nix];
result = lib.eval pkgs asts;  # evaluated values

# Evaluate inline-generated AST
ast = lib.toAST { x = 1 + 2; };
result = lib.eval pkgs [ast];  # => [ { x = 3; } ]

toAST

Convert any native Nix value to its AST representation. A pure function with no IFD required.

toAST :: a -> AST

How it works: Recursively inspects the Nix value’s type using builtins.isBool, builtins.isInt, etc., and constructs the corresponding AST nodes using syntax constructors (mkInt, mkSet, mkStr, etc.). Attrsets are converted to Set nodes with NamedVar bindings. Strings become StrDoubleQuotedPlain.

Supported types:

Nix typeAST representation
BoolConstant (Bool b)
IntConstant (Int i)
FloatConstant (Float f)
nullConstant Null
StringStr (DoubleQuoted [Plain s])
PathLiteralPath
ListList [items...]
AttrSetSet { recursive = false, bindings = [...] }

Unsupported types: Functions and derivations raise an error.

Example:

lib.toAST 42;
# => { tag = "Constant"; contents = { tag = "Int"; contents = 42; }; }

lib.toAST { x = 1; y = [1 2 3]; };
# => { tag = "Set"; recursive = false;
#      bindings = [
#        { tag = "NamedVar"; attrPath = [{tag="StaticKey"; contents="x"}];
#          value = { tag = "Constant"; contents = { tag = "Int"; contents = 1; }; }; }
#        ...
#      ]; }

Error behavior:

  • toAST on a function: "toAST: cannot convert function to AST"
  • toAST on a derivation: "toAST: cannot convert derivation to AST"
  • Unrecognized type: "toAST: unsupported Nix type"

fromAST

Convert an AST back to a native Nix value. A pure function that runs entirely in Nix with no IFD.

fromAST :: AST -> a

How it works: Dispatches on the AST node tag via match and recursively converts nodes to native Nix values. Atoms become their Nix equivalents, strings are concatenated from their parts, lists are mapped element-wise, and non-recursive attrsets are reconstructed from bindings.

Supported node conversions:

AST nodeNix typeNotes
Constantatom valueInt, Float, Bool, Null, Uri
StrStringConcatenates parts; only Str and text in interpolation
EnvPathStringReturns the path string directly
LiteralPathStringReturns the path string directly
ListListRecursively converts elements
SetAttrSetOnly non-recursive sets

Limitations:

  • Recursive sets (rec { ... }) throw: "fromAST: cannot convert recursive set to Nix value"
  • Plain inherit (without a scope) throws: "fromAST: plain inherit (without scope) is not supported"
  • String interpolation only supports Str nodes or plain text inside Antiquoted parts
  • Paths are returned as strings, not actual Nix paths

Example:

ast = lib.toAST { greeting = "hello"; count = 42; };
lib.fromAST ast;
# => { greeting = "hello"; count = 42; }

# Round-trip: toAST → fromAST preserves the value
assert lib.fromAST (lib.toAST [1 2 3]) == [1 2 3];

Workflow Example

Parse a Nix file, transform it, render the result, and verify via eval.

let
  inherit (lib) parse render eval traversal syntax toAST fromAST;

  # Step 1: Parse source files into ASTs
  asts = parse pkgs [./config.nix];
  original = builtins.head asts;

  # Step 2: Transform the AST (double all integer constants)
  transformed = traversal.transform (node:
    if syntax.isConstant node && syntax.isInt node.contents then
      syntax.mkConstant (syntax.mkInt (node.contents.contents * 2))
    else
      node
  ) original;

  # Step 3: Render back to .nix file
  outPaths = render pkgs [transformed];
  configFile = builtins.head outPaths;

  # Step 4: Import the result
  config = import configFile;

  # Step 5: Or evaluate the transformed AST directly
  evaluated = eval pkgs [transformed];

in { inherit config configFile evaluated; }

match: Pattern Matching

Type-safe pseudo pattern matching on AST node tags.

Syntax

match ast {
  Tag1 = handler1;
  Tag2 = handler2;
  _ = defaultHandler;
}

Each handler is a function that receives the matched node.

Examples

Simple Match

match ast {
  Sym = n: n.contents;
  _ = n: "unknown";
}

Destructure

match ast {
  App = { func, arg }: "application";
  If = { cond, thenExpr, elseExpr }: "conditional";
  Let = { bindings, body }: "let expression";
  _ = n: n.tag;
}

Nested Match

match ast {
  Set = { recursive, bindings }:
    if recursive then "recursive set" else "set";
  _ = n: "other";
}

Handler Signatures

Each handler receives the node as its argument. You can destructure the fields:

# Full node
match ast {
  Sym = n: n;  # n is the entire node
  _ = n: n;
}

# Destructured
match ast {
  Sym = { contents }: contents;  # destructure fields
  App = { func, arg }: func;
  _ = n: n.tag;
}

Wildcard

The _ handler catches any unmatched tag:

match ast {
  Sym = n: "symbol";
  _ = n: "other: ${n.tag}";
}

Use Cases

Type Checking

isInteger = match ast {
  Constant = { contents }: syntax.isInt contents;
  _ = _: false;
};

Pretty Printing

pretty = match ast {
  Sym = { contents }: contents;
  Int = { contents }: toString contents;
  Str = { contents }: "\"${contents}\"";
  _ = n: builtins.toJSON n;
};

syntax: Constructors & Predicates

Type-checked node builders and tag predicates. All constructors validate their arguments at runtime.

Constructors

Atoms

syntax.mkInt 42
syntax.mkFloat 3.14
syntax.mkBool true
syntax.mkNull
syntax.mkUri "https://example.com"

Strings

syntax.mkStr (syntax.mkDoubleQuoted [ syntax.mkPlain "hello" ])
syntax.mkStr (syntax.mkIndented 2 [ syntax.mkPlain "world" ])

Expressions

syntax.mkSym "x"
syntax.mkApp (syntax.mkSym "f") (syntax.mkInt 1)
syntax.mkAbs (syntax.mkParam "x") (syntax.mkSym "x")
syntax.mkIf (syntax.mkBool true) (syntax.mkInt 1) (syntax.mkInt 2)
syntax.mkLet [ syntax.mkNamedVar [ syntax.mkStaticKey "x" ] (syntax.mkInt 1) ] (syntax.mkSym "x")
syntax.mkSet false [ syntax.mkNamedVar [ syntax.mkStaticKey "x" ] (syntax.mkInt 1) ]
syntax.mkList [ syntax.mkInt 1 syntax.mkInt 2 syntax.mkInt 3 ]
syntax.mkBinary "+" (syntax.mkSym "x") (syntax.mkSym "y")
syntax.mkUnary "!" (syntax.mkSym "x")
syntax.mkWith (syntax.mkSym "pkgs") (syntax.mkSym "x")
syntax.mkAssert (syntax.mkBool true) (syntax.mkSym "x")
syntax.mkSelect (syntax.mkSym "default") (syntax.mkSym "set") [ syntax.mkStaticKey "attr" ]
syntax.mkHasAttr (syntax.mkSym "set") [ syntax.mkStaticKey "attr" ]
syntax.mkLiteralPath "./foo.nix"
syntax.mkEnvPath "<nixpkgs>"

Bindings

syntax.mkInherit null [ "x" "y" ]
syntax.mkInherit (syntax.mkSym "pkgs") [ "vim" "git" ]
syntax.mkNamedVar [ syntax.mkStaticKey "x" ] (syntax.mkInt 1)

Keys

syntax.mkStaticKey "foo"
syntax.mkDynamicKey (syntax.mkAntiquoted (syntax.mkSym "name"))

Parameters

syntax.mkParam "x"
syntax.mkParamSet null false [ [ "x" null ] [ "y" (syntax.mkInt 1) ] ]

Antiquoted Text

syntax.mkPlain "hello"
syntax.mkAntiquoted (syntax.mkSym "x")
syntax.mkEscapedNewline

Predicates

One predicate per node type:

# Expression predicates
syntax.isAbs node
syntax.isApp node
syntax.isAssert node
syntax.isBinary node
syntax.isConstant node
syntax.isEnvPath node
syntax.isHasAttr node
syntax.isIf node
syntax.isLet node
syntax.isList node
syntax.isLiteralPath node
syntax.isSelect node
syntax.isSet node
syntax.isStr node
syntax.isSym node
syntax.isSynHole node
syntax.isUnary node
syntax.isWith node

# Atom predicates
syntax.isInt node
syntax.isFloat node
syntax.isBool node
syntax.isNull node
syntax.isUri node

# Binding predicates
syntax.isInherit node
syntax.isNamedVar node

# Key predicates
syntax.isDynamicKey node
syntax.isStaticKey node

# String predicates
syntax.isDoubleQuoted node
syntax.isIndented node

# Antiquoted text predicates
syntax.isPlain node
syntax.isAntiquoted node
syntax.isEscapedNewline node

Helpers

# Get node tag
syntax.getExprKind node  # => "Sym", "App", etc.

# Check tag
syntax.hasTag "Sym" node  # => true/false

traversal: Tree Operations

All operations respect the children/rebuild contract: rebuild node (children node) == node.

Core Operations

children

Get immediate child nodes in deterministic order.

traversal.children :: Expr -> [Expr]

Example:

# For: { tag = "App"; func = f; arg = x; }
traversal.children node  # => [f, x]

rebuild

Reconstruct node from new children (inverse of children).

traversal.rebuild :: Expr -> [Expr] -> Expr

Example:

newNode = traversal.rebuild node [newFunc newArg]

descend

Apply function to all immediate children, then rebuild.

traversal.descend :: (Expr -> Expr) -> Expr -> Expr

Example:

# Transform all direct children
traversal.descend (n: n + 1) node

Transformations

transform

Bottom-up transformation: f (descend (transform f) node).

traversal.transform :: (Expr -> Expr) -> Expr -> Expr

Example:

# Replace all integers with their double
doubleInts = traversal.transform (node:
  if syntax.isConstant node && syntax.isInt node.contents then
    syntax.mkConstant (syntax.mkInt (node.contents.contents * 2))
  else
    node
);

rewrite

Apply rule bottom-up; null means no change.

traversal.rewrite :: (Expr -> Expr | null) -> Expr -> Expr

Example:

# Simplify constant folding
simplify = traversal.rewrite (node:
  if isBinaryAdd node && bothConstants node then
    syntax.mkInt (evalNode node)
  else
    null  # no change
);

para

Paramorphism: access node and recursive results from children.

traversal.para :: (Expr -> [a] -> a) -> Expr -> a

Example:

# Count all nodes
countNodes = traversal.para (node: childCounts:
  1 + builtins.foldl' (a: b: a + b) 0 childCounts
);

Exploration

universe

All descendant nodes including self.

traversal.universe :: Expr -> [Expr]

Example:

allSymbols = builtins.filter syntax.isSym (traversal.universe ast);

holes

Each child paired with a replacement function.

traversal.holes :: Expr -> [(Expr, Expr -> Expr)]

Example:

# Get all positions where a child can be replaced
holes = traversal.holes ast;
# => [(child1, \replacement -> rebuild node [replacement ...]),
#     (child2, \replacement -> rebuild node [... replacement ...])]

contexts

Every subnode paired with a function to replace it in context.

traversal.contexts :: Expr -> [(Expr, Expr -> Expr)]

Example:

# Get all subexpressions with their context
contexts = traversal.contexts ast;

Children/Rebuild Contract

All traversal operations maintain this invariant:

traversal.rebuild node (traversal.children node) == node

This ensures that operations can freely decompose and reconstruct nodes without losing information.

Use Cases

Deep Transformation

# Replace all variable references
replaceVars = traversal.transform (node:
  if syntax.isSym node then
    syntax.mkSym (replaceName node.contents)
  else
    node
);

Collection

# Collect all function applications
collectApps = traversal.universe >> builtins.filter syntax.isApp;

Analysis

# Check if expression contains a specific pattern
containsAssert = node:
  builtins.any syntax.isAssert (traversal.universe node);

Antiquoted

Constructors

ConstructorFieldsDescription
Plaincontents: vLiteral value: Text in string parts, String in DynamicKey
Antiquotedcontents: ExprEmbedded expression ${...}
EscapedNewline(none)Escaped newline \ in indented strings

Description

Antiquoted is polymorphic in the Plain constructor. It is used at two type arguments:

  • Antiquoted Text: in string parts (DoubleQuoted / Indented), Plain wraps literal text.
  • Antiquoted String: in DynamicKey, Plain wraps a String AST node (DoubleQuoted / Indented).

A string like "hello ${x} world" becomes a list: [Plain "hello ", Antiquoted (Sym "x"), Plain " world"]

Pages

  • String: DoubleQuoted / Indented containers
  • Str: expression wrapper

Nix Library Access

syntax.mkPlain "hello"       # text content (string parts)
syntax.mkAntiquoted (syntax.mkSym "x")
syntax.mkEscapedNewline

Antiquoted (Embedded Expression)

Embedded expression inside a string. Constructor of Antiquoted.

Definition

Antiquoted Expr

Fields

FieldTypeDescription
contentsExprThe embedded expression to evaluate and interpolate

Description

Antiquoted represents an ${...} expression inside a string. The contained Expr is evaluated and its result is converted to a string for interpolation.

Nix Source ↔ AST

# Nix
"hello ${name}"

# AST (part of DoubleQuoted contents)
{
  "tag": "Antiquoted",
  "contents": { "tag": "Sym", "contents": "name" }
}

Complex Antiquotation

# Nix
"${if cond then "yes" else "no"}"

# AST
{
  "tag": "Antiquoted",
  "contents": {
    "tag": "If",
    "cond": { "tag": "Sym", "contents": "cond" },
    "thenExpr": { "tag": "Str", ... },
    "elseExpr": { "tag": "Str", ... }
  }
}

Nix Library Access

syntax.mkAntiquoted (syntax.mkSym "name")
syntax.mkAntiquoted (syntax.mkIf cond thenExpr elseExpr)

EscapedNewline (Escaped Newline)

Escaped newline in indented strings. Constructor of Antiquoted.

Definition

EscapedNewline

Fields

None (nullary constructor).

Description

EscapedNewline represents a backslash-newline sequence (\ followed by newline) in an indented string (''...''). It allows splitting a long logical line across multiple physical lines without inserting a newline in the resulting string.

Nix Source ↔ AST

# Nix
''
  this is a \
  continued line
''

# AST (part of Indented parts)
{
  "tag": "EscapedNewline"
}

The resulting string content is "this is a continued line" (no newline between “a” and “continued”).

Nix Library Access

syntax.mkEscapedNewline

Plain (Antiquoted)

Literal value in an Antiquoted node. Constructor of Antiquoted, polymorphic in the content type.

Definition

Plain v

Fields

FieldTypeDescription
contentsvThe literal value: Text in string parts, String node in DynamicKey

Description

Plain represents a literal value that is not an antiquotation. In a string like "hello ${world}", the "hello " part is a Plain node with text content.

When used inside DynamicKey (as Antiquoted String), Plain wraps a String AST node (DoubleQuoted / Indented) instead of plain text. Use mkPlain for this case.

Nix Source ↔ AST

Antiquoted Text (string parts)

# Nix
"hello "

# AST (part of DoubleQuoted contents)
{ "tag": "Plain", "contents": "hello " }

Antiquoted String (DynamicKey)

# Nix
{ "foo bar" = 1; }

# AST (part of DynamicKey contents)
{ "tag": "Plain", "contents": { "tag": "DoubleQuoted", "contents": [...] } }

Nix Library Access

syntax.mkPlain "hello "       # text content (string parts)
syntax.mkPlain (syntax.mkDoubleQuoted [...])  # String node content (DynamicKey)

Atom

Constructors

ConstructorFieldTypeDescription
BoolcontentsBoolBoolean value (true/false)
FloatcontentsFloatFloating-point (not in Nix surface syntax)
IntcontentsInteger64-bit integer
Null(none)-Null value
UricontentsTextURI literal

Description

Atoms are the primitive constant values in Nix. They are always wrapped in a Constant expression node when used as expressions.

{
  "tag": "Constant",
  "contents": { "tag": "Int", "contents": 42 }
}

Pages

  • Constant: expression wrapper for atoms

Nix Library Access

syntax.mkInt 42
syntax.mkBool true
syntax.mkNull
syntax.mkUri "https://example.com"
syntax.mkFloat 3.14

# Wrapped in Constant
syntax.mkConstant (syntax.mkInt 42)

Bool (Boolean Atom)

Definition

Bool Bool

Fields

FieldTypeDescription
contentsBoolTrue or False

Nix Source ↔ AST

# Nix
true

# AST (wrapped in Constant)
{
  "tag": "Constant",
  "contents": { "tag": "Bool", "contents": true }
}
# Nix
false

# AST (wrapped in Constant)
{
  "tag": "Constant",
  "contents": { "tag": "Bool", "contents": false }
}
  • Constant: wrapper for atoms in expressions

Nix Library Access

syntax.mkBool true
syntax.mkBool false
syntax.mkConstant (syntax.mkBool true)

Float (Floating-Point Atom)

Definition

Float Float

Fields

FieldTypeDescription
contentsFloatDouble-precision floating-point number

Note

Nix itself does not have native floating-point literals. The Float atom exists in the AST but is not directly exposed in Nix source syntax. Floating-point numbers in Nix are typically represented as strings or rationals.

In nix-ast JSON AST, Float atoms appear wrapped in Constant:

{
  "tag": "Constant",
  "contents": { "tag": "Float", "contents": 3.14 }
}
  • Constant: wrapper for atoms in expressions

Nix Library Access

syntax.mkFloat 3.14
syntax.mkConstant (syntax.mkFloat 3.14)

Int (Integer Atom)

Definition

Int Integer

Fields

FieldTypeDescription
contentsIntegerArbitrary-precision integer (Nix uses 64-bit range)

Nix Source ↔ AST

# Nix
42

# AST (wrapped in Constant)
{
  "tag": "Constant",
  "contents": { "tag": "Int", "contents": 42 }
}
# Nix
-123

# AST (wrapped in Constant)
{
  "tag": "Constant",
  "contents": { "tag": "Int", "contents": -123 }
}

Note

Nix integers are 64-bit signed. The Integer type is arbitrary precision but values are constrained to Nix’s 64-bit range at runtime.

  • Constant: wrapper for atoms in expressions

Nix Library Access

syntax.mkInt 42
syntax.mkConstant (syntax.mkInt 42)

Null (Null Atom)

Definition

Null

Fields

None (nullary constructor).

Nix Source ↔ AST

# Nix
null

# AST (wrapped in Constant)
{
  "tag": "Constant",
  "contents": { "tag": "Null" }
}
  • Constant: wrapper for atoms in expressions

Nix Library Access

syntax.mkNull
syntax.mkConstant syntax.mkNull

Uri (URI Atom)

Definition

Uri Text

Fields

FieldTypeDescription
contentsTextThe URI string

Nix Source ↔ AST

# Nix
https://example.com

# AST (wrapped in Constant)
{
  "tag": "Constant",
  "contents": { "tag": "Uri", "contents": "https://example.com" }
}

Note

In Nix, URIs are a distinct literal type (not strings). They are written without quotes and must be valid URIs.

  • Constant: wrapper for atoms in expressions

Nix Library Access

syntax.mkUri "https://example.com"
syntax.mkConstant (syntax.mkUri "https://example.com")

Binding

Constructors

ConstructorFieldsDescription
Inheritscope: Maybe Expr, names: [VarName]Inherit variables from scope
NamedVarattrPath: AttrPath, value: ExprNamed variable binding

Description

Bindings are used in Let expressions and Set (attribute set) expressions. They associate names with values.

Pages

Nix Library Access

syntax.mkInherit null ["x" "y"]
syntax.mkInherit (syntax.mkSym "pkgs") ["vim" "git"]
syntax.mkNamedVar [syntax.mkStaticKey "x"] (syntax.mkInt 1)

Inherit (Inherit Binding)

Definition

Inherit { scope :: Maybe Expr, names :: [VarName] }

Fields

FieldTypeDescription
scopeMaybe ExprOptional scope expression (Nothing = current scope, Just e = inherit from e)
names[VarName]List of variable names to inherit

Nix Source ↔ AST

Without Scope (from current scope)

# Nix
inherit x y z;

# AST
{
  "tag": "Inherit",
  "scope": null,
  "names": ["x", "y", "z"]
}

With Scope (from specific expression)

# Nix
inherit (pkgs) vim git;

# AST
{
  "tag": "Inherit",
  "scope": { "tag": "Sym", "contents": "pkgs" },
  "names": ["vim", "git"]
}
  • NamedVar: named variable binding
  • Let: let expressions using bindings
  • Set: attribute sets using bindings

Nix Library Access

syntax.mkInherit null ["x" "y" "z"]
syntax.mkInherit (syntax.mkSym "pkgs") ["vim" "git"]

NamedVar (Named Variable Binding)

Definition

NamedVar { attrPath :: AttrPath, value :: Expr }

Fields

FieldTypeDescription
attrPathAttrPathAttribute path (non-empty list of KeyName)
valueExprThe bound expression

Nix Source ↔ AST

Simple Binding

# Nix
x = 1;

# AST
{
  "tag": "NamedVar",
  "attrPath": [{ "tag": "StaticKey", "contents": "x" }],
  "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
}

Nested Path Binding

# Nix
a.b.c = 1;

# AST
{
  "tag": "NamedVar",
  "attrPath": [
    { "tag": "StaticKey", "contents": "a" },
    { "tag": "StaticKey", "contents": "b" },
    { "tag": "StaticKey", "contents": "c" }
  ],
  "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
}

Dynamic Key Binding

# Nix
let name = "foo"; in { ${name} = 1; }

# AST
{
  "tag": "NamedVar",
  "attrPath": [
    { "tag": "DynamicKey", "contents": {
        "tag": "Antiquoted",
        "contents": { "tag": "Sym", "contents": "name" }
      }
    }
  ],
  "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
}

Nix Library Access

syntax.mkNamedVar [syntax.mkStaticKey "x"] (syntax.mkInt 1)
syntax.mkNamedVar [syntax.mkStaticKey "a", syntax.mkStaticKey "b"] (syntax.mkInt 1)

Expression Nodes

All 18 Constructors

nix-ast tagFieldsDescription
Absparams: Params, body: ExprFunction abstraction (lambda)
Appfunc: Expr, arg: ExprFunction application
Assertcond: Expr, body: ExprAssertion assert cond; body
Binaryop: Text, left: Expr, right: ExprBinary operator
Constantcontents: AtomAtomic constant wrapper
EnvPathcontents: FilePathSearch-path <nixpkgs>
HasAttrexpr: Expr, attrPath: AttrPathHas-attribute check set ? attr
Ifcond: Expr, thenExpr: Expr, elseExpr: ExprConditional
Letbindings: [Binding], body: ExprLet bindings
Listcontents: [Expr]List [ ... ]
LiteralPathcontents: FilePathRelative/absolute path ./foo.nix
SelectdefaultValue: Maybe Expr, expr: Expr, selectPath: AttrPathAttribute selection set.attr or default
Setrecursive: Bool, bindings: [Binding]Attribute set { ... }
Strcontents: StringString expression (wraps String)
Symcontents: VarNameVariable reference
SynHolecontents: VarNameSyntax hole/placeholder
Unaryop: Text, arg: ExprUnary operator
Withnamespace: Expr, body: ExprWith expression with pkgs; ...

Node Hierarchy

Expr
├── Abs
├── App
├── Assert
├── Binary
├── Constant    → wraps Atom (Int, Float, Bool, Null, Uri)
├── EnvPath
├── HasAttr
├── If
├── Let
├── List
├── LiteralPath
├── Select
├── Set
├── Str         → wraps String (DoubleQuoted / Indented)
├── Sym
├── SynHole
├── Unary
└── With

Pages

Primitive Expressions

Function & Application

  • Abs: lambda abstraction
  • App: function application

Control Flow

Data Structures

  • List: list literal
  • Set: attribute set
  • Select: attribute selection

Bindings & Scope

  • Let: local bindings
  • With: bring set into scope

Operations

References & Constants

  • Sym: variable reference
  • Constant: atom wrapper
  • Str: string expression

See Also

Abs (Lambda Abstraction)

Definition

Abs { params :: Params, body :: Expr }

Fields

FieldTypeDescription
paramsParamsFunction parameters (single Param or ParamSet)
bodyExprFunction body expression

Nix Source ↔ AST

Single Parameter

# Nix
x: x + 1

# AST
{
  "tag": "Abs",
  "params": { "tag": "Param", "contents": "x" },
  "body": {
    "tag": "Binary",
    "op": "+",
    "left": { "tag": "Sym", "contents": "x" },
    "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
  }
}

Parameter Set

# Nix
{ x, y ? 1 }: x + y

# AST
{
  "tag": "Abs",
  "params": {
    "tag": "ParamSet",
    "paramSetName": null,
    "variadic": false,
    "params": [
      ["x", null],
      ["y", { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }]
    ]
  },
  "body": {
    "tag": "Binary",
    "op": "+",
    "left": { "tag": "Sym", "contents": "x" },
    "right": { "tag": "Sym", "contents": "y" }
  }
}

Variadic Parameter Set

# Nix
{ x, ... }: x

# AST
{
  "tag": "Abs",
  "params": {
    "tag": "ParamSet",
    "paramSetName": null,
    "variadic": true,
    "params": [["x", null]]
  },
  "body": { "tag": "Sym", "contents": "x" }
}

Nix Library Access

syntax.mkAbs (syntax.mkParam "x") (syntax.mkBinary "+" (syntax.mkSym "x") (syntax.mkInt 1))
syntax.mkAbs (syntax.mkParamSet null false [["x" null] ["y" null]]) body

App (Function Application)

Definition

App { func :: Expr, arg :: Expr }

Fields

FieldTypeDescription
funcExprThe function expression being applied
argExprThe argument expression

Nix Source ↔ AST

# Nix
f x

# AST
{
  "tag": "App",
  "func": { "tag": "Sym", "contents": "f" },
  "arg": { "tag": "Sym", "contents": "x" }
}

Chained Application

# Nix
f x y

# AST (left-associative)
{
  "tag": "App",
  "func": {
    "tag": "App",
    "func": { "tag": "Sym", "contents": "f" },
    "arg": { "tag": "Sym", "contents": "x" }
  },
  "arg": { "tag": "Sym", "contents": "y" }
}

Nix Library Access

syntax.mkApp (syntax.mkSym "f") (syntax.mkSym "x")

Assert

Definition

Assert { cond :: Expr, body :: Expr }

Fields

FieldTypeDescription
condExprCondition that must evaluate to true
bodyExprExpression to evaluate if assertion passes

Nix Source ↔ AST

# Nix
assert x > 0; x + 1

# AST
{
  "tag": "Assert",
  "cond": {
    "tag": "Binary",
    "op": ">",
    "left": { "tag": "Sym", "contents": "x" },
    "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 0 } }
  },
  "body": {
    "tag": "Binary",
    "op": "+",
    "left": { "tag": "Sym", "contents": "x" },
    "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
  }
}

Nix Library Access

syntax.mkAssert cond body

Binary (Binary Operation)

Definition

Binary { op :: Text, left :: Expr, right :: Expr }

Fields

FieldTypeDescription
opTextOperator symbol (e.g., +, ==, &&)
leftExprLeft operand
rightExprRight operand

Supported Operators

See Operators for the full list of binary operators.

Nix Source ↔ AST

# Nix
x + y

# AST
{
  "tag": "Binary",
  "op": "+",
  "left": { "tag": "Sym", "contents": "x" },
  "right": { "tag": "Sym", "contents": "y" }
}
# Nix
{ a = 1; } // { b = 2; }

# AST
{
  "tag": "Binary",
  "op": "//",
  "left": { "tag": "Set", "recursive": false, "bindings": [...] },
  "right": { "tag": "Set", "recursive": false, "bindings": [...] }
}

Nix Library Access

syntax.mkBinary "+" (syntax.mkSym "x") (syntax.mkSym "y")
syntax.mkBinary "//" set1 set2

Constant (Atom Wrapper)

Definition

Constant Atom

Fields

FieldTypeDescription
contentsAtomThe atomic value (Int, Float, Bool, Null, Uri)

Description

Constant wraps an Atom to make it a valid expression node. All literal values in Nix (integers, booleans, null, URIs) are represented as Constant containing an Atom.

Nix Source ↔ AST

# Nix
42

# AST
{
  "tag": "Constant",
  "contents": { "tag": "Int", "contents": 42 }
}
# Nix
true

# AST
{
  "tag": "Constant",
  "contents": { "tag": "Bool", "contents": true }
}
# Nix
null

# AST
{
  "tag": "Constant",
  "contents": { "tag": "Null" }
}
  • Atom: the wrapped atomic types (Int, Float, Bool, Null, Uri)
  • See Atoms for atom constructors

Nix Library Access

syntax.mkConstant (syntax.mkInt 42)
syntax.mkConstant (syntax.mkBool true)
syntax.mkConstant syntax.mkNull

EnvPath (Search Path)

Definition

EnvPath FilePath

Fields

FieldTypeDescription
contentsFilePathThe path string (e.g., nixpkgs)

Description

EnvPath represents paths written in angle brackets like <nixpkgs> or <nixos>. These are looked up in the Nix search path (NIX_PATH environment variable) and are distinct from literal file paths.

Nix Source ↔ AST

# Nix
<nixpkgs>

# AST
{
  "tag": "EnvPath",
  "contents": "nixpkgs"
}
# Nix
<nixos>

# AST
{
  "tag": "EnvPath",
  "contents": "nixos"
}

Nix Library Access

syntax.mkEnvPath "nixpkgs"
syntax.mkEnvPath "nixos"

HasAttr (Has Attribute)

Definition

HasAttr { expr :: Expr, attrPath :: AttrPath }

Fields

FieldTypeDescription
exprExprThe expression to check (typically a set)
attrPathAttrPathThe attribute path to check for

Description

HasAttr represents the ? operator in Nix, which checks whether an attribute exists in a set. Returns a boolean.

Nix Source ↔ AST

# Nix
set ? attr

# AST
{
  "tag": "HasAttr",
  "expr": { "tag": "Sym", "contents": "set" },
  "attrPath": [
    { "tag": "StaticKey", "contents": "attr" }
  ]
}
# Nix
set ? a.b.c

# AST
{
  "tag": "HasAttr",
  "expr": { "tag": "Sym", "contents": "set" },
  "attrPath": [
    { "tag": "StaticKey", "contents": "a" },
    { "tag": "StaticKey", "contents": "b" },
    { "tag": "StaticKey", "contents": "c" }
  ]
}
  • AttrPath: attribute path structure
  • Select: attribute selection (with default)

Nix Library Access

syntax.mkHasAttr (syntax.mkSym "set") [syntax.mkStaticKey "attr"]

If (Conditional)

Definition

If { cond :: Expr, thenExpr :: Expr, elseExpr :: Expr }

Fields

FieldTypeDescription
condExprCondition expression (must evaluate to boolean)
thenExprExprExpression when condition is true
elseExprExprExpression when condition is false

Nix Source ↔ AST

# Nix
if cond then 1 else 2

# AST
{
  "tag": "If",
  "cond": { "tag": "Sym", "contents": "cond" },
  "thenExpr": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } },
  "elseExpr": { "tag": "Constant", "contents": { "tag": "Int", "contents": 2 } }
}

Nix Library Access

syntax.mkIf cond thenExpr elseExpr

Let (Let Bindings)

Definition

Let { bindings :: [Binding], body :: Expr }

Fields

FieldTypeDescription
bindings[Binding]List of local bindings (Inherit or NamedVar)
bodyExprBody expression evaluated with bindings in scope

Nix Source ↔ AST

# Nix
let x = 1; y = 2; in x + y

# AST
{
  "tag": "Let",
  "bindings": [
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "x" }],
      "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
    },
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "y" }],
      "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 2 } }
    }
  ],
  "body": {
    "tag": "Binary",
    "op": "+",
    "left": { "tag": "Sym", "contents": "x" },
    "right": { "tag": "Sym", "contents": "y" }
  }
}
  • Binding: Inherit and NamedVar constructors
  • With: similar but brings a set into scope

Nix Library Access

syntax.mkLet [syntax.mkNamedVar [syntax.mkStaticKey "x"] (syntax.mkInt 1)] (syntax.mkBinary "+" (syntax.mkSym "x") (syntax.mkSym "y"))

List

Definition

List [Expr]

Fields

FieldTypeDescription
contents[Expr]List of element expressions

Nix Source ↔ AST

# Nix
[ 1 2 3 ]

# AST
{
  "tag": "List",
  "contents": [
    { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } },
    { "tag": "Constant", "contents": { "tag": "Int", "contents": 2 } },
    { "tag": "Constant", "contents": { "tag": "Int", "contents": 3 } }
  ]
}
# Nix
[ (x + 1) (y * 2) ]

# AST
{
  "tag": "List",
  "contents": [
    { "tag": "Binary", "op": "+", "left": { "tag": "Sym", "contents": "x" }, "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } } },
    { "tag": "Binary", "op": "*", "left": { "tag": "Sym", "contents": "y" }, "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 2 } } }
  ]
}

Nix Library Access

syntax.mkList [syntax.mkInt 1, syntax.mkInt 2, syntax.mkInt 3]

LiteralPath (Relative/Absolute Path)

Definition

LiteralPath FilePath

Fields

FieldTypeDescription
contentsFilePathThe path string (e.g., ./foo.nix, /etc/config)

Description

LiteralPath represents paths written without angle brackets: relative paths like ./foo.nix, ../bar.nix, or absolute paths like /etc/nixos/configuration.nix. These are resolved relative to the current file or as absolute paths, distinct from search-path paths.

Nix Source ↔ AST

# Nix
./foo.nix

# AST
{
  "tag": "LiteralPath",
  "contents": "./foo.nix"
}
# Nix
../config.nix

# AST
{
  "tag": "LiteralPath",
  "contents": "../config.nix"
}
# Nix
/etc/nixos/configuration.nix

# AST
{
  "tag": "LiteralPath",
  "contents": "/etc/nixos/configuration.nix"
}
  • EnvPath: search paths like <nixpkgs>

Nix Library Access

syntax.mkLiteralPath "./foo.nix"
syntax.mkLiteralPath "/etc/nixos/configuration.nix"

Select (Attribute Selection)

Definition

Select { defaultValue :: Maybe Expr, expr :: Expr, selectPath :: AttrPath }

Fields

FieldTypeDescription
defaultValueMaybe ExprDefault value if attribute not found (Nothing = no default, throws error)
exprExprThe set expression to select from
selectPathAttrPathThe attribute path to select

Nix Source ↔ AST

Without Default (throws if missing)

# Nix
set.attr

# AST
{
  "tag": "Select",
  "defaultValue": null,
  "expr": { "tag": "Sym", "contents": "set" },
  "selectPath": [{ "tag": "StaticKey", "contents": "attr" }]
}

With Default (or)

# Nix
set.attr or "default"

# AST
{
  "tag": "Select",
  "defaultValue": { "tag": "Str", "contents": { "tag": "DoubleQuoted", "contents": [{ "tag": "Plain", "contents": "default" }] } },
  "expr": { "tag": "Sym", "contents": "set" },
  "selectPath": [{ "tag": "StaticKey", "contents": "attr" }]
}

Nested Path

# Nix
set.a.b.c or 0

# AST
{
  "tag": "Select",
  "defaultValue": { "tag": "Constant", "contents": { "tag": "Int", "contents": 0 } },
  "expr": { "tag": "Sym", "contents": "set" },
  "selectPath": [
    { "tag": "StaticKey", "contents": "a" },
    { "tag": "StaticKey", "contents": "b" },
    { "tag": "StaticKey", "contents": "c" }
  ]
}

Nix Library Access

syntax.mkSelect null (syntax.mkSym "set") [syntax.mkStaticKey "attr"]
syntax.mkSelect (syntax.mkStr (syntax.mkDoubleQuoted [syntax.mkPlain "default"])) (syntax.mkSym "set") [syntax.mkStaticKey "attr"]

Set (Attribute Set)

Definition

Set { recursive :: Bool, bindings :: [Binding] }

Fields

FieldTypeDescription
recursiveBooltrue for rec { ... }, false for { ... }
bindings[Binding]List of bindings (Inherit or NamedVar)

Nix Source ↔ AST

Non-Recursive Set

# Nix
{ x = 1; y = 2; }

# AST
{
  "tag": "Set",
  "recursive": false,
  "bindings": [
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "x" }],
      "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
    },
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "y" }],
      "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 2 } }
    }
  ]
}

Recursive Set

# Nix
rec { x = 1; y = x + 1; }

# AST
{
  "tag": "Set",
  "recursive": true,
  "bindings": [
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "x" }],
      "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
    },
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "y" }],
      "value": {
        "tag": "Binary",
        "op": "+",
        "left": { "tag": "Sym", "contents": "x" },
        "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
      }
    }
  ]
}

With Inherit

# Nix
{ inherit x y; z = 3; }

# AST
{
  "tag": "Set",
  "recursive": false,
  "bindings": [
    { "tag": "Inherit", "scope": null, "names": ["x", "y"] },
    {
      "tag": "NamedVar",
      "attrPath": [{ "tag": "StaticKey", "contents": "z" }],
      "value": { "tag": "Constant", "contents": { "tag": "Int", "contents": 3 } }
    }
  ]
}
  • Binding: Inherit and NamedVar constructors
  • KeyName: StaticKey / DynamicKey for attribute paths

Nix Library Access

syntax.mkSet false [syntax.mkNamedVar [syntax.mkStaticKey "x"] (syntax.mkInt 1)]
syntax.mkSet true [...]

Str (String Expression)

Definition

Str String

Fields

FieldTypeDescription
contentsStringThe string contents (DoubleQuoted or Indented)

Description

Str is the expression-level node for strings. It wraps a String node which contains the actual string parts (plain text, antiquotations, escaped newlines).

Nix Source ↔ AST

Double-Quoted String

# Nix
"hello ${world}"

# AST
{
  "tag": "Str",
  "contents": {
    "tag": "DoubleQuoted",
    "contents": [
      { "tag": "Plain", "contents": "hello " },
      { "tag": "Antiquoted", "contents": { "tag": "Sym", "contents": "world" } }
    ]
  }
}

Indented String

# Nix
''
  hello
  world
''

# AST
{
  "tag": "Str",
  "contents": {
    "tag": "Indented",
    "indent": 2,
    "parts": [
      { "tag": "Plain", "contents": "hello\nworld" }
    ]
  }
}
  • String: DoubleQuoted and Indented constructors
  • Antiquoted: Plain, Antiquoted, EscapedNewline parts

Nix Library Access

syntax.mkStr (syntax.mkDoubleQuoted [syntax.mkPlain "hello"])
syntax.mkStr (syntax.mkIndented 2 [syntax.mkPlain "hello\nworld"])

Sym (Variable Reference)

Definition

Sym VarName

Fields

FieldTypeDescription
contentsVarNameThe variable name

Description

Sym represents a reference to a variable in scope. It is used for:

  • Function parameters
  • Let-bound variables
  • Inherited variables
  • Built-in identifiers (e.g., builtins, pkgs)

Nix Source ↔ AST

# Nix
x

# AST
{
  "tag": "Sym",
  "contents": "x"
}
# Nix
builtins.map

# AST
{
  "tag": "Sym",
  "contents": "builtins.map"
}

Nix Library Access

syntax.mkSym "x"
syntax.mkSym "builtins.map"

SynHole (Syntax Hole)

Definition

SynHole VarName

Fields

FieldTypeDescription
contentsVarNameThe hole identifier/name

Description

SynHole represents a placeholder or “hole” in the AST that can be filled later during metaprogramming or code generation. This is useful for:

  • Template generation
  • Partial AST construction
  • Code synthesis tools

The VarName identifies the hole for later substitution.

Nix Source ↔ AST

There is no direct Nix syntax for SynHole: it is constructed programmatically.

{
  "tag": "SynHole",
  "contents": "myHole"
}

Nix Library Access

syntax.mkSynHole "myHole"

Unary (Unary Operation)

Definition

Unary { op :: Text, arg :: Expr }

Fields

FieldTypeDescription
opTextOperator symbol (- or !)
argExprOperand expression

Supported Operators

See Operators for the full list of unary operators.

Nix Source ↔ AST

# Nix
-x

# AST
{
  "tag": "Unary",
  "op": "-",
  "arg": { "tag": "Sym", "contents": "x" }
}
# Nix
!cond

# AST
{
  "tag": "Unary",
  "op": "!",
  "arg": { "tag": "Sym", "contents": "cond" }
}

Nix Library Access

syntax.mkUnary "-" (syntax.mkSym "x")
syntax.mkUnary "!" (syntax.mkSym "cond")

With (With Expression)

Definition

With { namespace :: Expr, body :: Expr }

Fields

FieldTypeDescription
namespaceExprThe set expression whose attributes are brought into scope
bodyExprBody expression evaluated with namespace attributes in scope

Description

With brings all attributes of the namespace set into the lexical scope of body. This allows referencing pkgs.hello as just hello within the body.

Nix Source ↔ AST

# Nix
with pkgs; hello

# AST
{
  "tag": "With",
  "namespace": { "tag": "Sym", "contents": "pkgs" },
  "body": { "tag": "Sym", "contents": "hello" }
}
  • Let: similar but with explicit bindings

Nix Library Access

syntax.mkWith (syntax.mkSym "pkgs") (syntax.mkSym "hello")

KeyName

Constructors

ConstructorFieldsDescription
DynamicKeycontents: Antiquoted StringDynamic/computed key
StaticKeycontents: VarNameStatic identifier key

Description

KeyName represents attribute keys in bindings and attribute paths. A bare identifier like foo is a StaticKey; a quoted string like "foo bar" or antiquoted ${name} is a DynamicKey.

Pages

Nix Library Access

syntax.mkStaticKey "foo"
syntax.mkDynamicKey (syntax.mkAntiquoted (syntax.mkSym "name"))

DynamicKey (Dynamic Attribute Key)

Definition

DynamicKey (Antiquoted String)

Fields

FieldTypeDescription
contentsAntiquoted StringA string that may contain antiquotations

Description

DynamicKey is used for attribute keys that are not static identifiers: either quoted strings like "foo bar" or antiquoted expressions like ${name}. In Nix, any key in quotes or with antiquotation is parsed as a DynamicKey.

The contents is an Antiquoted String, meaning it can be:

  • Plain string parts
  • Antiquoted expressions
  • Escaped newlines (in indented strings)

Nix Source ↔ AST

Quoted String Key

# Nix
{ "foo bar" = 1; }

# AST (key part)
{
  "tag": "DynamicKey",
  "contents": {
    "tag": "Plain",
    "contents": {
      "tag": "DoubleQuoted",
      "contents": [
        { "tag": "Plain", "contents": "foo bar" }
      ]
    }
  }
}

Antiquoted Key

# Nix
let name = "foo"; in { ${name} = 1; }

# AST (key part)
{
  "tag": "DynamicKey",
  "contents": {
    "tag": "Antiquoted",
    "contents": { "tag": "Sym", "contents": "name" }
  }
}

Nix Library Access

syntax.mkDynamicKey (syntax.mkAntiquoted (syntax.mkSym "name"))
# Plain + String variant (rare): syntax.mkDynamicKey (syntax.mkPlain (syntax.mkDoubleQuoted [...]))

StaticKey (Static Attribute Key)

Definition

StaticKey VarName

Fields

FieldTypeDescription
contentsVarNameThe identifier name

Description

StaticKey is used for bare identifier keys in attribute sets: keys that are valid Nix identifiers (alphanumeric + underscore, not starting with a digit). These are written without quotes.

{ foo = 1; bar = 2; }  -- both are StaticKey

Nix Source ↔ AST

# Nix
{ foo = 1; }

# AST (key part)
{ "tag": "StaticKey", "contents": "foo" }

Rules

  • Must be a valid Nix identifier: [a-zA-Z_][a-zA-Z0-9_]*
  • Cannot start with a digit
  • Cannot contain spaces or special characters (those require DynamicKey)

Nix Library Access

syntax.mkStaticKey "foo"
syntax.mkStaticKey "my_var_123"

Params

Constructors

ConstructorFieldsDescription
ParamSetparamSetName: Maybe VarName, variadic: Bool, params: ParamSetParameter set { x, y ? 1, ... }
Paramcontents: VarNameSingle parameter x: ...

Description

Params represents function parameters: either a single parameter (x: body) or a parameter set ({ x, y ? 1 }: body).

The ParamSet constructor contains an inner ParamSet type which is a map of parameter names to optional default expressions.

Pages

Nix Library Access

syntax.mkParam "x"
syntax.mkParamSet null false [["x" null] ["y" null]]
syntax.mkParamSet null false [["x" (syntax.mkInt 1)]]
syntax.mkParamSet null true [["x" null] ["y" null]]

ParamSet (Parameter Set)

Parameter set constructor for function parameters. Part of the Params type.

Definition

ParamSet { paramSetName :: Maybe VarName, variadic :: Bool, params :: ParamSet }

Fields

FieldTypeDescription
paramSetNameMaybe VarNameName for @-pattern (e.g., args @ { x, y })
variadicBooltrue if ... is present (allows extra arguments)
paramsParamSetThe actual parameter set (map of name → optional default)

Description

ParamSet represents a function parameter set like { x, y ? 1, ... }. It contains the parameter names, their optional default values, whether it’s variadic, and an optional name for the whole set (for @-patterns).

The inner ParamSet field wraps a parameter set type that maps parameter names to optional default expressions.

type ParamSet = [(Text, Maybe Expr)]

Invariant: duplicate parameter names are semantically invalid in Nix. Each parameter name must be unique.

Nix Source ↔ AST

Fixed Parameters

# Nix
{ x, y }: x + y

# AST
{
  "tag": "Abs",
  "params": {
    "tag": "ParamSet",
    "paramSetName": null,
    "variadic": false,
    "params": [
      ["x", null],
      ["y", null]
    ]
  },
  "body": { ... }
}

With Defaults

# Nix
{ x ? 1, y ? "hello" }: x

# AST
{
  "tag": "ParamSet",
  "paramSetName": null,
  "variadic": false,
  "params": [
    ["x", { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }],
    ["y", { "tag": "Str", "contents": { "tag": "DoubleQuoted", "contents": [{ "tag": "Plain", "contents": "hello" }] } }]
  ]
}

Variadic (...)

# Nix
{ x, y, ... }: x + y

# AST
{
  "tag": "ParamSet",
  "paramSetName": null,
  "variadic": true,
  "params": [
    ["x", null],
    ["y", null]
  ]
}

With @-Pattern

# Nix
args @ { x, y }: args.x + args.y

# AST
{
  "tag": "ParamSet",
  "paramSetName": "args",
  "variadic": false,
  "params": [
    ["x", null],
    ["y", null]
  ]
}
  • Param: single parameter constructor
  • Params: the parent sum type

Nix Library Access

syntax.mkParamSet null false [["x" null] ["y" null]]
syntax.mkParamSet null false [["x" (syntax.mkInt 1)]]
syntax.mkParamSet null true [["x" null] ["y" null]]
syntax.mkParamSet "args" false [["x" null] ["y" null]]

Param (Single Parameter)

Single parameter constructor for function parameters. Part of the Params type.

Definition

Param VarName

Fields

FieldTypeDescription
contentsVarNameThe parameter name

Description

Param represents a single-argument function parameter like x: x + 1. This is distinct from ParamSet which represents { x, y }: ....

Nix Source ↔ AST

# Nix
x: x + 1

# AST
{
  "tag": "Abs",
  "params": { "tag": "Param", "contents": "x" },
  "body": {
    "tag": "Binary",
    "op": "+",
    "left": { "tag": "Sym", "contents": "x" },
    "right": { "tag": "Constant", "contents": { "tag": "Int", "contents": 1 } }
  }
}

Nix Library Access

syntax.mkParam "x"

String

Constructors

ConstructorFieldsDescription
DoubleQuotedcontents: [Antiquoted Text]Standard string "..."
Indentedindent: Int, parts: [Antiquoted Text]Multi-line indented string ''...''

Description

String represents the contents of a string literal (not the expression wrapper). The expression-level node is Str which wraps a String.

String parts are Antiquoted Text: either Plain text, Antiquoted expressions, or EscapedNewline (indented strings only).

Pages

String Parts (Antiquoted)

See Antiquoted for:

  • Plain: literal text
  • Antiquoted: embedded expression ${...}
  • EscapedNewline: \ newline in indented strings

Nix Library Access

syntax.mkStr (syntax.mkDoubleQuoted [syntax.mkPlain "hello"])
syntax.mkStr (syntax.mkIndented 2 [syntax.mkPlain "hello\nworld"])

DoubleQuoted (Standard String)

Definition

DoubleQuoted [Antiquoted Text]

Fields

FieldTypeDescription
contents[Antiquoted Text]List of string parts

Nix Source ↔ AST

Simple String

# Nix
"hello"

# String node
{
  "tag": "DoubleQuoted",
  "contents": [
    { "tag": "Plain", "contents": "hello" }
  ]
}

With Antiquotation

# Nix
"hello ${name}"

# String node
{
  "tag": "DoubleQuoted",
  "contents": [
    { "tag": "Plain", "contents": "hello " },
    { "tag": "Antiquoted", "contents": { "tag": "Sym", "contents": "name" } }
  ]
}

Expression Wrapper

The expression-level node is Str wrapping this:

{
  "tag": "Str",
  "contents": { "tag": "DoubleQuoted", "contents": [...] }
}
  • Indented: multi-line indented strings
  • Antiquoted: Plain, Antiquoted, EscapedNewline parts
  • Str: expression wrapper

Nix Library Access

syntax.mkStr (syntax.mkDoubleQuoted [syntax.mkPlain "hello"])
syntax.mkStr (syntax.mkDoubleQuoted [syntax.mkPlain "hello ", syntax.mkAntiquoted (syntax.mkSym "name")])

Indented (Multi-line String)

Definition

Indented { indent :: Int, parts :: [Antiquoted Text] }

Fields

FieldTypeDescription
indentIntIndentation level (spaces stripped from each line)
parts[Antiquoted Text]List of string parts

Nix Source ↔ AST

Simple Indented String

# Nix
''
  hello
  world
''

# String node
{
  "tag": "Indented",
  "indent": 2,
  "parts": [
    { "tag": "Plain", "contents": "hello\nworld" }
  ]
}

With Antiquotation

# Nix
''
  hello
  ${name}
''

# String node
{
  "tag": "Indented",
  "indent": 2,
  "parts": [
    { "tag": "Plain", "contents": "hello\n" },
    { "tag": "Antiquoted", "contents": { "tag": "Sym", "contents": "name" } }
  ]
}

Escaped Newline

# Nix
''
  first line \
  second line
''

# String node
{
  "tag": "Indented",
  "indent": 2,
  "parts": [
    { "tag": "Plain", "contents": "first line " },
    { "tag": "EscapedNewline" },
    { "tag": "Plain", "contents": "second line" }
  ]
}

Expression Wrapper

{
  "tag": "Str",
  "contents": { "tag": "Indented", "indent": 2, "parts": [...] }
}

Nix Library Access

syntax.mkStr (syntax.mkIndented 2 [syntax.mkPlain "hello\nworld"])

AttrPath

Definition

NonEmpty KeyName

Description

AttrPath represents a path to an attribute in an attribute set, like foo.bar.baz. An attribute path must be non-empty: it always contains at least one key.

Nix Source ↔ AST

{
  "tag": "NamedVar",
  "attrPath": [
    { "tag": "StaticKey", "contents": "foo" },
    { "tag": "StaticKey", "contents": "bar" }
  ],
  "value": { ... }
}

Examples

foo.bar.baz
  → [ StaticKey "foo", StaticKey "bar", StaticKey "baz" ]

"${name}.static"
  → [ DynamicKey (Antiquoted (Sym "name")), StaticKey "static" ]

Used as the attrPath field of:

  • NamedVar: binding left-hand side
  • Select: attribute selection
  • HasAttr: attribute existence check

Nix Library Access

syntax.mkStaticKey "foo"
syntax.mkDynamicKey (syntax.mkAntiquoted (syntax.mkSym "name"))

Operators

Binary Operators

OperatorDescriptionExample
==Equalityx == y
!=Inequalityx != y
<Less thanx < y
<=Less than or equalx <= y
>Greater thanx > y
>=Greater than or equalx >= y
&&Logical ANDx && y
||Logical ORx || y
->Implicationx -> y
//Set update/mergex // y
+Additionx + y
-Subtractionx - y
*Multiplicationx * y
/Divisionx / y
++List/string concatenationx ++ y

Example

# Nix source
x + y

# AST
{
  "tag": "Binary",
  "op": "+",
  "left": { "tag": "Sym", "contents": "x" },
  "right": { "tag": "Sym", "contents": "y" }
}

Unary Operators

OperatorDescriptionExample
-Numeric negation-x
!Logical NOT!x

Example

# Nix source
!x

# AST
{
  "tag": "Unary",
  "op": "!",
  "arg": { "tag": "Sym", "contents": "x" }
}

Special Operators

Operationnix-ast node
Function applicationApp
Attribute selectionSelect
Has attributeHasAttr

Nix Library Access

syntax.mkBinary "+" (syntax.mkSym "x") (syntax.mkSym "y")
syntax.mkUnary "!" (syntax.mkSym "x")
syntax.mkBinary "==" (syntax.mkInt 1) (syntax.mkInt 1)
syntax.mkBinary "//" (syntax.mkSym "a") (syntax.mkSym "b")
syntax.mkBinary "++" (syntax.mkSym "xs") (syntax.mkSym "ys")

ParamSet

VarName

Description

VarName represents identifiers in Nix code: variable names, attribute names, parameter names, etc.

Nix Source ↔ AST

# Nix
myVariable

# AST (inside Sym)
{ "tag": "Sym", "contents": "myVariable" }

Used as the contents field of:

  • Sym: variable references
  • Param: function parameters
  • StaticKey: static attribute keys
  • Inherit: inherited names
  • ParamSet: parameter set parameter names

Nix Library Access

syntax.mkSym "myVariable"