AST Authoring Best Practices
This guide covers writing and mechanically editing *.x07.json (x07AST) and using the toolchain to catch common AST-shape mistakes early.
Canonical Workflow
- Keep AST JSON canonical while editing:
x07 fmt --input program.x07.json --write
x07 lint --input program.x07.json
x07 fix --input program.x07.json --write
- Use
x07 checkin two modes:
# Fast feedback for AST authoring: schema/shape + lint only.
x07 check --project x07.json --ast
# Full check: lint + typecheck + backend-check.
x07 check --project x07.json
Notes:
x07 check --astruns schema validation + shape validation + lint and emits anx07diagreport.x07 checkalso runs project-wide typecheck and backend-check, and surfaces codegen errors in unreachable declarations (fail-fast).
- Try it on the runnable example project:
docs/examples/ast-authoring-best-practices/
x07 check --project docs/examples/ast-authoring-best-practices/x07.json --ast
x07 check --project docs/examples/ast-authoring-best-practices/x07.json
printf 'hello' | x07 run --repair=off --project docs/examples/ast-authoring-best-practices/x07.json --stdin
Common Shape Errors
Binary operators are binary
Operators like + are binary in x07AST:
- Valid form:
["+", <a>, <b>] - Suggested fix for n-ary uses: nest left-associatively (for example
["+", ["+", a, b], c])
x07 check --ast reports this as X07-ARITY-BINOP-0001, and a quickfix may be available for some cases.
for loops have a strict shape
Valid form:
["for", "i", <start:i32>, <end:i32>, <body:any>]
If you need multiple body expressions, wrap them in begin:
["for", "i", start, end, ["begin", ...]]
bytes vs bytes_view: move vs borrow
bytesis an owned buffer (move-only).bytes_viewis a borrowed view into an owned buffer.
Common pitfalls (and fixes):
-
Borrowing a view from a temporary triggers
X07-BORROW-0001.- Bad:
["bytes.view", <expr>]where<expr>is not an identifier. - Fix: bind the owner with
letfirst (or runx07 fixif a quickfix is offered).
- Bad:
-
Borrowing a view from a literal: prefer
bytes.view_lit.- Bad:
["bytes.view", ["bytes.lit", "..."]] - Good:
["bytes.view_lit", "..."]
- Bad:
-
Asserts that consume inputs:
std.test.assert_bytes_eq(a: bytes, b: bytes, ...)moves both values.- If you need to reuse values after the assert, use
std.test.assert_view_eq(["bytes.view", "a"], ["bytes.view", "b"], ...).
- If you need to reuse values after the assert, use
-
Borrow-union returns: returning a
bytes_viewfrom a conditional where branches borrow from different owners is often rejected.- Fix: return an owned
bytesinstead (copy in each branch withview.to_bytes) or restructure so the view always borrows from the same owner.
- Fix: return an owned
A common fix for move errors is to copy before moving a value you still need:
["view.to_bytes", ["bytes.view", "x"]]
Dependency roots (imports)
If x07 check fails with an import error (“module not found”), the usual causes are:
- missing
x07.json.module_rootsentry for the directory that contains the module - missing or stale
x07.lock.json(or a dependency path that doesn’t exist on disk)
Canonical debugging loop:
x07 pkg tree --project x07.json
x07 pkg provides <module-id> --project x07.json
x07 pkg lock --project x07.json --check --offline
Expert Notes
- Diagnostic codes are documented in
docs/toolchain/diagnostic-codes.mdand searchable viax07 diag explain <CODE>. - For project-level check behavior and report outputs, see
docs/toolchain/cli.md(x07 check).