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
| Feature | Description |
|---|---|
| Parse | Parse any Nix expression (file, string, or stdin) into a structured, typed AST |
| Render | Convert an AST back to formatted, importable Nix code |
| Eval | Evaluate ASTs using hnix’s evaluator and get the results as JSON values |
| toAST | Convert native Nix values (ints, strings, lists, attrsets) to AST nodes (pure, no IFD) |
| fromAST | Convert an AST back to a native Nix value, the inverse of toAST (pure, no IFD) |
| Pattern Matching | Type-safe tag-based dispatch with match ast { Tag = handler; _ = fallback; } |
| Traversal | Recursive transform, rewrite, universe, children/rebuild, and contexts |
| Type-checked Constructors | All mk* builders validate argument types at runtime with descriptive error messages |
| CLI Tool | nix-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:
| API | Description |
|---|---|
lib.parse lib.render lib.eval | IFD-based: bridge to hnix’s parser/evaluator |
lib.toAST lib.fromAST | Pure Nix: convert between Nix values and ASTs |
lib.match lib.syntax lib.traversal | Work 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
- Core Functions:
parse,render,eval,toAST,fromAST - Pattern Matching: tag-based dispatch on node types
- Syntax: runtime-validated constructors and predicates
- Traversal: children, rebuild, transform, universe
- Core Functions:
- AST Reference: every node type with fields, JSON representation, and examples
- 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:
| Layer | Functions | Description |
|---|---|---|
| IFD bridge | parse, render, eval | Bridge to the Haskell parser/evaluator via Import From Derivation |
| Value conversion | toAST, fromAST | Pure Nix conversion between Nix values and AST nodes |
| AST tools | match, syntax, traversal | Pattern matching, constructors, and tree operations |
Pages
| Page | Description |
|---|---|
| Core Functions | parse, render, eval, toAST, fromAST |
| syntax: Constructors & Predicates | mk* builders and is* tag checks for all node types |
| match: Pattern Matching | Type-safe tag dispatch with match ast { ... } |
| traversal: Tree Operations | children, rebuild, transform, universe, contexts |
How IFD Works
parse, render, and eval use pkgs.runCommand to create a derivation that:
- Serializes the input (paths, ASTs) to a JSON file via
pkgs.writeText - Runs the
nix-astCLI binary inside the derivation, piping the JSON file to stdin - Captures stdout as the derivation output
- 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
| Type | Description |
|---|---|
| Expr | The main expression tree, with 19 constructors |
| Atom | Primitive constant values (Bool, Float, Int, Null, Uri) |
| VarName | Identifier name (simple string) |
| AttrPath | Non-empty list of KeyName for attribute paths |
| Binding | Left-hand side of attribute bindings (Inherit, NamedVar) |
| KeyName | Attribute path components (StaticKey, DynamicKey) |
| Operators | Binary and unary operators |
| Params | Function parameter definitions (Param, ParamSet) |
| String | String literal contents (DoubleQuoted, Indented) |
| Antiquoted | String 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
--expror--jsonflag 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:
| Mode | Flag | Input source | Output |
|---|---|---|---|
| Direct | --json JSON | Single AST as JSON string | Single 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:
| Mode | Flag | Input source | Output |
|---|---|---|---|
| Direct | --expr EXPR | Single Nix expression string | Single 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
Exprtype, and serialized to JSON. - Batch mode: Stdin is read as a JSON array of file paths. Each file is read concurrently using
mapConcurrentlywith 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:
| Mode | Flag | Input source | Output |
|---|---|---|---|
| Direct | --json JSON | Single AST as JSON string | Formatted 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 stdin | Files 0.nix, 1.nix, … in DIR |
How it works: Each AST is deserialized and pretty-printed back to formatted Nix source code.
- Direct mode (
--jsononly, 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>.nixin the specified directory. Files are numbered sequentially starting from 0. - Forbidden combination:
--json+--out-diris 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:
| Parameter | Type | Description |
|---|---|---|
pkgs | pkgs | Nixpkgs 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:
| Parameter | Type | Description |
|---|---|---|
pkgs | pkgs | Nixpkgs 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:
| Parameter | Type | Description |
|---|---|---|
pkgs | pkgs | Nixpkgs 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 Str → DoubleQuoted → Plain.
Supported types:
| Nix type | AST representation |
|---|---|
Bool | Constant (Bool b) |
Int | Constant (Int i) |
Float | Constant (Float f) |
null | Constant Null |
String | Str (DoubleQuoted [Plain s]) |
Path | LiteralPath |
List | List [items...] |
AttrSet | Set { 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:
toASTon a function:"toAST: cannot convert function to AST"toASTon 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 node | Nix type | Notes |
|---|---|---|
Constant | atom value | Int, Float, Bool, Null, Uri |
Str | String | Concatenates parts; only Str and text in interpolation |
EnvPath | String | Returns the path string directly |
LiteralPath | String | Returns the path string directly |
List | List | Recursively converts elements |
Set | AttrSet | Only 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
Strnodes or plain text insideAntiquotedparts - 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
| Constructor | Fields | Description |
|---|---|---|
Plain | contents: v | Literal value: Text in string parts, String in DynamicKey |
Antiquoted | contents: Expr | Embedded 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),Plainwraps literal text.Antiquoted String: inDynamicKey,Plainwraps aStringAST node (DoubleQuoted/Indented).
A string like "hello ${x} world" becomes a list:
[Plain "hello ", Antiquoted (Sym "x"), Plain " world"]
Pages
Related
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
| Field | Type | Description |
|---|---|---|
contents | Expr | The 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", ... }
}
}
Related
- Plain: literal text
- EscapedNewline: escaped newline
- String: containers
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”).
Related
- Plain: literal text
- Antiquoted: embedded expression
- Indented: indented string container
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
| Field | Type | Description |
|---|---|---|
contents | v | The 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": [...] } }
Related
- Antiquoted: embedded expression
- EscapedNewline: escaped newline in indented strings
- String:
DoubleQuoted/Indentedcontainers
Nix Library Access
syntax.mkPlain "hello " # text content (string parts)
syntax.mkPlain (syntax.mkDoubleQuoted [...]) # String node content (DynamicKey)
Atom
Constructors
| Constructor | Field | Type | Description |
|---|---|---|---|
Bool | contents | Bool | Boolean value (true/false) |
Float | contents | Float | Floating-point (not in Nix surface syntax) |
Int | contents | Integer | 64-bit integer |
Null | (none) | - | Null value |
Uri | contents | Text | URI 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
Related
- 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
| Field | Type | Description |
|---|---|---|
contents | Bool | True 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 }
}
Related
- 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
| Field | Type | Description |
|---|---|---|
contents | Float | Double-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 }
}
Related
- 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
| Field | Type | Description |
|---|---|---|
contents | Integer | Arbitrary-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.
Related
- 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" }
}
Related
- Constant: wrapper for atoms in expressions
Nix Library Access
syntax.mkNull
syntax.mkConstant syntax.mkNull
Uri (URI Atom)
Definition
Uri Text
Fields
| Field | Type | Description |
|---|---|---|
contents | Text | The 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.
Related
- Constant: wrapper for atoms in expressions
Nix Library Access
syntax.mkUri "https://example.com"
syntax.mkConstant (syntax.mkUri "https://example.com")
Binding
Constructors
| Constructor | Fields | Description |
|---|---|---|
Inherit | scope: Maybe Expr, names: [VarName] | Inherit variables from scope |
NamedVar | attrPath: AttrPath, value: Expr | Named variable binding |
Description
Bindings are used in Let expressions and Set (attribute set) expressions. They associate names with values.
Pages
Related
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
| Field | Type | Description |
|---|---|---|
scope | Maybe Expr | Optional 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"]
}
Related
- 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
| Field | Type | Description |
|---|---|---|
attrPath | AttrPath | Attribute path (non-empty list of KeyName) |
value | Expr | The 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 } }
}
Related
- Inherit: inherit binding
- AttrPath: attribute path structure
- KeyName:
StaticKey/DynamicKey - Let: let expressions
- Set: attribute sets
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 tag | Fields | Description |
|---|---|---|
Abs | params: Params, body: Expr | Function abstraction (lambda) |
App | func: Expr, arg: Expr | Function application |
Assert | cond: Expr, body: Expr | Assertion assert cond; body |
Binary | op: Text, left: Expr, right: Expr | Binary operator |
Constant | contents: Atom | Atomic constant wrapper |
EnvPath | contents: FilePath | Search-path <nixpkgs> |
HasAttr | expr: Expr, attrPath: AttrPath | Has-attribute check set ? attr |
If | cond: Expr, thenExpr: Expr, elseExpr: Expr | Conditional |
Let | bindings: [Binding], body: Expr | Let bindings |
List | contents: [Expr] | List [ ... ] |
LiteralPath | contents: FilePath | Relative/absolute path ./foo.nix |
Select | defaultValue: Maybe Expr, expr: Expr, selectPath: AttrPath | Attribute selection set.attr or default |
Set | recursive: Bool, bindings: [Binding] | Attribute set { ... } |
Str | contents: String | String expression (wraps String) |
Sym | contents: VarName | Variable reference |
SynHole | contents: VarName | Syntax hole/placeholder |
Unary | op: Text, arg: Expr | Unary operator |
With | namespace: Expr, body: Expr | With 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
- SynHole: syntax hole (nix-ast specific)
- EnvPath:
<nixpkgs>search path - LiteralPath:
./foo.nixpath - HasAttr:
set ? attrcheck
Function & Application
Control Flow
Data Structures
Bindings & Scope
Operations
References & Constants
See Also
- Operators: Binary and unary operator reference
- Atom: constant values
- Binding: let/set bindings
- Params: function parameters
Abs (Lambda Abstraction)
Definition
Abs { params :: Params, body :: Expr }
Fields
| Field | Type | Description |
|---|---|---|
params | Params | Function parameters (single Param or ParamSet) |
body | Expr | Function 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
| Field | Type | Description |
|---|---|---|
func | Expr | The function expression being applied |
arg | Expr | The 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
| Field | Type | Description |
|---|---|---|
cond | Expr | Condition that must evaluate to true |
body | Expr | Expression 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
| Field | Type | Description |
|---|---|---|
op | Text | Operator symbol (e.g., +, ==, &&) |
left | Expr | Left operand |
right | Expr | Right 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
| Field | Type | Description |
|---|---|---|
contents | Atom | The 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" }
}
Related
Nix Library Access
syntax.mkConstant (syntax.mkInt 42)
syntax.mkConstant (syntax.mkBool true)
syntax.mkConstant syntax.mkNull
EnvPath (Search Path)
Definition
EnvPath FilePath
Fields
| Field | Type | Description |
|---|---|---|
contents | FilePath | The 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"
}
Related
- LiteralPath: relative/absolute paths like
./foo.nix
Nix Library Access
syntax.mkEnvPath "nixpkgs"
syntax.mkEnvPath "nixos"
HasAttr (Has Attribute)
Definition
HasAttr { expr :: Expr, attrPath :: AttrPath }
Fields
| Field | Type | Description |
|---|---|---|
expr | Expr | The expression to check (typically a set) |
attrPath | AttrPath | The 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" }
]
}
Related
Nix Library Access
syntax.mkHasAttr (syntax.mkSym "set") [syntax.mkStaticKey "attr"]
If (Conditional)
Definition
If { cond :: Expr, thenExpr :: Expr, elseExpr :: Expr }
Fields
| Field | Type | Description |
|---|---|---|
cond | Expr | Condition expression (must evaluate to boolean) |
thenExpr | Expr | Expression when condition is true |
elseExpr | Expr | Expression 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
| Field | Type | Description |
|---|---|---|
bindings | [Binding] | List of local bindings (Inherit or NamedVar) |
body | Expr | Body 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" }
}
}
Related
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
| Field | Type | Description |
|---|---|---|
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
| Field | Type | Description |
|---|---|---|
contents | FilePath | The 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"
}
Related
- 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
| Field | Type | Description |
|---|---|---|
defaultValue | Maybe Expr | Default value if attribute not found (Nothing = no default, throws error) |
expr | Expr | The set expression to select from |
selectPath | AttrPath | The 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" }
]
}
Related
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
| Field | Type | Description |
|---|---|---|
recursive | Bool | true 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 } }
}
]
}
Related
Nix Library Access
syntax.mkSet false [syntax.mkNamedVar [syntax.mkStaticKey "x"] (syntax.mkInt 1)]
syntax.mkSet true [...]
Str (String Expression)
Definition
Str String
Fields
| Field | Type | Description |
|---|---|---|
contents | String | The 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" }
]
}
}
Related
- String:
DoubleQuotedandIndentedconstructors - Antiquoted:
Plain,Antiquoted,EscapedNewlineparts
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
| Field | Type | Description |
|---|---|---|
contents | VarName | The 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"
}
Related
- VarName: the underlying type
Nix Library Access
syntax.mkSym "x"
syntax.mkSym "builtins.map"
SynHole (Syntax Hole)
Definition
SynHole VarName
Fields
| Field | Type | Description |
|---|---|---|
contents | VarName | The 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
| Field | Type | Description |
|---|---|---|
op | Text | Operator symbol (- or !) |
arg | Expr | Operand 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
| Field | Type | Description |
|---|---|---|
namespace | Expr | The set expression whose attributes are brought into scope |
body | Expr | Body 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" }
}
Related
- Let: similar but with explicit bindings
Nix Library Access
syntax.mkWith (syntax.mkSym "pkgs") (syntax.mkSym "hello")
KeyName
Constructors
| Constructor | Fields | Description |
|---|---|---|
DynamicKey | contents: Antiquoted String | Dynamic/computed key |
StaticKey | contents: VarName | Static 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
Related
Nix Library Access
syntax.mkStaticKey "foo"
syntax.mkDynamicKey (syntax.mkAntiquoted (syntax.mkSym "name"))
DynamicKey (Dynamic Attribute Key)
Definition
DynamicKey (Antiquoted String)
Fields
| Field | Type | Description |
|---|---|---|
contents | Antiquoted String | A 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" }
}
}
Related
- StaticKey: static identifier keys
- String:
DoubleQuoted/Indentedstring nodes - Antiquoted: string parts
- NamedVar: uses
AttrPathofKeyName
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
| Field | Type | Description |
|---|---|---|
contents | VarName | The 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)
Related
- DynamicKey: dynamic/computed keys
- VarName: underlying type
- NamedVar: uses
AttrPathofKeyName
Nix Library Access
syntax.mkStaticKey "foo"
syntax.mkStaticKey "my_var_123"
Params
Constructors
| Constructor | Fields | Description |
|---|---|---|
ParamSet | paramSetName: Maybe VarName, variadic: Bool, params: ParamSet | Parameter set { x, y ? 1, ... } |
Param | contents: VarName | Single 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
Related
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
| Field | Type | Description |
|---|---|---|
paramSetName | Maybe VarName | Name for @-pattern (e.g., args @ { x, y }) |
variadic | Bool | true if ... is present (allows extra arguments) |
params | ParamSet | The 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]
]
}
Related
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
| Field | Type | Description |
|---|---|---|
contents | VarName | The 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 } }
}
}
Related
Nix Library Access
syntax.mkParam "x"
String
Constructors
| Constructor | Fields | Description |
|---|---|---|
DoubleQuoted | contents: [Antiquoted Text] | Standard string "..." |
Indented | indent: 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 textAntiquoted: embedded expression${...}EscapedNewline:\newline in indented strings
Related
- Str: expression wrapper
- Antiquoted: string parts
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
| Field | Type | Description |
|---|---|---|
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": [...] }
}
Related
- Indented: multi-line indented strings
- Antiquoted:
Plain,Antiquoted,EscapedNewlineparts - 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
| Field | Type | Description |
|---|---|---|
indent | Int | Indentation 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": [...] }
}
Related
- DoubleQuoted: standard strings
- Antiquoted:
Plain,Antiquoted,EscapedNewlineparts - Str: expression wrapper
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 sideSelect: attribute selectionHasAttr: attribute existence check
Related
Nix Library Access
syntax.mkStaticKey "foo"
syntax.mkDynamicKey (syntax.mkAntiquoted (syntax.mkSym "name"))
Operators
Binary Operators
| Operator | Description | Example |
|---|---|---|
== | Equality | x == y |
!= | Inequality | x != y |
< | Less than | x < y |
<= | Less than or equal | x <= y |
> | Greater than | x > y |
>= | Greater than or equal | x >= y |
&& | Logical AND | x && y |
|| | Logical OR | x || y |
-> | Implication | x -> y |
// | Set update/merge | x // y |
+ | Addition | x + y |
- | Subtraction | x - y |
* | Multiplication | x * y |
/ | Division | x / y |
++ | List/string concatenation | x ++ y |
Example
# Nix source
x + y
# AST
{
"tag": "Binary",
"op": "+",
"left": { "tag": "Sym", "contents": "x" },
"right": { "tag": "Sym", "contents": "y" }
}
Unary Operators
| Operator | Description | Example |
|---|---|---|
- | Numeric negation | -x |
! | Logical NOT | !x |
Example
# Nix source
!x
# AST
{
"tag": "Unary",
"op": "!",
"arg": { "tag": "Sym", "contents": "x" }
}
Special Operators
| Operation | nix-ast node |
|---|---|
| Function application | App |
| Attribute selection | Select |
| Has attribute | HasAttr |
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 referencesParam: function parametersStaticKey: static attribute keysInherit: inherited namesParamSet: parameter set parameter names
Related
Nix Library Access
syntax.mkSym "myVariable"