on master help sign in
211 files changed+134700−42927
since splitsnapshots
unifiedsplit
.DS_Store .cargo/config.toml+17 .github/workflows/clippy.yml+25 .github/workflows/release-binaries.yml+85 .github/workflows/release-vscode.yml+43 .gitignore+30 .hermes/RECORD_MIGRATION_SPEC.md+48 .hermes/UNIFIED_NAMESPACE_BRIEF.md+447 .hermes/doccheck.py+344 .hermes/plans/2026-07-22-sources-in-records.md+212 .hermes/sql-pushdown-opportunities.md+102 Bool CLAUDE.md−110 Cargo.lock+2840−450 Cargo.toml+31−9 DESIGN.md−1165 README.md+6−42 base.md−551 crates/knot-compile-rt/Cargo.toml+14 crates/knot-compile-rt/src/lib.rs+117 crates/knot-compiler/Cargo.toml+26−1 crates/knot-compiler/build.rs+188 crates/knot-compiler/src/base.rs+461−82 crates/knot-compiler/src/builtins.rs+252 crates/knot-compiler/src/codegen.rs+19946−5237 crates/knot-compiler/src/decl_view.rs+176 crates/knot-compiler/src/desugar.rs+1233−541 crates/knot-compiler/src/effects.rs−991 crates/knot-compiler/src/infer.rs+14195−2000 crates/knot-compiler/src/lib.rs+8−3 crates/knot-compiler/src/linker.rs+42−6 crates/knot-compiler/src/lockfile.rs+601−373 crates/knot-compiler/src/main.rs+1076−57 crates/knot-compiler/src/modules.rs−205 crates/knot-compiler/src/nonterm.rs+384 crates/knot-compiler/src/refine_smt.rs+254 crates/knot-compiler/src/sql_lint.rs+1247 crates/knot-compiler/src/stack.rs+90 crates/knot-compiler/src/stratify.rs+672−233 crates/knot-compiler/src/types.rs+2250−262 crates/knot-compiler/src/unused.rs+403 crates/knot-compiler/src/unwind.rs+333 crates/knot-compiler/tests/base_listing.rs+73 crates/knot-compiler/tests/compile_boundary.rs+334 crates/knot-compiler/tests/compile_panic_containment.rs+51 crates/knot-compiler/tests/descriptor_roundtrip.rs+84 crates/knot-descriptor/Cargo.toml+6 crates/knot-descriptor/src/lib.rs+376 crates/knot-jit/Cargo.toml+25 crates/knot-jit/src/lib.rs+365 crates/knot-jit/tests/adts.rs+109 crates/knot-jit/tests/cli_overrides.rs+982 crates/knot-jit/tests/concurrency.rs+94 crates/knot-jit/tests/constraints.rs+115 crates/knot-jit/tests/crypto.rs+116 crates/knot-jit/tests/e2e.rs+149 crates/knot-jit/tests/errors.rs+111 crates/knot-jit/tests/extract.rs+438 crates/knot-jit/tests/functions.rs+304 crates/knot-jit/tests/harness.rs+139 crates/knot-jit/tests/http.rs+166 crates/knot-jit/tests/jit_e2e.rs+197 crates/knot-jit/tests/lists.rs+132 crates/knot-jit/tests/literal_pushdown.rs+155 crates/knot-jit/tests/migration.rs+485 crates/knot-jit/tests/migration_corner.rs+1431 crates/knot-jit/tests/nested_and_meta.rs+85 crates/knot-jit/tests/numeric_text.rs+154 crates/knot-jit/tests/patterns.rs+123 crates/knot-jit/tests/persistence.rs+968 crates/knot-jit/tests/refinements.rs+273 crates/knot-jit/tests/relations.rs+292 crates/knot-jit/tests/scalars.rs+238 crates/knot-jit/tests/single_value.rs+174 crates/knot-jit/tests/smoke.rs+31 crates/knot-jit/tests/text_bytes.rs+120 crates/knot-jit/tests/type_errors.rs+107 crates/knot-jit/tests/units.rs+103 crates/knot-jit/tests/views.rs+68 crates/knot-lsp/Cargo.toml+1 crates/knot-lsp/src/analysis.rs+740 crates/knot-lsp/src/builtins.rs+9 crates/knot-lsp/src/call_hierarchy.rs+295 crates/knot-lsp/src/code_action.rs+1804 crates/knot-lsp/src/code_lens.rs+124 crates/knot-lsp/src/completion.rs+1727 crates/knot-lsp/src/defs.rs+665 crates/knot-lsp/src/diagnostics.rs+223 crates/knot-lsp/src/document_highlight.rs+87 crates/knot-lsp/src/document_symbol.rs+254 crates/knot-lsp/src/folding.rs+153 crates/knot-lsp/src/formatting.rs+282 crates/knot-lsp/src/goto.rs+176 crates/knot-lsp/src/hover.rs+547 crates/knot-lsp/src/incremental.rs+535 crates/knot-lsp/src/inlay_hints.rs+1219 crates/knot-lsp/src/legend.rs+53 crates/knot-lsp/src/lib.rs+1 crates/knot-lsp/src/linked_editing.rs+334 crates/knot-lsp/src/main.rs+1274−1494 crates/knot-lsp/src/parsed_type.rs+743 crates/knot-lsp/src/references.rs+153 crates/knot-lsp/src/rename.rs+1090 crates/knot-lsp/src/selection_range.rs+182 crates/knot-lsp/src/semantic_tokens.rs+824 crates/knot-lsp/src/shared.rs+1147 crates/knot-lsp/src/signature_help.rs+285 crates/knot-lsp/src/state.rs+804 crates/knot-lsp/src/type_format.rs+345 crates/knot-lsp/src/utils.rs+556 crates/knot-lsp/src/workspace_diagnostics.rs+722 crates/knot-lsp/src/workspace_symbol.rs+212 crates/knot-runtime/Cargo.toml+19−6 crates/knot-runtime/src/db_dump.rs+143 crates/knot-runtime/src/lib.rs+24073−5598 crates/knot-runtime/src/log.rs+305 crates/knot-runtime/src/tui.rs+293−236 crates/knot/src/ast.rs+525−216 crates/knot/src/diagnostic.rs+111−77 crates/knot/src/format.rs+1523 crates/knot/src/lexer.rs+551−358 crates/knot/src/lib.rs+1 crates/knot/src/parser.rs+3315−2127 crates/knot/tests/parser.rs−3476 editors/vscode/.gitignore+3 editors/vscode/.vscodeignore+4 editors/vscode/icon.png editors/vscode/language-configuration.json+27 editors/vscode/package-lock.json+3027 editors/vscode/package.json+98 editors/vscode/src/extension.ts+119 editors/vscode/syntaxes/knot.tmLanguage.json+299 editors/vscode/tsconfig.json+15 examples/.gitignore+1 examples/apiserver.knot+92 examples/arith_aggregate.knot+23 examples/arith_aggregate.schema.lock+7 examples/associated_types.knot−21 examples/builtins.knot+31 examples/bytes.knot+54−36 examples/cons_pattern.knot+16 examples/constraints.knot+15−15 examples/constraints.schema.lock+6−9 examples/crypto.knot+51−31 examples/fold_dictionaries.knot+20 examples/fold_stream.knot+33 examples/fold_stream.schema.lock+7 examples/for_each.knot+12 examples/forall.knot+27 examples/groupby.knot+17−16 examples/groupby.schema.lock−8 examples/hash.knot+25 examples/hello.knot+7−6 examples/hello.schema.lock+5−6 examples/hole.knot+35 examples/implicit_dictionaries.knot+31 examples/inline_pushdown.knot+55 examples/inline_pushdown.schema.lock+7 examples/json.knot+24−20 examples/let_pushdown.knot+39 examples/log_test.knot+12 examples/maybe.knot+34−62 examples/migrate.knot+34 examples/modules/lib/core.knot−1 examples/modules/lib/helpers.knot−3 examples/modules/main.knot−10 examples/modules/main.schema.lock−21 examples/modules/transitive.knot−5 examples/modules/types.knot−5 examples/modules/utils.knot−3 examples/morph.knot+45 examples/nested.knot+27−17 examples/nested.schema.lock+5−6 examples/ord.knot+61 examples/query_opt.knot+119−85 examples/query_opt.schema.lock+7−11 examples/race.knot+30 examples/recursive.knot+18−24 examples/recursive.schema.lock+4−4 examples/result.knot+30−51 examples/routes.knot+33−32 examples/routes.schema.lock−12 examples/showcase.knot+407−268 examples/showcase.schema.lock+28−88 examples/stm_row_filter.knot+35 examples/stm_row_filter.schema.lock+7 examples/temporal.knot−29 examples/temporal.schema.lock−5 examples/todo.knot+20−18 examples/todo.schema.lock+5−6 examples/trait_replacement.knot+25 examples/trait_replacement.o examples/traits.knot−58 examples/uuid.knot+8 examples/views.knot−45 examples/views.schema.lock−15 examples/views_history.knot−49 examples/views_history.schema.lock−11 examples/when_unless.knot+7 knot.md−812 logo.png stdlib.md−658 tree-sitter-knot/grammar.js+196−38 tree-sitter-knot/queries/folds.scm+5 tree-sitter-knot/queries/highlights.scm+107−15 tree-sitter-knot/queries/indents.scm+4 tree-sitter-knot/queries/locals.scm+9−3 tree-sitter-knot/queries/tags.scm+24−2 tree-sitter-knot/src/grammar.json+1050−71 tree-sitter-knot/src/node-types.json+1577−117 tree-sitter-knot/src/parser.c+20699−14193
.DS_Storedeleted

Binary file not shown.

.cargo/config.tomlnew file+17
@@ -0,0 +1,17 @@
1+# Export the host binary's `knot_*` runtime symbols into the dynamic symbol
2+# table so code JIT-compiled at runtime (the `compile` builtin / knot-jit) can
3+# resolve them against the running process via dlsym(RTLD_DEFAULT). AOT builds
4+# also pass this via knot-compiler's linker (linker.rs); this config covers
5+# Rust-built binaries in this workspace (tests, dev tools).
6+#
7+# The flag is linker-specific: `--export-dynamic` is the GNU/ELF form (Linux),
8+# while macOS's ld64 (Mach-O) uses `-export_dynamic` — and rejects the GNU
9+# spelling with "linker command failed". Scope per-target so cross-building
10+# the macOS release binary isn't broken by the ELF flag. (macOS also gets
11+# `-force_load`/`-export_dynamic` from knot-compiler's build.rs, so it needs
12+# nothing here.)
13+[target.x86_64-unknown-linux-gnu]
14+rustflags = ["-C", "link-args=-Wl,--export-dynamic"]
15+
16+[target.aarch64-unknown-linux-gnu]
17+rustflags = ["-C", "link-args=-Wl,--export-dynamic"]
.github/workflows/clippy.ymlnew file+25
@@ -0,0 +1,25 @@
1+name: Clippy
2+
3+on:
4+ push:
5+ branches: [master]
6+ pull_request:
7+
8+env:
9+ CARGO_TERM_COLOR: always
10+
11+jobs:
12+ clippy:
13+ runs-on: ubuntu-latest
14+ steps:
15+ - uses: actions/checkout@v4
16+
17+ - name: Install Rust toolchain
18+ run: rustup component add clippy
19+
20+ - uses: Swatinem/rust-cache@v2
21+ with:
22+ key: clippy
23+
24+ - name: Run clippy
25+ run: cargo clippy --all-targets --workspace -- -D warnings
.github/workflows/release-binaries.ymlnew file+85
@@ -0,0 +1,85 @@
1+name: Release Binaries
2+
3+on:
4+ push:
5+ branches: [master]
6+ workflow_dispatch:
7+
8+permissions:
9+ contents: write
10+
11+jobs:
12+ version:
13+ runs-on: ubuntu-latest
14+ outputs:
15+ tag: ${{ steps.version.outputs.tag }}
16+ steps:
17+ - uses: actions/checkout@v4
18+
19+ - name: Compute version from commit date
20+ id: version
21+ run: |
22+ read YEAR MONTH DAY HOUR MINUTE <<< \
23+ "$(TZ=UTC git log -1 --format=%cd --date=format-local:'%Y %-m %-d %-H %-M')"
24+ TIME=$((HOUR * 100 + MINUTE))
25+ echo "tag=${YEAR}.${MONTH}.${DAY}.${TIME}" >> "$GITHUB_OUTPUT"
26+
27+ build:
28+ needs: version
29+ strategy:
30+ fail-fast: false
31+ matrix:
32+ include:
33+ - os: ubuntu-latest
34+ target: x86_64-unknown-linux-gnu
35+ name: linux-x86_64
36+ - os: ubuntu-24.04-arm
37+ target: aarch64-unknown-linux-gnu
38+ name: linux-aarch64
39+ - os: macos-15
40+ target: aarch64-apple-darwin
41+ name: macos-aarch64
42+ runs-on: ${{ matrix.os }}
43+ env:
44+ CARGO_TERM_COLOR: always
45+ steps:
46+ - uses: actions/checkout@v4
47+
48+ - name: Install Rust target
49+ run: rustup target add ${{ matrix.target }}
50+
51+ - uses: Swatinem/rust-cache@v2
52+ with:
53+ key: ${{ matrix.target }}
54+
55+ - name: Build knot, knot-lsp, and runtime
56+ run: cargo build --release --target ${{ matrix.target }} -p knot-compiler -p knot-lsp -p knot-runtime
57+
58+ - name: Package knot
59+ shell: bash
60+ run: |
61+ STAGE="knot-${{ needs.version.outputs.tag }}-${{ matrix.name }}"
62+ mkdir -p "$STAGE"
63+ cp "target/${{ matrix.target }}/release/knot" "$STAGE/"
64+ cp "target/${{ matrix.target }}/release/libknot_runtime.a" "$STAGE/"
65+ tar -czf "$STAGE.tar.gz" "$STAGE"
66+ shasum -a 256 "$STAGE.tar.gz" > "$STAGE.tar.gz.sha256"
67+
68+ - name: Package knot-lsp
69+ shell: bash
70+ run: |
71+ STAGE="knot-lsp-${{ needs.version.outputs.tag }}-${{ matrix.name }}"
72+ mkdir -p "$STAGE"
73+ cp "target/${{ matrix.target }}/release/knot-lsp" "$STAGE/"
74+ tar -czf "$STAGE.tar.gz" "$STAGE"
75+ shasum -a 256 "$STAGE.tar.gz" > "$STAGE.tar.gz.sha256"
76+
77+ - name: Upload to release
78+ uses: softprops/action-gh-release@v2
79+ with:
80+ tag_name: ${{ needs.version.outputs.tag }}
81+ name: Knot ${{ needs.version.outputs.tag }}
82+ files: |
83+ knot-*.tar.gz
84+ knot-*.tar.gz.sha256
85+ make_latest: true
.github/workflows/release-vscode.ymlnew file+43
@@ -0,0 +1,43 @@
1+name: Release VS Code Extension
2+
3+on:
4+ push:
5+ branches: [master]
6+
7+jobs:
8+ release:
9+ runs-on: ubuntu-latest
10+ permissions:
11+ contents: write
12+ steps:
13+ - uses: actions/checkout@v4
14+
15+ - uses: actions/setup-node@v4
16+ with:
17+ node-version: "20"
18+
19+ - name: Compute version from commit date
20+ id: version
21+ run: |
22+ read YEAR MONTH DAY HOUR MINUTE <<< \
23+ "$(TZ=UTC git log -1 --format=%cd --date=format-local:'%Y %-m %-d %-H %-M')"
24+ TIME=$((HOUR * 100 + MINUTE))
25+ # VSIX requires valid semver (3 segments): encode month+day into minor
26+ VSIX_MINOR=$((MONTH * 100 + DAY))
27+ echo "vsix=${YEAR}.${VSIX_MINOR}.${TIME}" >> "$GITHUB_OUTPUT"
28+ echo "tag=${YEAR}.${MONTH}.${DAY}.${TIME}" >> "$GITHUB_OUTPUT"
29+
30+ - name: Package extension
31+ working-directory: editors/vscode
32+ run: |
33+ npm ci
34+ npm version "${{ steps.version.outputs.vsix }}" --no-git-tag-version --allow-same-version
35+ npx @vscode/vsce package
36+
37+ - name: Create release
38+ uses: softprops/action-gh-release@v2
39+ with:
40+ tag_name: ${{ steps.version.outputs.tag }}
41+ name: Knot VS Code ${{ steps.version.outputs.tag }}
42+ files: editors/vscode/*.vsix
43+ make_latest: true
.gitignore+30
@@ -2,6 +2,10 @@
22*.db
33*.db-shm
44*.db-wal
5+.DS_Store
6+/.vibe
7+/.vix
8+.opencode-tmp
59examples/hello
610examples/todo
711examples/traits
@@ -22,3 +26,29 @@ examples/modules/main
2226examples/views_history
2327examples/query_opt
2428examples/showcase
29+examples/crypto
30+examples/log_test
31+examples/fold_stream
32+examples/inline_pushdown
33+examples/hash
34+examples/let_pushdown
35+examples/cons_pattern
36+examples/required_args
37+examples/find_first
38+examples/when_unless
39+examples/for_each
40+examples/stm_row_filter
41+examples/uuid
42+examples/race
43+examples/builtins
44+examples/implicit_dictionaries
45+examples/ord
46+examples/trait_replacement
47+
48+# Schema lockfiles (regenerated on each build)
49+*.schema.lock
50+__pycache__/
51+*.pyc
52+
53+# Hermes agent workspace (local tooling/notes)
54+/.hermes/
.hermes/RECORD_MIGRATION_SPEC.mdnew file+48
@@ -0,0 +1,48 @@
1+# Knot record/`with` syntax migration spec
2+
3+The knot language DROPPED the old record delimiters. Colon and comma are gone
4+from record EXPRESSIONS, record PATTERNS, and `with` blocks. Record TYPES keep
5+their old syntax. There is NO field punning anymore.
6+
7+## The parser rule (so output must re-parse)
8+A record field value is a SINGLE ATOM or a PARENTHESIZED compound. After a
9+completed value, a bare lowercase identifier ALWAYS opens the next field. So a
10+value that is an application, binary/unary op, lambda, `if`, `do`, `case`,
11+`with`, or any multi-token expression MUST be wrapped in parentheses.
12+
13+Atomic values (NO parens needed): integer/float/text/bool literals, a variable
14+(`x`), a field-access chain (`r.a`, `a.b.c`), a constructor (`Just`, `Nothing`),
15+a nested record (`{...}`), a list (`[...]`), an already-parenthesized expr.
16+
17+## Transformations
18+
19+1. Record EXPRESSION `{f: v, g: w}` -> `{f v g w}`
20+ - drop `:` after each field name, drop `,` separators, use ONE space.
21+ - parenthesize non-atomic `v`/`w`:
22+ `{value: f value}` -> `{value (f value)}`
23+ `{n: 1 + 2}` -> `{n (1 + 2)}`
24+ `{name: e.name}` -> `{name e.name}` (field-access is atomic)
25+ `{x: {a: 1}}` -> `{x {a 1}}` (nested record, recurse)
26+
27+2. Record UPDATE — REMOVED. `{base | f: v, g: w}` / `{base | f v g w}` no
28+ longer parse. Use `base.unify base {f v g w}` instead (right-biased merge;
29+ it is update AND extension, and works on open rows like lambda params).
30+
31+3. `with` BLOCK `with {x: e, y: e2} body` -> `with {x e y e2} body`
32+ - field names are the bindings; same value-parenthesization rule.
33+
34+4. Record PATTERN `{f: p, g: q}` -> `{f p g q}`
35+ - PUNNING GONE: `Just {value}` -> `Just {value value}`
36+ `Just {value: x}` -> `Just {value x}`
37+ - empty patterns `Nothing {}` / `Just {}` unchanged.
38+
39+## DO NOT CHANGE
40+- Record TYPES in annotations/aliases: `{name: Text, age: Int}` (KEEP `:` `,`).
41+- Type annotations `x : Int`, signatures `f : a -> b`.
42+- Effect rows `IO {| e} {}` (the `{| e}` row-tail and trailing `{}` stay).
43+- Rust code itself — only the knot source inside string literals / .knot files.
44+
45+## Verify
46+After editing a file, run its test target and make it pass:
47+ cargo test -p <crate> --test <file_stem>
48+The compiler validates the new syntax; a green test run is the acceptance gate.
.hermes/UNIFIED_NAMESPACE_BRIEF.mdnew file+447
@@ -0,0 +1,447 @@
1+# Implementation brief — unified value/type namespace with type-directed explicit type arguments (erased)
2+
3+Scope: investigation only; no code changes. Anchors cite `crates/knot` and
4+`crates/knot-compiler` sources as of this writing.
5+
6+## 0. Verified starting facts
7+
8+- Parser emits a neutral `ExprKind::Constructor(name)` for any Upper name in
9+ expr position (parser.rs:2924-2927); lowercase → `ExprKind::Var`
10+ (parser.rs:2919-2922). No parser change needed for the *argument* side.
11+- Typechecker `Constructor(name)` arm: infer.rs:5064; `instantiate_ctor`
12+ (infer.rs:4935) returns `Some((data_ty, record_ty))` for a known data ctor,
13+ else the arm errors "unknown constructor" (infer.rs:5091-5096). This else
14+ branch is the unified-namespace hook. Confirmed today: `f = Int 1` in value
15+ position errors exactly there ("unknown constructor 'Int'").
16+- Application: infer-mode `App` arm at infer.rs:5313; generic path at
17+ infer.rs:5450-5457 (`arg_ty = infer_expr(arg); unify(func_ty, Fun(arg_ty,
18+ result))`). There is already a *directed* precedent: the higher-rank slot
19+ check at infer.rs:5435-5448 inspects the *function's* parameter type before
20+ deciding how to handle the argument (checks arg against `Ty::Forall`).
21+ There is NO separate check-mode `App` arm; `check_expr` falls through to
22+ infer+unify at infer.rs:6088-6093.
23+- Type binder form `\(T : Type)` is free syntax: any parenthesized
24+ typed-pattern is a parse error today ("expected ')' to close pattern
25+ group", parser.rs:3799 and 3922 — verified for both `\(T : Type)` and
26+ `\(x : Int 1)`). `parse_lambda` (parser.rs:3294) parses params via
27+ `parse_pat` (parser.rs:3709) in the loop at parser.rs:3310-3321.
28+- Erasure precedent: `ExprKind::TypeCtor` (ast.rs:293-297) is typed as its
29+ kind `Type -> ... -> Type` in infer (infer.rs:5868-5897) and erased to
30+ `knot_value_unit` in codegen (codegen.rs:5563-5567).
31+- `Ty::Con("Type", [])` is the kind marker (infer.rs:5874, 5890-5894);
32+ `Ty::Unit(..)` shows the "kind ≠ Type, no value inhabitants" pattern
33+ (types in infer.rs:376-381). A signature `f : Type -> Type` already
34+ typechecks today (`Type` elaborates via the default `Named` arm of
35+ `ast_type_to_ty`, infer.rs:4504-4506, to `Ty::Con("Type", [])`).
36+- `Ty::Forall(Vec<TyVar>, Box<Ty>)` exists (infer.rs:386);
37+ `skolemise_scheme` (infer.rs:3471) + the `check_expr` Forall arm
38+ (infer.rs:5912-5944) + `skolemise_forall_body` (infer.rs:1922) implement
39+ rigid-var scoping. Top-level `id : forall a. a -> a` works through
40+ pre-register (infer.rs:8114-8124 lifts outermost Forall into the Scheme).
41+- Inline `: forall` on an *infer-mode* lambda value is not rejected by
42+ syntax; it is handled (and effectively rejected via the escape check) at
43+ infer.rs:5801-5829 — message "polymorphic type escapes its scope … an
44+ inline `forall` annotation cannot make a monomorphic value polymorphic".
45+
46+## 1. TYPE BINDER — `\(T : Type)` AST + parser design
47+
48+Recommendation: **new `PatKind` variant**, not a lambda attribute or expr
49+wrapper.
50+
51+```rust
52+// ast.rs, in PatKind (after `Var`):
53+/// `(T : Type)` — a lambda-only type-witness binder. Binds the *type* `T`
54+/// in the body; erased at runtime (no value is passed).
55+TypeWitness { name: Name, kind: Type },
56+```
57+
58+Why a pattern:
59+- Lambda params are `Vec<Pat>` (ast.rs:218-221); every consumer already
60+ matches on `PatKind`, so the new form is additive at well-known sites
61+ (§6). A lambda-level attribute (`type_params: Vec<Name>`) would force
62+ positional alignment with `params` and complicate interleaving
63+ (`\(T : Type) x (U : Type) y -> …`); an `Expr` wrapper
64+ (`ExprKind::TyLam`) would duplicate the entire lambda handling in infer,
65+ codegen, desugar, effects, formatter, LSP — far from minimal-diff.
66+- Patterns already have a precedent for "binder with payload": `Constructor
67+ { name, payload }` (ast.rs:380).
68+
69+Parser change (small, two places):
70+- In `parse_lambda`'s param loop (parser.rs:3310-3321), before calling
71+ `parse_pat`, peek for `LParen Upper Colon` — or better, add the form
72+ inside `parse_pat_inner`'s `LParen` arm (parser.rs:3789-3802): after
73+ consuming `(`, if `peek` is `Upper(_)` *and* the token after is
74+ `TokenKind::Colon`, parse `Name : Type )` into `PatKind::TypeWitness`.
75+ Putting it in `parse_pat_inner` keeps one pattern entry point, but note
76+ `parse_pat` is also used for case arms — the new kind must then be
77+ *rejected in case patterns* (typechecker: `check_pattern` arm errors
78+ "type-witness binder is only allowed as a lambda parameter") unless you
79+ deliberately want type-level case. Recommend lambda-only for v1: enforce
80+ by a flag param (`parse_pat(allow_witness: bool)`) or by post-parse
81+ validation in `parse_lambda`.
82+- `push_pat_vars` (parser.rs:285-309): new arm — push nothing (a witness
83+ binds no *value* var) or push the name into a separate
84+ `bound_type_vars` stack if you want the parser to know `T` is in type
85+ scope (not strictly needed; the typechecker can own this).
86+
87+Signature surface form `\(T : Type) -> T -> T`:
88+- `ast::Type` does NOT need a new node for the *witness arrow itself* if
89+ you represent it as an ordinary `TypeKind::Function { param: Named
90+ "Type", result }` — i.e. the surface signature for the example is
91+ literally `f : Type -> T -> T`. But then `T` is an unbound lowercase var
92+ unless the *declaration* signature quantifies it: the natural surface
93+ spelling is `f : forall a. Type -> a -> a` … which is confusing.
94+- Recommended: add ONE new `TypeKind`:
95+
96+```rust
97+/// `(a : Type) -> T` — explicit type-witness arrow (dependent-ish arrow,
98+/// erased). Binds `a` in `T`.
99+TyArrow { var: Name, kind: Box<Type>, body: Box<Type> },
100+```
101+
102+ It elaborates in `ast_type_to_ty` (infer.rs:4406) to
103+ `Ty::Fun(Con("Type",[]), body)` with `annotation_vars[var]` bound to a
104+ fresh var *that is also registered as the witness* (see §2). The
105+ advantage over reusing `Function{Named "Type"}`: the name binding is
106+ explicit and scoped, and the formatter round-trips the user's syntax.
107+ Minimal alternative (zero new Type nodes): require users to write
108+ `f : forall a. Type -> a -> a` and treat the *first* `Type` argument
109+ positionally. Cheaper but worse UX and fragile with multiple witnesses.
110+
111+## 2. TYPE-WITNESS IN SCOPE
112+
113+Represent a bound `T` exactly like today's signature-bound rigid vars —
114+**reuse skolems + `annotation_vars`**, do not build a new mechanism:
115+
116+- When checking a lambda against an expected type (the `check_expr` Lambda
117+ arm, infer.rs:5959-5993, which peels `Fun` arrows), a peeled param whose
118+ type is `Ty::Con("Type", [])` paired with a `PatKind::TypeWitness{name}`
119+ binder should:
120+ 1. allocate a fresh TyVar `s`, insert into `self.skolems` (rigid),
121+ 2. `self.annotation_vars.insert(name, s)` — this is what makes a later
122+ param `x : T` (or an inner annotation `(e : T)`) resolve `T` to the
123+ same rigid var, because `ast_type_to_ty`'s `TypeKind::Var` arm
124+ (infer.rs:4509-4512) goes through `annotation_var`
125+ (infer.rs:4698-4706). NOTE: `TypeKind::Var` requires *lowercase*
126+ names; for uppercase witnesses (`T`) you need `TypeKind::Named("T")`
127+ to consult `annotation_vars` FIRST (before aliases/data) — a small,
128+ contained change at infer.rs:4408: at the top of the `Named` arm, `if
129+ let Some(v) = self.annotation_vars.get(name) { return Ty::Var(*v); }`
130+ guarded by a new flag (e.g. `in_witness_scope`) so ordinary
131+ annotations don't start resolving type names to vars.
132+ 3. NOT bind anything in the value scope (`self.bind`) — the witness has
133+ no value.
134+- At a call site, the witness var is just a scheme variable: when `f`'s
135+ scheme is instantiated (`instantiate_at`), the witness position becomes a
136+ fresh flexible var, and the explicit type argument unifies with it (§3).
137+ This is precisely how `forall a.` already scopes rigid vars through a
138+ body — no new scoping machinery.
139+- Drop the witness skolems at lambda scope exit (mirror the `for s in
140+ fresh_skolems { self.skolems.remove(&s); }` pattern at infer.rs:5941-5943)
141+ and restore the previous `annotation_vars` entry (mirror the save/restore
142+ in `ast_type_to_ty`'s Forall arm, infer.rs:4670-4692).
143+- Escape check: reuse the existing free-vars-in-env leak check
144+ (infer.rs:5930-5940) so a witness can't leak into the enclosing type.
145+
146+## 3. ARG REINTERPRETATION (type-directed application)
147+
148+The signal must live in the **function's type**: a leading parameter whose
149+type is the kind marker `Ty::Con("Type", [])`. That is already the
150+convention established by `TypeCtor` (infer.rs:5889-5896), so "this arrow
151+is a type arrow" is representable today with zero new `Ty` variants.
152+
153+In the infer-mode `App` arm (infer.rs:5313), extend the *existing* directed
154+precedent at infer.rs:5435-5448 (which already inspects the function's
155+param type before handling the arg):
156+
157+```rust
158+let func_ty = self.infer_expr(func);
159+let func_applied = self.apply(&func_ty);
160+if let Ty::Fun(arg_slot, ret_ty) = &func_applied {
161+ let arg_slot_resolved = self.apply(arg_slot);
162+ // NEW ARM — before the existing Forall arm:
163+ if matches!(arg_slot_resolved.peel_alias(), Ty::Con(n, args) if n == "Type" && args.is_empty()) {
164+ // The function expects a TYPE as its next argument. Reinterpret
165+ // the argument expression as a type.
166+ let ty_arg = self.expr_as_type(arg); // see below
167+ self.unify(arg_slot, &ty_arg, arg.span); // or unify_dir with polarity
168+ return (**ret_ty).clone();
169+ }
170+ if matches!(arg_slot_resolved, Ty::Forall(..)) { …existing… }
171+}
172+```
173+
174+`expr_as_type(arg)` — synthesize an `ast::Type` from the arg `Expr` and run
175+the existing `ast_type_to_ty`:
176+- `ExprKind::Constructor(name)` → `TypeKind::Named(name)` (aliases,
177+ parameterized data (`TyCon`), and plain `Con` all handled by the existing
178+ `Named` arm, infer.rs:4408-4508).
179+- `ExprKind::Var(name)` → `TypeKind::Var(name)` — resolves to an in-scope
180+ witness via `annotation_vars` (needs the §2 guard so lowercase works too,
181+ or map Var→Named for uniformity).
182+- `ExprKind::App{func, arg}` (e.g. `f (Maybe Int)`) →
183+ `TypeKind::App{func: expr_as_type(func), arg: expr_as_type(arg)}` —
184+ `ast_type_to_ty`'s App arm (infer.rs:4532-4562) handles `Con` arg
185+ accumulation, `TyCon`/`Var` HK application, and `expand_param_alias`
186+ (infer.rs:4353) already peels App spines.
187+- Anything else → error "expected a type expression".
188+
189+Important subtleties:
190+- `Int` / `Float` special case: the `Named` arm *errors* on bare `Int` in
191+ annotation mode (infer.rs:4409-4416: "bare `Int` requires a unit").
192+ `expr_as_type` should set `in_type_annotation = true` (consistent with
193+ how annotations behave) — so the explicit type arg for a dimensionless
194+ int is written `Int 1`, which parses in expr position as
195+ `App(Constructor("Int"), Lit(1))`. Handle by mapping that App shape to
196+ `TypeKind::UnitAnnotated` (ast.rs:498-501) when func is `Named
197+ "Int"/"Float"` and arg is `Lit(Int("1"))` — or more generally route
198+ through `parse_unit_type_arg` semantics. This is the main
199+ `Int 1`-collision risk (§7) and is solvable entirely inside
200+ `expr_as_type`.
201+- Also wire the same check into the two early-return App paths that bypass
202+ the generic path if they can precede a witness slot: the ctor-application
203+ path (infer.rs:5358-5364) and the lambda-last path (5402-5427). The
204+ lambda-last path triggers on `Var` heads with 2-arg signatures —
205+ `f Int (\x -> x)` where `f : Type -> (Int 1 -> Int 1) -> …` would hit it;
206+ make `takes_two_args` peeling (infer.rs:11474-11478) witness-aware or
207+ exclude functions whose first param is kind `Type`.
208+- Erasure of the arg: because the new App arm returns before ever calling
209+ `infer_expr(arg)` on the type argument, the argument's inferred-type
210+ record never binds a value type for it. For codegen, mark the arg span in
211+ a side set (`self.type_arg_spans.insert(arg.span)`) so codegen's
212+ `compile_app` can skip emitting it (§5). Record the *witness→type*
213+ binding (`(func.span, TyVar) → Ty`) in another side table
214+ (`witness_bindings`, same pattern as `show_calls` infer.rs:5472-5475 and
215+ `traverse_calls` 5480-5486) if codegen ever needs the resolved type
216+ (e.g. for runtime type-directed dispatch later). For pure erasure, the
217+ span set alone suffices.
218+
219+## 4. UNIFIED NAMESPACE — `Constructor(name)` else-branch
220+
221+Change infer.rs:5087-5096 from unconditional error to:
222+
223+1. If `instantiate_ctor` succeeded → existing value behavior (unchanged).
224+2. Else, if the name resolves as a *type* in scope — i.e. any of:
225+ - `self.aliases.contains_key(name)` or
226+ `self.param_aliases.contains_key(name)` (alias / parameterized alias),
227+ - `self.data_types.contains_key(name)` (a data type name used as a
228+ type — note `instantiate_ctor` looks up *constructor* names in
229+ `self.constructors`, which is keyed by ctor name, so a data type whose
230+ ctor names differ from the type name lands here),
231+ - `self.annotation_vars` contains `name` under the §2 witness flag,
232+ - `name` is `Int`/`Float`/`Text`/`Bool`/`Bytes`/`Uuid` (builtin types),
233+ then return the kind type `Ty::Con("Type", [])` (or `Type -> … -> Type`
234+ for a parameterized head, mirroring the fold at infer.rs:5889-5895) and
235+ record `(expr.span → name)` in a side table so codegen erases this
236+ occurrence to unit (exactly like `TypeCtor`, codegen.rs:5563-5567).
237+3. Else keep the "unknown constructor" error.
238+
239+This makes `map Int [1,2]` (type-witness argument position) work through §3
240+*and* lets a bare type name appear wherever a kind-`Type` value is
241+expected.
242+
243+Precedence when a name is BOTH a data ctor and a type (e.g. `data Box a =
244+Box {value: a}` — `Box` is both `self.constructors["Box"]` and
245+`self.data_types["Box"]`):
246+- Value position: `instantiate_ctor` succeeds → data ctor wins. Already
247+ the behavior; unchanged.
248+- Type-witness position (§3 arm): `expr_as_type` maps
249+ `Constructor("Box")` → `TypeKind::Named("Box")` → `ast_type_to_ty`
250+ resolves the *type* (data_types/aliases), never consulting
251+ `self.constructors`. Type wins.
252+- So precedence is positional, not global: **ctor in value position, type
253+ in witness position** — confirmed implementable because the two paths
254+ never share a lookup. The only overlap is the §4 else-branch, which only
255+ fires when the name is NOT a ctor, so no ambiguity is introduced there.
256+- `check_expr` expected-type-aware variant (optional refinement): in
257+ `check_expr`'s fallthrough (infer.rs:6088-6093), a `Constructor` expr
258+ whose expected type is kind `Type` could be routed directly to
259+ `expr_as_type`. Not required for v1 since §3's App-directed path covers
260+ the intended use.
261+
262+## 5. ERASURE / CODEGEN
263+
264+- Lambda params become runtime params in `compile_lambda_inner`
265+ (codegen.rs:10996; signature fixed at codegen.rs:11060-11065 as
266+ `(db, env, arg)`; multi-param currying at 11005-11022; `PendingLambda`
267+ carries `param_pat`, 11072-11079; binding emitted via the
268+ `PatKind::Var(name) => env.set(name, …)` pattern at codegen.rs:3728 and
269+ the general binder at 8081-8126).
270+- Dropping witness params: in `compile_lambda_inner`, filter
271+ `PatKind::TypeWitness` out of `params` BEFORE the curry split and before
272+ `pat_bound_names` (codegen.rs:11029-11032). Because infer already typed
273+ the lambda as `Fun(Con("Type"), rest)`, erasing the param in codegen
274+ changes the *runtime* arity relative to the static arity — this is fine
275+ only if call sites also drop the corresponding argument. Hence:
276+- Call-site erasure: `compile_app` (called from codegen.rs:5064) must skip
277+ arguments whose span is in the `type_arg_spans` side set (§3). The
278+ curried-application machinery (codegen.rs:8635 peels nested lambdas for
279+ arity; check how partial application counts args — grep
280+ `ExprKind::App` spines around codegen.rs:5054 and the `compile_app`
281+ body) must count only runtime args. This is the trickiest codegen bit:
282+ every place that computes a function's arity from its *type* or from the
283+ lambda's `params.len()` (e.g. codegen.rs:1792 `params.len()`, 2959)
284+ needs to subtract witness params. Audit those three sites.
285+- Type-ctor-field erasure precedent confirmed: `TypeCtor` →
286+ `knot_value_unit` (codegen.rs:5563-5567); the same treatment applies to
287+ a bare `Constructor(name)` reinterpreted as a type in value position
288+ (§4): side-table the span in infer, erase to unit in codegen's
289+ `Constructor` arm.
290+- `pat_bound_names` (codegen.rs:16561-16583), `pretty_pat`
291+ (codegen.rs:17313-17342): new arm — witness binds no names; pretty-prints
292+ as `(T : Type)`.
293+
294+## 6. ALL MATCH/VISITOR SITES FOR NEW NODES
295+
296+New `PatKind::TypeWitness` arms needed:
297+- knot/parser.rs: `push_pat_vars` (285) — push nothing.
298+- knot/format.rs: `render_pat` (2345-2401 region, exact fn at 2345) — print
299+ `(T : Type)`; also the multiline lambda paths at 1007/1020/1075 reuse
300+ `render_pat`.
301+- knot-compiler/infer.rs: `check_pattern` (6779) — new arm implementing §2
302+ (only valid against kind-`Type` expected, else error "type-witness
303+ parameter must have kind Type"); ensure infer-mode Lambda
304+ (5295-5311) gives a witness param `Ty::Con("Type",[])` instead of
305+ `self.fresh()` when no expected type (or better: require an annotation —
306+ see Risks).
307+- knot-compiler/desugar.rs: `pat_bound_names` (670-674 region), the
308+ pattern walks at 656/726/961/1188 — new arm returning no names, and
309+ ensure no desugar rewrite synthesizes/breaks witness pats.
310+- knot-compiler/codegen.rs: `pat_bound_names` (16561), `pretty_pat`
311+ (17313), binder emission (3728, 8081-8126), lambda compile
312+ (10996-11035).
313+- knot-compiler/unused.rs: pattern walk (410-411) — do not mark the type
314+ name as a used *value*; ideally record the type-name use so `data` types
315+ referenced only via witnesses aren't flagged unused.
316+- knot-compiler/effects.rs: `pat`-walking helper at 1922-1923 region +
317+ lambda-param inspection at 1324 and 1659 (both match
318+ `ast::PatKind::Var(name)` on lambda params — with a witness param these
319+ just don't match; confirm no index-based param/arg correspondence is
320+ assumed there — effects.rs:1324/1659 pair params with effect info, audit).
321+- knot-compiler/stratify.rs: expr walker (247 region) — patterns not
322+ matched there beyond existing kinds; add arm if the walker is
323+ exhaustive.
324+- knot-compiler/sql_lint.rs: pattern sites (it matched `ExprKind` at
325+ 242/1097 for TypeCtor; check for PatKind matches) — likely none; verify
326+ with a build after adding the variant (exhaustive matches will flag).
327+- knot-lsp: any `PatKind` matches (semantic_tokens, hover, defs,
328+ references, document_highlight, rename) — the compiler will flag
329+ exhaustive matches; ensure hover on `(T : Type)` reports the *type*
330+ binding, and rename/references treat witness occurrences (which are
331+ `TypeKind::Named`/`Var` in annotations + `Constructor`/`Var` exprs in
332+ type-arg position) consistently.
333+
334+New `TypeKind::TyArrow` arms needed (only if you take the recommended
335+signature node):
336+- knot/format.rs: `render_type` (search `TypeKind::Function` around
337+ format.rs; render as `(a : Type) -> …`).
338+- knot-compiler/infer.rs: `ast_type_to_ty` (4406), `expand_param_alias`
339+ spine peeling (4353-4364 — treat TyArrow as non-App head), the
340+ type-walkers (`collect_refined_names` 1428, `collect_free_unit_vars`
341+ 3866, subst/apply at 1549/1831/3699/3769/4251 — only if they pattern
342+ match `ast::Type`, most operate on `Ty`).
343+- knot-lsp: `collect_names_in_type` (code_action.rs:2089-2119),
344+ `defs.rs` resolve_type (564-594), completion.rs:325/390/1668,
345+ signature_help/type_format — add recursion into `body`, and register
346+ `var` as a local type binder for goto-def/rename.
347+- knot-compiler/types.rs: `resolve_type` (schema resolution) — should
348+ treat `TyArrow` as transparent to `body` (witnesses erased; a source
349+ field can't have kind Type) or reject it in source schemas.
350+
351+No new `ExprKind` is required for the core design (arguments stay
352+`Constructor`/`Var`; reinterpretation happens in infer). If you prefer an
353+explicit marker for erased type args post-inference, you could rewrite the
354+arg into `ExprKind::TypeCtor`-like node in a post-inference pass (the
355+codebase already rewrites AST post-inference — see
356+`resolve_result_markers` and the rewrite at infer.rs:11598), but the
357+span side-table is less invasive.
358+
359+## 7. RISKS
360+
361+1. **`Int 1` collision**: In value position `Int 1` is currently an error
362+ (verified), so no regression, but in *witness* position `f Int` where
363+ the witness should be a dimensionless int must spell `Int 1` — and in
364+ expr position that is `App(Constructor("Int"), Lit(Int "1"))`. The §3
365+ `expr_as_type` must map that shape to `TypeKind::UnitAnnotated` (or set
366+ `in_type_annotation` and synthesize the unit arg). If missed, users get
367+ the confusing "bare `Int` requires a unit" error from a value-position
368+ argument. Contained in `expr_as_type`, but must be designed explicitly.
369+2. **HKT interplay**: `Ty::TyCon`/`Ty::App` unify arms (infer.rs:2238-2310)
370+ treat `App(f,a)` vs `Relation`/`Int`/`Float`/`IO` specially. A witness
371+ bound to a `TyCon` (e.g. `f Maybe`) produces a kind `Type -> Type`
372+ argument — the §3 arm's `unify(arg_slot, ty_arg)` would equate
373+ `Con("Type",[])` with `TyCon("Maybe")` and FAIL. So the witness slot's
374+ *kind* must be checked structurally: arg_slot `Type` accepts only
375+ fully-applied types (`Con(..)`, `Record`, `Relation`, `Var`,
376+ `Alias`); a `TyCon`/partially-applied arg needs arg_slot
377+ `Fun(Type, Type)`. Implement a tiny `kind_of(&Ty) ->
378+ Option<Kind-as-Ty-shape>` and unify kinds instead of hard-coding
379+ `Con("Type",[])`. The `TypeCtor` kind-fold (infer.rs:5889-5895) is the
380+ template.
381+3. **Forall instantiation**: witness params generalize like ordinary vars
382+ (`generalize`/`instantiate_at`), so `f = \(T : Type) x -> x` gets
383+ `∀t. Type -> t -> t` — good. But `check_pattern`'s Forall arm
384+ (infer.rs:6787) binds *value* vars with polymorphic schemes; do NOT
385+ route witness binders through it. Also the escape check
386+ (infer.rs:5930-5940) will fire if a witness skolem leaks into the
387+ enclosing env — that's desired, but the error message should mention
388+ type witnesses (currently says "add an explicit `forall` annotation").
389+4. **Infer-mode lambdas without signatures**: infer.rs:5295-5311 gives
390+ every param a fresh var; a `\(T : Type)` param there would unify
391+ `Con("Type",[])` with a flexible var — fine — but then the *body*'s use
392+ of `T` must connect: the body annotation `(x : T)` resolves `T` via
393+ `annotation_vars` — works only if the infer-mode Lambda arm also runs
394+ the §2 registration when it sees `PatKind::TypeWitness` (give the param
395+ type `Con("Type",[])` and bind name→fresh skolem/var in
396+ `annotation_vars`). Without a signature the witness var stays flexible
397+ and will be generalized — acceptable, mirrors HM.
398+5. **Formatter round-trip**: two new syntaxes — `(T : Type)` in lambda
399+ params and the witness arg (plain `Int`/`Maybe Int` in arg position).
400+ The former round-trips via `render_pat`. The latter is *just an
401+ expression* — `map Int xs` formats as an ordinary app, which re-parses
402+ to the same AST. Round-trip safe by construction; add
403+ format_roundtrip tests (crates/knot/tests/format_roundtrip.rs) with
404+ both forms. Careful: `render_pat` for `TypeWitness` must include the
405+ parens or the reparse breaks (`\(T : Type) -> …` without parens is a
406+ different — invalid — pattern sequence).
407+6. **Ambiguity of `Constructor` in witness position when both a ctor and a
408+ type exist**: resolved positionally (§4), but the *error messages* must
409+ not regress: `unknown constructor` currently fires for type names used
410+ as values; after §4, `Box` alone (a non-nullary ctor) used as a value
411+ argument where a value is expected still works (ctor wins), while `map
412+ Box xs` reinterprets. Document the rule; add tests where a name is both
413+ (e.g. `data Maybe a = Just {value: a} | Nothing` + `data Box a = Box
414+ {value: a}`).
415+7. **Desugar before infer**: desugar rewrites lambdas for do-blocks and
416+ comprehensions (desugar.rs:1223-1292 synthesizes wildcard/var params);
417+ ensure no rewrite assumes all params are value patterns (e.g. eta-expansion
418+ or `__result` wrapping at infer.rs:5327-5347 operates on exprs, fine).
419+8. **`expand_param_alias` spine peeling** (infer.rs:4353-4364): a witness
420+ arg like `Pair Int Text` goes through this path already; but if the
421+ witness head is a *witness variable* (`\(F : Type -> Type) … f (F
422+ Int)`), `expr_as_type` yields `App(Var F, Int)` — `ast_type_to_ty`'s
423+ App arm handles `Ty::Var` func via `Ty::App` (4556-4558). Requires the
424+ §2 change so `Named "F"` resolves to the witness var first.
425+
426+## Recommended minimal-diff plan (ordered)
427+
428+1. `PatKind::TypeWitness{name, kind}` + parser LParen-arm extension +
429+ `push_pat_vars`/`render_pat`/pretty_pat arms; reject in case patterns.
430+2. infer: `check_pattern` arm (witness registration via skolems +
431+ `annotation_vars`, guarded); infer-mode Lambda arm support;
432+ `ast_type_to_ty` `Named` arm consults witness scope (flagged).
433+3. infer App arm: kind-`Type` param detection + `expr_as_type` + side
434+ tables (`type_arg_spans`, `witness_bindings`); kind checker for HK
435+ witnesses; extend lambda-last/ctor paths to not shadow it.
436+4. `Constructor` else-branch (§4) for bare type names in value position +
437+ erase-to-unit side table.
438+5. codegen: filter witness params in `compile_lambda_inner`, skip
439+ `type_arg_spans` args in `compile_app`, audit arity sites
440+ (codegen.rs:1792, 2959, 8635), erase marked `Constructor` exprs to unit.
441+6. Remaining visitor arms (§6) driven by compiler exhaustiveness errors;
442+ LSP hover/rename polish.
443+7. Tests: parser (crates/knot/tests/parser.rs), formatter round-trip,
444+ infer (crates/knot-compiler/tests/regress_infer*.rs), codegen end-to-end
445+ (`knot build` + run) covering: witness identity, witness used in body
446+ annotation, HK witness (`F : Type -> Type`), `Int 1` witness, name that
447+ is both ctor and type, leak/escape error, formatter round-trip.
.hermes/doccheck.pynew file+344
@@ -0,0 +1,344 @@
1+#!/usr/bin/env python3
2+"""Extract every fenced code block from the knot docs and try to compile it.
3+
4+Block classes and handling:
5+ - complete program (has `with {` AND a body expr after the closing brace,
6+ or is a bare `(do ...)`): compile as-is.
7+ - declaration fragment (bindings like `names ([...])`, `f \\x -> ...`, sigs):
8+ wrap in `with { ... } (do base.println "ok"; yield {})`.
9+ - reference signature (`name : T`, optionally `base.` prefixed): turn into a
10+ sig-check `checkN : T` + `checkN base.<name>` so a wrong sig fails compile.
11+
12+Usage: doccheck.py <doc.md> [--write-report]
13+Prints per-block PASS/FAIL with the first error line for failures.
14+"""
15+import re, os, subprocess, sys, tempfile
16+
17+WORK = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18+KNOT = os.path.join(WORK, "target/debug/knot")
19+
20+def extract_blocks(path):
21+ lines = open(path).read().splitlines()
22+ blocks = []
23+ i = 0
24+ while i < len(lines):
25+ m = re.match(r"^```([\w-]*)\s*$", lines[i])
26+ if m:
27+ lang = m.group(1)
28+ start = i + 2 # 1-indexed line of first content line
29+ j = i + 1
30+ body = []
31+ while j < len(lines) and not re.match(r"^```\s*$", lines[j]):
32+ body.append(lines[j]); j += 1
33+ blocks.append({"lang": lang, "body": body, "line": start})
34+ i = j
35+ i += 1
36+ return blocks
37+
38+def is_comment_or_blank(s):
39+ s = s.strip()
40+ return not s or s.startswith("--")
41+
42+SIG_RE = re.compile(r"^\s*(?:base\.)?([a-zA-Z][a-zA-Z0-9_.]*)\s*:\s*(.+)$")
43+
44+def looks_like_knot(body):
45+ """Heuristic: is this untagged block knot code (vs prose/output/diagram)?"""
46+ nonempty = [b for b in body if not is_comment_or_blank(b)]
47+ if not nonempty:
48+ return False
49+ for ln in nonempty:
50+ s = ln.strip()
51+ # obvious non-knot: paths, backticks, box-drawing, leading bullets/SQL
52+ if "/" in s or "`" in s or s.startswith(("│", "╭", "╰", "├", "└", "-", "*")):
53+ return False
54+ if re.match(r"^(SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|CREATE|GET|POST|PUT|HTTP)\b", s, re.I):
55+ return False
56+ # must contain at least one knot-ish signal: a sig, binding, or known keyword
57+ text = "\n".join(nonempty)
58+ return bool(re.search(r"[a-zA-Z_]\s*:\s|\bwith\b|\bdo\b|\bcase\b|\\|->|\(do|base\.", text))
59+
60+def classify(body):
61+ text = "\n".join(body)
62+ nonempty = [b for b in body if not is_comment_or_blank(b)]
63+ if not nonempty:
64+ return "empty", None
65+ # complete program: an OUTER record-open `with {` at col 0 followed by a
66+ # top-level body. Both `with {name value ...} body` and the comprehension
67+ # form `with {name do ...} (do ...)` are programs when they start at col 0
68+ # (an *inner* comprehension would be indented inside an enclosing do).
69+ outer_with = re.search(r'^with \{', text, re.M)
70+ if outer_with:
71+ # is there a body after the last closing brace at col 0?
72+ # crude: last non-comment line that's not part of the with-record
73+ tail = [b for b in reversed(body) if not is_comment_or_blank(b)]
74+ if tail and not tail[0].strip().startswith("}"):
75+ # body exists if some top-level line after the with block
76+ return "program", text
77+ # with-block but maybe body is `(do ...)` right after }
78+ if re.search(r"^\}\s*$", text, re.M) and re.search(r"^\(do|^do\b", text, re.M):
79+ return "program", text
80+ return "program", text # try as program; if no body it'll error clearly
81+ stripped0 = nonempty[0].strip()
82+ if stripped0.startswith("(do") or stripped0 == "do" or stripped0.startswith("do "):
83+ return "program", text
84+ # Program-level declaration sequence (route/data/type/serve at top level),
85+ # possibly with a trailing body expression: wrap decls in `with { }`.
86+ if re.search(r'^(route|data|type|serve)\b', text, re.M):
87+ return "program_decls", body
88+ # Declaration(s) followed by a top-level `(do ...)` or `do ...` program body
89+ # (e.g. `*src : T` / `f \\x -> ...` fields then a `do` body): program_decls.
90+ if re.search(r'^\(?do\s*$', text, re.M) or (re.search(r'^\(do\b', text, re.M) and re.search(r'^[*&]?[a-zA-Z_]\w*\s*[:=]', text, re.M)):
91+ # only when there is at least one field-like declaration line above it
92+ if re.search(r'^[*&]?[a-zA-Z_]\w*\s*[:=\\]|^[*&]?[a-zA-Z_]\w*\s+do\b', text, re.M):
93+ return "program_decls", body
94+ # pure signature(s)?
95+ sigs = []
96+ allsig = True
97+ for b in nonempty:
98+ m = SIG_RE.match(b)
99+ if m and ("\\" not in b) and ("(" not in b.split(":",1)[0]):
100+ sigs.append((m.group(1), m.group(2)))
101+ else:
102+ allsig = False
103+ break
104+ if allsig and sigs:
105+ return "sigs", sigs
106+ # otherwise: declaration/expr fragment
107+ return "fragment", text
108+
109+DECL_START = re.compile(r'^\s*(?:[*&]?[a-zA-Z_]\w*|route\b|serve\b)\s*(?::|=|\bwhere\b|\\|\(|\bdo\b)', )
110+# Subset-constraint declaration: `*a.b <= *c.d` or `*a <= *a.b`. These are
111+# declarations, not expressions — keep them in the with-record.
112+CONSTRAINT_DECL = re.compile(r'^\s*\*[a-zA-Z_]\w*(?:\.\w+)?\s*<=')
113+# A `name <value>` with-field line (constant/function/derived with a body). The
114+# value may be a literal, record, lambda, `do` block, source read, or call.
115+FIELD_WITH_BODY = re.compile(r'^\s*[*&]?[a-zA-Z_]\w*\s+\S')
116+# Type-first signature / source declaration: `Type name` (a type, then a tall
117+# 2+-space gap or the type's trailing token, then the annotated name). Covers
118+# `Int 1 -> Int 1 addOne`, `Rel {name Text} *people`, `IO a -> IO b race`,
119+# `Maybe (Int 1) asInt`, record-type-led `{name Text} x`, constraint-led
120+# `(<>ctx) => T f`. The separator is 2+ spaces (tall whitespace).
121+TYPE_FIRST_DECL = re.compile(
122+ r'^\s*(?:[A-Z][\w.]*|Rel\b|\{|\(|forall\b|_).*?\s{2,}[*a-z_]\w*\s*$', )
123+EXPR_START = re.compile(r'^\s*[\(]?\s*(?:base\.|do\b|\(do\b|[a-zA-Z_][\w.]*\s*\()', )
124+
125+def _paren_balanced(s):
126+ depth = 0
127+ in_str = False
128+ for ch in s:
129+ if ch == '"':
130+ in_str = not in_str
131+ if in_str:
132+ continue
133+ if ch in '([{':
134+ depth += 1
135+ elif ch in ')]}':
136+ depth -= 1
137+ return depth <= 0
138+
139+def split_decls_exprs(lines):
140+ """Group a fragment's lines into declarations vs trailing bare expressions.
141+
142+ A declaration starts at a DECL_START line and continues while its parens are
143+ unbalanced OR it opens an indented body block (`name do`, `name \\args ->`)
144+ whose following lines are more indented. Lines that don't belong to any
145+ declaration and aren't declarations themselves are expressions.
146+ """
147+ decls, exprs = [], []
148+ cur = None # accumulating a multi-line declaration
149+ cur_indent = 0 # indent of the declaration's opening line
150+ def opens_block(s):
151+ # `... do`, `... ->`, `... =` at end of line opens an indented body, as
152+ # does `... -> case ... of` / `... case ... of` (arms follow indented).
153+ return bool(re.search(r'(\bdo\b|->|=|\bof)\s*$', s))
154+ for raw in lines:
155+ if is_comment_or_blank(raw):
156+ if cur is not None:
157+ cur.append(raw)
158+ continue
159+ s = raw.strip()
160+ indent = len(raw) - len(raw.lstrip())
161+ # a with-field: `name : T`, `name = v`, `name do ...`, `name \a -> ...`,
162+ # or `name <value>` (constant with a body). Dotted names (base.x) are not fields.
163+ starts_decl = (bool(DECL_START.match(raw)) or bool(FIELD_WITH_BODY.match(raw))
164+ or bool(TYPE_FIRST_DECL.match(raw))) \
165+ and not s.startswith("(") and "." not in s.split(None, 1)[0]
166+ # a subset-constraint `*a.b <= *c.d` IS a declaration despite the dotted name
167+ starts_decl = starts_decl or bool(CONSTRAINT_DECL.match(raw))
168+ if cur is not None:
169+ joined = "\n".join(cur)
170+ # continue if parens unbalanced OR this line is a more-indented body line
171+ if not _paren_balanced(joined) or indent > cur_indent:
172+ cur.append(raw)
173+ if _paren_balanced("\n".join(cur)) and indent <= cur_indent:
174+ decls.append("\n".join(cur)); cur = None
175+ continue
176+ # a same-or-less-indented balanced line closes the declaration
177+ decls.append(joined); cur = None
178+ if starts_decl:
179+ if _paren_balanced(raw) and not opens_block(s):
180+ decls.append(raw)
181+ else:
182+ cur = [raw]; cur_indent = indent
183+ else:
184+ exprs.append(raw)
185+ if cur is not None:
186+ decls.append("\n".join(cur))
187+ return decls, exprs
188+
189+def wrap_fragment(text):
190+ lines = text.splitlines()
191+ decls, exprs = split_decls_exprs(lines)
192+ out = ["with {"]
193+ out.extend(decls)
194+ out.append("}")
195+ if exprs:
196+ # Each bare expression line is an independent example (e.g. a reference
197+ # table of `single [...]` / `single []` cases). Emit one println per
198+ # expression so multi-expression blocks compile.
199+ body_lines = ["(do"]
200+ for e in exprs:
201+ e2 = re.sub(r'\s+--.*$', '', e).strip()
202+ if e2:
203+ body_lines.append(f" base.println (base.show ({e2}))")
204+ body_lines.append(" yield {})\n")
205+ out.extend(body_lines)
206+ else:
207+ out.append("(do\n base.println \"ok\"\n yield {})\n")
208+ return "\n".join(out)
209+
210+def wrap_program_decls(lines):
211+ """Wrap a program-level declaration sequence in `with { }`. The trailing
212+ top-level expression (a line at col 0 starting with `(` or `do`) becomes the
213+ body; everything else goes inside the record."""
214+ body_idx = None
215+ for i, raw in enumerate(lines):
216+ if re.match(r'^(\(|do\b)', raw) and not raw.startswith(' '):
217+ body_idx = i
218+ break
219+ decls = lines if body_idx is None else lines[:body_idx]
220+ body = [] if body_idx is None else lines[body_idx:]
221+ out = ["with {"]
222+ out.extend(decls)
223+ out.append("}")
224+ if body:
225+ out.extend(body)
226+ else:
227+ out.append("(do\n base.println \"ok\"\n yield {})\n")
228+ return "\n".join(out)
229+
230+# Builtins that are language KEYWORDS and cannot be referenced as `base.x`
231+# values (`base.not` fails to parse: `not` is a keyword). For these we verify
232+# only that the documented TYPE parses, by ascribing it to a type-correct
233+# lambda. ADT constructors likewise are not `base.x` values.
234+KEYWORD_LAMBDAS = {
235+ "not": "\\b -> not b",
236+ "atomic": None, # keyword block form; sig not value-bindable
237+ "where": None, "migrate": None, "serve": None, "route": None,
238+ "fetch": None, "retry": None,
239+}
240+CTORS = {"Just", "Nothing", "Ok", "Err", "LT", "EQ", "GT", "True", "False"}
241+
242+def wrap_sigs(sigs):
243+ out = ["with {"]
244+ for i, (name, ty) in enumerate(sigs):
245+ bare = name.split(".")[-1]
246+ tyc = ty.strip()
247+ lam = KEYWORD_LAMBDAS.get(bare)
248+ if bare in KEYWORD_LAMBDAS or bare in CTORS:
249+ if lam is None:
250+ # keyword block form: no value check possible; emit a placeholder
251+ # so the `with` is a valid program (type itself already trusted).
252+ out.append(f"kw{i} 0")
253+ continue
254+ out.append(f"check{i} : {tyc}")
255+ out.append(f"check{i} ({lam})")
256+ continue
257+ out.append(f"check{i} : {tyc}")
258+ # SIG_RE strips any leading `base.`; `name` is the path WITHOUT it
259+ # (e.g. `morph.textToBytes.into` or `sum`). Reference the full path.
260+ out.append(f"check{i} base.{name}")
261+ out.append("}")
262+ out.append("(do\n base.println \"ok\"\n yield {})\n")
263+ return "\n".join(out)
264+
265+def compile_src(src, tag):
266+ fd, srcf = tempfile.mkstemp(prefix=f"hermes-doccheck-{tag}-", suffix=".knot")
267+ os.close(fd)
268+ binf = srcf[:-5] # strip .knot
269+ with open(srcf, "w") as f:
270+ f.write(src)
271+ try:
272+ r = subprocess.run([KNOT, "build", srcf, "-o", binf],
273+ cwd=WORK, capture_output=True, text=True, timeout=60)
274+ ok = os.path.exists(binf)
275+ err = ""
276+ if not ok:
277+ first = next((l for l in (r.stderr + r.stdout).splitlines() if l.strip().startswith("Error")), "")
278+ err = first or (r.stderr + r.stdout).strip().splitlines()[:1]
279+ err = err if isinstance(err, str) else (err[0] if err else "unknown error")
280+ return ok, err
281+ except subprocess.TimeoutExpired:
282+ return False, "TIMEOUT"
283+ finally:
284+ for p in (srcf, binf):
285+ if os.path.exists(p):
286+ os.remove(p)
287+
288+def main():
289+ doc = sys.argv[1]
290+ blocks = extract_blocks(doc)
291+ # Build work items first, then compile in parallel (each block spawns the
292+ # compiler; serial is ~5s/block which times out on the big docs).
293+ work_items = []
294+ skipped = []
295+ for idx, b in enumerate(blocks):
296+ # `knot-skip` blocks are intentionally not compiled (document a feature
297+ # whose impl doesn't yet match the doc — doc-vs-impl gap). Report them
298+ # so the skip is visible, not silent.
299+ if b["lang"] == "knot-skip":
300+ skipped.append(b)
301+ continue
302+ if b["lang"] not in ("knot", ""):
303+ continue
304+ if b["lang"] == "" and not looks_like_knot(b["body"]):
305+ continue
306+ kind, payload = classify(b["body"])
307+ if kind == "empty":
308+ continue
309+ if kind == "program":
310+ src = payload
311+ elif kind == "program_decls":
312+ src = wrap_program_decls(payload)
313+ elif kind == "fragment":
314+ src = wrap_fragment(payload)
315+ elif kind == "sigs":
316+ src = wrap_sigs(payload)
317+ else:
318+ continue
319+ work_items.append({"idx": idx, "line": b["line"], "kind": kind, "src": src,
320+ "preview": next((l for l in b["body"] if not is_comment_or_blank(l)), "")[:60]})
321+
322+ from concurrent.futures import ThreadPoolExecutor
323+ def run(item):
324+ ok, err = compile_src(item["src"], f"{os.path.basename(doc)}-{item['idx']}")
325+ item["ok"] = ok; item["err"] = err
326+ return item
327+ results = []
328+ with ThreadPoolExecutor(max_workers=8) as ex:
329+ for item in ex.map(run, work_items):
330+ results.append(item)
331+ results.sort(key=lambda r: r["line"])
332+ npass = sum(1 for r in results if r["ok"])
333+ skip_note = f" ({len(skipped)} skipped: knot-skip)" if skipped else ""
334+ print(f"\n==== {doc}: {npass}/{len(results)} blocks compile{skip_note} ====")
335+ for s in skipped:
336+ preview = next((l for l in s["body"] if not is_comment_or_blank(l)), "")[:60]
337+ print(f"SKIP L{s['line']} {preview}")
338+ for r in results:
339+ if not r["ok"]:
340+ print(f"FAIL L{r['line']} [{r['kind']}] {r['preview']}")
341+ print(f" {r['err']}")
342+
343+if __name__ == "__main__":
344+ main()
.hermes/plans/2026-07-22-sources-in-records.mdnew file+212
@@ -0,0 +1,212 @@
1+# Plan: Sources in records with static-resolution enforcement
2+
3+## Goal
4+
5+Allow source declarations as fields of a record literal, so a record can group a
6+persisted source with its `data`/`type` and operations (a self-contained
7+"module"). Enforce the **static-resolution rule**: a source may only be used
8+where the compiler can resolve it, at compile time, to a specific declared source
9+with a known name + schema. This preserves the entire safety story — schema
10+lockfile, migrations, effect tracking, SQL pushdown.
11+
12+## Core design decision (locked with user): `*` is part of the NAME, not a prefix operator
13+
14+A source is a binding/field **whose name begins with `*`**. There is no `*`
15+prefix operator and no separate `SourceRef` atom — the sigil is folded into the
16+identifier, and "source-ness" is a property of the name.
17+
18+- **Declaration:** a field or top-level binding named `*todos` is a persisted
19+ source. `db = { *todos : [Todo] }` declares a source field literally named
20+ `*todos`. Bare top-level `*todos : [Todo]` keeps working (a top-level source
21+ binding named `*todos`).
22+- **Read:** `db.*todos` is ordinary field access on a field named `*todos`;
23+ yields the relation value `[Todo]`.
24+- **Write:** `=` is permitted **only** on a path whose final segment is a
25+ `*`-named field: `db.*todos = …`. Settable-ness comes from the field *being a
26+ source* (name starts with `*`), resolved from the declaration — not from any
27+ use-site marker.
28+- The lexer must treat `*` followed by a lowercase ident as a single
29+ `*`-prefixed identifier token in field/binding position (see Parser below).
30+
31+This removes the read/write ambiguity by construction: `db.todos` (a plain field)
32+is never a source; `db.*todos` (a `*`-named field) is always a source. The
33+distinction lives in the record's field names, not at the use site.
34+
35+## The static-resolution rule (agreed)
36+
37+A `*`-named source may only be used where the compiler can trace it to a
38+declaration:
39+
40+- ✅ `*todos` — bare top-level source binding (today).
41+- ✅ `db.*todos` where `db` is a **statically-known record literal** containing a
42+ `*todos : [Todo]` field — resolves to that declared source.
43+- ❌ A source in an **opaque** position — a lambda param, a `let` bound to a
44+ non-literal, a function return, a list element — anywhere the compiler can't
45+ trace the record back to a literal. The write/`*`-field reference there is a
46+ compile error (not read-only).
47+
48+## Design decisions (locked)
49+
50+- **Path-qualified source identity.** A source's identity is its access path:
51+ `todos` (top-level) or `db.*todos` (record field). The qualified path is the
52+ key for `source_schemas` / `source_types`, the effect name (`w db.*todos`),
53+ the lockfile key, the migration target, and the SQLite table name.
54+- **Table name = `_knot_` + path verbatim (NO mangling).** The physical SQLite
55+ table is `format!("_knot_{}", path)` used via `quote_ident`, and quoted
56+ identifiers accept `.`/`*` fine. `parse_table_aliases` reads to the closing
57+ quote (dots/`*` inside are harmless) and `ensure_index` is already injective
58+ via length-prefixing. So `_knot_db.*todos` is valid; no character mangling is
59+ needed. Just centralize the mapping in one `table_name(path)` helper.
60+- **Static resolution via literal tracing.** Resolution succeeds only when the
61+ record expression is a literal (or traces through `let`/`with` to one), like
62+ implicit-dictionary records and embedded `data`/`type` are resolved today.
63+- **No dynamic handles.** No runtime name/schema resolution, no fallback path.
64+- **Reads of plain relation fields stay free.** `db.todos` (non-`*` field) is a
65+ normal value and flows anywhere; only `*`-named fields are restricted.
66+- **Duplicate source names are a hard compile error — GLOBALLY, across all
67+ scopes.** A source's identity is its qualified path, and that namespace is
68+ **flat and program-wide**, not lexical: every source maps to one physical
69+ table (`_knot_<path>`) and one lockfile entry, so two sources that resolve to
70+ the same path cannot coexist **even in non-overlapping scopes**. `*todos` in
71+ one record and `*todos` in a different record, one in a function body and one
72+ at top level, two in separate `with` blocks that never interleave — all of
73+ these collide, because scope does not isolate the underlying table. The check
74+ is a whole-program uniqueness pass over every source declaration (top-level +
75+ every record-literal `*`-field), erroring on any repeated qualified path and
76+ naming all declaration sites. Never silently overwrite a
77+ `source_schemas`/`source_types` entry (today a duplicate `HashMap::insert`
78+ would clobber). This matters more once record fields can introduce sources,
79+ since collisions become easier to write accidentally.
80+
81+## Work items
82+
83+### 1. Lexer (`crates/knot/src/lexer.rs`)
84+- **Language-wide rule (user-locked): binary operators REQUIRE whitespace on both
85+ sides.** So `a * b`, `a + b`, `x - y`, `Float M * S` are infix; an operator
86+ char immediately followed by a letter is never infix. This makes `*`
87+ unambiguous with NO lookahead: `*` immediately followed by an ASCII lowercase
88+ letter ⇒ source identifier; otherwise (whitespace/EOF/non-letter after) ⇒
89+ operator.
90+- Recognize `*lower` as a single `TokenKind::StarIdent(String)` where the String
91+ INCLUDES the leading `*` (e.g. `"*todos"`). At the `b'*'` case, peek the next
92+ byte: if ASCII-lowercase, lex a StarIdent (`*` + ident-continuation chars);
93+ else emit `TokenKind::Star`.
94+- Existing `Star` (binary op) uses all have spaces, so they're unaffected. The
95+ `*name` in top-level source decls / `replace *name` / source refs currently
96+ lex as `Star`+`Lower`; after this change they're a single `StarIdent`, and the
97+ parser sites that matched `Star`+`Lower` must match `StarIdent` instead.
98+- Migration: existing no-space binops must be reformatted. Grep found only
99+ `f-8`-style subtractions in examples/bytes.knot and examples/crypto.knot (the
100+ `replace *name` hits are source refs, unaffected). Run the formatter over
101+ affected files; the formatter should emit spaces around binops going forward.
102+
103+### 2. AST (`crates/knot/src/ast.rs`)
104+- Field/binding names can now start with `*` (the `Name` is `"*todos"`). No
105+ change to `RecordField.name` (it's already a `Name` String).
106+- `DeclKind::Source { name, .. }`: `name` is `"*todos"` (or keep bare `todos`
107+ and treat "starts with `*`" as the source marker — **decide one canonical
108+ form**; prefer storing the name WITH the `*` so name-based dispatch is just a
109+ `starts_with('*')` check everywhere).
110+- `ExprKind::SourceRef(Name)` may become unnecessary for the record case (it's
111+ just `FieldAccess`/`Var` on a `*`-name), but keep it for top-level source
112+ reads if that simplifies effects. **Decide during impl:** likely unify so a
113+ source read/write is recognized by "name starts with `*`" rather than a
114+ dedicated node — this is the simplification the user's change enables.
115+
116+### 3. Parser (`crates/knot/src/parser.rs`)
117+- **Record-literal field arm** (≈2914 loop): accept `*name : Type` as a field
118+ whose name is `*name`. Reuse the existing `type`/`data`/sig-arm structure.
119+- **Top-level source decl** (`parse_source_or_view` ≈1180): now keyed by the
120+ `*`-name directly.
121+- **Set/ReplaceSet** (≈2011, 2030): target is a field-access path whose final
122+ segment starts with `*`; produce `Set{ target, value }` where `target` is the
123+ path expr. Validation that the final segment is a source moves to infer (it
124+ can resolve the path).
125+- **Field access**: `db.*todos` — after `.`, accept a `StarIdent`.
126+
127+### 4. Types env (`crates/knot-compiler/src/types.rs`)
128+- `source_schemas: HashMap<String,String>` keyed by qualified path
129+ (`todos`, `db.*todos`). Add a walk over top-level record-let literals
130+ registering each `*`-named field's source under `<record>.<field>`.
131+- **Global duplicate-name check (whole-program, scope-agnostic):** collect every
132+ source declaration — top-level `DeclKind::Source` plus every record-literal
133+ `*`-field reachable in the module — into one map keyed by qualified path. Any
134+ repeated path is a hard error naming the path and ALL declaration sites,
135+ regardless of whether the declarations' scopes overlap (they share one
136+ physical table / lockfile entry, so scope is irrelevant). Never silently
137+ overwrite. Run this once over the whole module, not per-scope.
138+- `schema_for_source` unchanged (operates on the field's type); only the key
139+ changes. Top-level sources keyed by bare `todos` (length-1).
140+
141+### 5. Infer (`crates/knot-compiler/src/infer.rs`)
142+- `source_types` keyed by qualified path (populate at ≈8787/8796 + the
143+ record-literal walk).
144+- **Source read** (`db.*todos`): type `IO {Reads "db.*todos"} [Todo]`. Resolve
145+ the root of the path to a static record literal (through `let_bindings` /
146+ `with` frames / top-level literals), confirm the field is a source.
147+- **Write** (`Set`/`ReplaceSet` ≈6385, 6440): resolve the target path to a
148+ source; effect `Writes path`; refinement + set/replace distinction by path.
149+- **Opacity enforcement:** if a `*`-field reference's root is not statically
150+ resolvable (lambda param, non-literal let, etc.) → compile error: "source
151+ `<path>` is not statically known; sources may only be used where their
152+ declaration is visible." This is the single choke point for the rule.
153+
154+### 6. Codegen (`crates/knot-compiler/src/codegen.rs`)
155+- `source_schemas`, `views`, `scalar_sources`, `source_refinements` keyed by
156+ qualified path.
157+- **Central `table_name(path) -> String`** = `format!("_knot_{}", path)`
158+ verbatim (no mangling — quoted idents accept `.`/`*`); replace the scattered
159+ `format!("_knot_{}", name)` sites. Runtime unchanged (still gets a string).
160+- Source read (≈3817), `Set` (≈4463), `ReplaceSet` (≈4717),
161+ `collect_direct_write_targets` (≈5330): resolve path → qualified name. The
162+ `if let SourceRef(name)` guards become path resolution + `starts_with('*')`.
163+
164+### 7. Lockfile (`crates/knot-compiler/src/lockfile.rs`)
165+- Walk top-level record literals for `*`-named source fields, registering under
166+ qualified paths so `check` sees them (≈619/636/645 loops). Lockfile key =
167+ qualified path. Migrations match on it. Single-name sources unchanged.
168+
169+### 8. Migrations / subset constraints
170+- `migrate` target and `<=` subset paths resolve record-literal source paths the
171+ same way (they already use `RelationPath`).
172+
173+### 9. Formatter / LSP
174+- Round-trip `*name : Type` record fields and `db.*todos` access.
175+- LSP `analysis.rs`: update source patterns to the `*`-name / path form.
176+
177+## Testing
178+
179+New `crates/knot-compiler/tests/regress_source_in_record.rs` (build + run via
180+the real binary, following `regress_implicit_dict.rs`):
181+1. **Read via path:** `db = { *todos : [Todo] }`; `ts <- db.*todos` reads rows.
182+2. **Write via path:** `db.*todos = union db.*todos […]` persists; re-read shows
183+ the row. Table created under the mangled name.
184+3. **replace via path:** `replace db.*todos = []` empties it.
185+4. **with-peel (decide):** does `with db (… *todos …)` bring the source field
186+ into scope as bare `*todos`? Resolution must still trace to the literal.
187+5. **Opacity error:** `process = \db -> db.*todos = …` → compile error naming
188+ the unresolved source. Opaque **read** `ts <- db.*todos` through a param —
189+ decide allow-or-error (leaning: error too, since it's still a source).
190+6. **Lockfile:** build twice → drift on `db.*todos` detected; `migrate` to
191+ `db.*todos` validates.
192+7. **Effects:** a fn writing `db.*todos` infers the write effect; `main` accepts.
193+8. **Duplicate-name errors (global, scope-agnostic):** (a) two `*todos` fields
194+ in one record literal; (b) `*todos` in two DIFFERENT records whose paths
195+ don't overlap lexically; (c) `*todos` in a function-body record vs a
196+ top-level `*todos`; (d) two top-level sources with the same name — every one
197+ is a hard compile error naming the duplicate path and all sites, even when
198+ the scopes never overlap.
199+
200+## Open questions to settle during implementation
201+- Lexer disambiguation of `*ident` vs `*` infix (spacing rule).
202+- Store source names WITH or WITHOUT the `*` in the various maps (canonical
203+ form). Prefer WITH for a uniform `starts_with('*')` dispatch.
204+- `with db` peel of a source field: support or require the qualified path.
205+- Opaque reads of `*`-fields: allowed or also static-only.
206+
207+## Verification
208+- `cargo build --workspace` clean.
209+- New regression suite green.
210+- Full `cargo test --workspace` green (watch lockfile, effects, SQL pushdown,
211+ codegen source guards).
212+- Ad-hoc end-to-end script building + running the examples against the binary.
.hermes/sql-pushdown-opportunities.mdnew file+102
@@ -0,0 +1,102 @@
1+# SQL Pushdown — Current Coverage & Opportunities
2+
3+## STATUS (updated during implementation)
4+- Delegation 1 (in flight): Feature A (arith-map→aggregate) + Feature B (map-dedup→SELECT DISTINCT). Confirmed gaps via `--debug` SQL.
5+- Remaining (scoped below): #1 GROUP BY, #3 elem-subquery→IN(SELECT), #5 EXISTS.
6+
7+## Empirically confirmed today (via `./prog --debug`)
8+- A: `sum (map (\i -> i.price * i.qty) items)` → ships all rows, computes in memory. Should be `SELECT SUM("price"*"qty") FROM _knot_items`. `base.sum:[a]->a` is DIRECT; the projection is a preceding `base.map`.
9+- B: `map (\p -> p.dept) people` → `SELECT name,dept FROM _knot_people` + temp `SELECT DISTINCT`. Should be `SELECT DISTINCT "dept" FROM _knot_people`. Relations are sets → map already dedups; this pushes dedup+projection into SQL.
10+
11+## Scoping for the hard three
12+
13+### #3 elem-subquery → IN (SELECT ...)
14+codegen.rs:12805. `elem` has two paths: (a) literal list→`IN (?,...)`, (b) dynamic haystack→`IN (SELECT value FROM json_each(?))` (whole relation JSON-encoded as a param, gated by `elem_pushdown_ok.dynamic`, Text/Bool/Uuid only).
15+GAP: when haystack is a do-block comprehension over a source (`do d <- *depts; where ...; yield d.dept`), it materializes + JSON-encodes instead of `needle IN (SELECT "dept" FROM _knot_depts WHERE ...)`.
16+IMPL: detect Do-block haystack → reuse `analyze_sql_plan` to get the inner query → emit `needle_sql IN (<inner sql>)`. Keep the float/type gates. The needle and inner are on different sources — needle is the outer bind_var's column.
17+NOTE: in a `where` clause the haystack must be a pure relation value; comprehension-in-where needs the `with`-bound form. Verify the accepted surface syntax first (my direct `where elem p.dept (do ...)` failed to typecheck — needs binding).
18+
19+### #5 EXISTS for count-guard
20+Shape: `where count (filter (\d -> ...) *depts) > 0` → `WHERE EXISTS (SELECT 1 FROM _knot_depts WHERE ...)`. Lower priority / less common. Only implement if cheap after #3 (similar subquery machinery).
21+
22+### #1 GROUP BY + per-group aggregate (biggest, riskiest)
23+TODAY: groupBy pushes every row into a temp relation, closes loops (arena), then `knot_relation_group_by` reads the temp + ORDER BYs keys to group in memory. Per-group aggregates run row-by-row in Knot.
24+TARGET: `SELECT "key", COUNT(*)/SUM(..)/AVG(..)/MIN(..)/MAX(..) FROM _knot_src GROUP BY "key"` for the shape `t <- *src; groupBy {k: t.key}; ...aggregate over group...`.
25+CONSTRAINTS discovered: keys must be plain field accesses on the primary bind (codegen.rs:10803-10835 enforces this — good, maps to GROUP BY cols). The groupBy codegen is deeply tied to temp-relation + arena-promotion + loop-closing machinery (~10690-10850). A SQL path must DETECT the simple single-source shape EARLY and bypass that machinery entirely, emitting a GroupBy terminal.
26+EFFORT: large. New QueryTerminal::GroupBy { key_cols, agg } + runtime read-back of grouped rows (needs a schema for the {key, group-relation} shape Knot produces). RISK: high — must not break the 31 examples (several use groupBy) or the nested-relation result shape.
27+RECOMMEND: do A+B+#3 first, keep suite green, THEN attempt #1 in isolation with heavy example coverage.
28+
29+Source of truth: `crates/knot-compiler/src/sql_lint.rs` (mirrors codegen),
30+`crates/knot-compiler/src/codegen.rs` (`aggregate_sql_func_runtime`,
31+`pipe_ops_order_pushable`, set-op terminals), `crates/knot-runtime/src/lib.rs`.
32+
33+## Already pushed down (verified in codegen)
34+- `where` comprehensions → `WHERE` (single- and multi-relation joins →
35+ `FROM a, b WHERE join-cond AND filter`; auto-indexes WHERE/ORDER BY cols)
36+- comparisons `==` `!=` `<` `>` `<=` `>=`, `&&` `||` `not`
37+- arithmetic `+` `-` `*` `++`; `/` `%` only with provably-nonzero literal divisor
38+- `contains` → `INSTR(...) > 0`; `elem` → `IN (...)` / `IN (SELECT json_each(?))`
39+- `count` → `COUNT(*)`; `countWhere` → `COUNT(*) WHERE`
40+- `sum`/`avg`/`minOn`/`maxOn` → `SUM`/`AVG`/`MIN`/`MAX` (int/text only)
41+- `sortBy` → `ORDER BY` (int/text projections only)
42+- `take N` → `LIMIT`; `drop M` → `OFFSET`; `drop M |> take N` → `LIMIT N OFFSET M`
43+- set ops `union`/`inter`/`diff` → `UNION`/`INTERSECT`/`EXCEPT`
44+- pipe chains in the fixed stage order
45+ filter* → sortBy? → map? → drop? → take? → aggregate?
46+
47+## Deliberate in-memory fallbacks (correctness, NOT gaps)
48+These stay in memory because SQLite semantics diverge from Knot — do NOT
49+"fix" these without changing semantics:
50+- **Float comparisons/ORDER BY/MIN/MAX**: SQLite -0.0/NaN-as-NULL vs Knot
51+ `total_cmp`. Fundamental.
52+- **`tag` columns (all-nullary ADTs) ordered comparisons**: SQLite compares
53+ constructor names alphabetically; Knot `Ord` uses declaration order.
54+- **json columns** (payload ADTs, nested records): runtime binds differently.
55+- **`toUpper`/`toLower`/`trim`/`length`**: SQLite UPPER/LOWER/TRIM are
56+ ASCII-only, LENGTH counts pre-NUL; Knot is Unicode-aware.
57+- `/` `%` with non-literal/zero-able divisor: SQLite NULLs, Knot panics.
58+
59+## Real pushdown OPPORTUNITIES (ranked)
60+
61+### 1. GROUP BY + per-group aggregate ← highest value
62+`groupBy {key: t.field}` currently calls `knot_relation_group_by` which
63+materialises the whole relation, inserts keys into a temp table, and ORDER
64+BYs to group in memory. A following per-group aggregate
65+(`count`/`sum`/`avg`/`min`/`max` over each group) is done row-by-row in Knot.
66+SQL could do `SELECT key, COUNT(*) ... GROUP BY key` in one pass.
67+ - codegen.rs:1316 declares the runtime fn; no GROUP BY SQL is emitted.
68+ - Effort: large (groupBy has complex key/rebinding semantics — see
69+ `validate_group_by_references`, temp-relation machinery ~10059-10748).
70+ - Win: the canonical DB workload; turns O(N) row shipping into O(groups).
71+
72+### 2. `distinct` / dedup → `SELECT DISTINCT`
73+No `DISTINCT` pushdown exists; dedup goes through `sql_dedup`
74+(runtime/lib.rs:6004) which round-trips rows. `SELECT DISTINCT cols`
75+for the common "yield a projection of unique rows" case.
76+ - Effort: medium. Win: moderate.
77+
78+### 3. `elem x (subquery-relation)` — uncorrelated IN subquery
79+`elem` pushes down for literal lists and `json_each` haystacks, but an
80+IN-haystack that is itself a pushed-down relation comprehension could become
81+`... WHERE x IN (SELECT ...)`. Currently a dynamic haystack is evaluated
82+outside SQL row scope and shipped.
83+ - Effort: medium. Win: avoids shipping the inner relation.
84+
85+### 4. Arithmetic projections in `map` that only feed a later aggregate
86+`map (\r -> r.a + r.b) |> sum` — the `+` is pushable as an atom but the
87+map-projection stage blocks the aggregate pushdown when it isn't the terminal.
88+Fold the arithmetic into the aggregate's SELECT expression.
89+ - Effort: small-medium (atoms already compile). Win: moderate.
90+
91+### 5. `length`/`count` of a *filtered* subrelation feeding a comparison
92+`where count (filter ...) > 0` style guards could become `EXISTS`.
93+ - Effort: medium. Win: moderate, less common shape.
94+
95+## Notes / constraints
96+- The lint (`sql_lint.rs`) MUST stay in lockstep with codegen — it
97+ re-implements every predicate so the "evaluated at runtime" info is accurate.
98+ Any new pushdown needs a mirrored lint predicate.
99+- Every pushdown entry point auto-indexes WHERE/ORDER BY columns
100+ (`ensure_indexes_for_sql`), so new pushdowns inherit index coverage.
101+- Float/tag/unicode fallbacks are documented in AGENTS.md/DESIGN.md as
102+ intentional — treat them as spec, not TODOs.
Boolnew file

No changes to the content.

CLAUDE.mddeleted−110
@@ -1,110 +0,0 @@
1−# Knot
2−
3−Knot is a functional relational programming language. Relations are the primary data structure, computation is pure and functional, and state is automatically persisted to SQLite.
4−
5−See `DESIGN.md` for the full language specification.
6−
7−## Project Structure
8−
9−Cargo workspace with three crates:
10−
11−```
12−crates/
13− knot/ Frontend library (lexer, parser, AST, diagnostics)
14− knot-runtime/ Rust staticlib linked into compiled programs (value representation, SQLite persistence)
15− knot-compiler/ Cranelift-based compiler producing native executables (CLI binary: knotc)
16−examples/ Example .knot programs
17−```
18−
19−## Build & Test
20−
21−```sh
22−cargo build # Build all crates
23−cargo test # Run all tests (230 parser/lexer tests)
24−cargo test -p knot # Run only frontend tests
25−```
26−
27−## Compiling Knot Programs
28−
29−```sh
30−cargo run -p knot-compiler -- build examples/hello.knot
31−./examples/hello
32−```
33−
34−The compiler (`knotc`) looks for `libknot_runtime.a` next to its own executable. In a cargo workspace, both end up in `target/<profile>/`, so this works automatically. Override with `KNOT_RUNTIME_LIB=/path/to/libknot_runtime.a`.
35−
36−Compiled binaries create a `knot.db` SQLite database in the current directory for persistence.
37−
38−## Architecture
39−
40−### Compilation Pipeline
41−
42−```
43−source.knot → Lexer → Tokens → Parser → AST → Desugar → Type Inference → Effect Inference → Type Resolution → Cranelift IR → .o → cc link → executable
44−```
45−
46−### Frontend (`crates/knot/`)
47−
48−- **Lexer** (`lexer.rs`): Tokenizer with layout-sensitive newline handling
49−- **Parser** (`parser.rs`): Recursive-descent with Pratt expression parsing for operator precedence
50−- **AST** (`ast.rs`): Spanned AST nodes for all language constructs
51−- **Diagnostics** (`diagnostic.rs`): Error reporting with source locations
52−
53−Public API:
54−```rust
55−let lexer = knot::lexer::Lexer::new(&source);
56−let (tokens, lex_diags) = lexer.tokenize();
57−let parser = knot::parser::Parser::new(source, tokens);
58−let (module, parse_diags) = parser.parse_module();
59−```
60−
61−### Runtime (`crates/knot-runtime/`)
62−
63−Compiled as a `staticlib` (with `rlib` for workspace dependency resolution). All functions use `extern "C"` ABI, called by Cranelift-generated code via symbol references.
64−
65−- All Knot values are `*mut Value` (heap-allocated tagged enum); integers are unbounded (`num_bigint::BigInt`); IO actions are `Value::IO(fn_ptr, env)` thunks executed via `knot_io_run`
66−- `rusqlite` with `bundled` feature statically links SQLite into every compiled binary
67−- Source relations map to SQLite tables with schema derived from Knot types
68−- Schema descriptor format for records: `"name:text,age:int"` (passed as string constants from generated code)
69−- Schema descriptor format for direct ADT relations: `"#Circle:radius=float|Rect:width=float;height=float"` — `#` prefix signals ADT schema, `|` separates constructors, `:` separates name from fields, `;` separates fields, `=` separates field name from type; runtime creates wide table with `_tag TEXT` + all constructor fields as nullable columns
70−- Column type `tag` for enum-like ADT fields (all nullary constructors): stored as TEXT in SQLite, reconstructed as `Constructor(tag, Unit)` on read
71−- Automatic indexing: the runtime observes query patterns and creates indexes lazily — ADT tables get a `_tag` index at init; columns in `DELETE WHERE` and `UPDATE WHERE` clauses are auto-indexed on first use; `KnotDb` tracks created indexes in a per-session `HashSet` to avoid redundant DDL; uses `CREATE INDEX IF NOT EXISTS` for cross-session idempotency
72−- Nested relation fields (`[T]` inside records): stored in **child tables** (`_knot_{source}__{field}`) with `_parent_id` FK linking to parent's `_id`; parent tables with nested children get `_id INTEGER PRIMARY KEY AUTOINCREMENT`; schema descriptor format `field:[child_schema]` (bracket-aware parsing via `split_respecting_brackets`); deeply nested relations recurse arbitrarily
73−
74−### Compiler (`crates/knot-compiler/`)
75−
76−- **Desugaring** (`desugar.rs`): Transforms "pure comprehension" do blocks into nested calls to `__bind`, `__yield`, and `__empty`. A pure comprehension is a do block with only Bind/Where/Let/Yield statements (no bare side-effecting expressions or `groupBy`). Do blocks that are direct values of `set`/`full set` expressions are NOT desugared (preserves SQL optimization patterns). Do blocks containing `groupBy` are NOT desugared (require loop-based codegen for the collect/group/re-iterate pattern). Do blocks containing constructor pattern binds (`Ctor pat <- expr`) are NOT desugared (the desugarer cannot syntactically distinguish value pattern matches from monadic binds; direct codegen handles both). Mixed/sequential do blocks (e.g. `main = do { println ...; yield {} }`) remain as `Do(stmts)` nodes for imperative codegen. IO do blocks (containing IO-returning builtins like `println`, `readFile`, `now`) are compiled via a dedicated IO codegen path that runs each IO action with `knot_io_run` and wraps the result in `knot_io_pure`. The desugared `__bind`/`__yield`/`__empty` dispatch through Monad/Applicative/Alternative trait impls — the monad type is resolved at compile time via HKT unification (`monad_info` maps spans to `MonadKind::Relation`, `MonadKind::Adt(name)`, or `MonadKind::IO`), and codegen calls the appropriate `Monad_{type}_bind`, `Applicative_{type}_yield`, or `Alternative_{type}_empty` mangled function. This allows `do` blocks to work with any type implementing the Monad/Applicative/Alternative traits, not just `[]`.
77−- **Type inference** (`infer.rs`): Hindley-Milner type inference with row-polymorphic records and row-polymorphic variants, let-generalization, unification with occurs check, ADT constructor typing, trait method registration, associated type erasure, higher-kinded type polymorphism (`TyCon`/`App` type nodes with normalization-based unification), IO monad type (`Ty::IO(BTreeSet<IoEffect>, Box<Ty>)` with effect tracking for `Console`, `Fs`, `Network`, `Clock`, `Random`), and trait bound checking (explicit `TraitName a =>` constraints on function signatures are validated at call sites against known `impl` declarations; trait method calls automatically carry their trait's constraint). Constructor patterns in `case` and do-bind create open variant types `<Ctor {} | r>` (the scrutinee only needs to *contain* the matched constructor), enabling row-polymorphic variant matching; when all constructors of a known data type are covered without a wildcard, the row variable is closed and exhaustiveness is checked normally; open variants require a wildcard `_` case
78−- **Effect inference** (`effects.rs`): Infers per-declaration effects (`{reads *rel}`, `{writes *rel}`, `{console}`, `{clock}`, etc.), checks IO-in-atomic constraints, validates explicit effect annotations against inferred effects
79−- **Type resolution** (`types.rs`): Resolves aliases (including multi-variant ADTs to `ResolvedType::Adt`), computes SQLite schemas from Knot type annotations, collects subset constraints
80−- **Codegen** (`codegen.rs`): Cranelift IR generation — the `build_function` pattern moves `ctx`/`builder_ctx` out of `self` to avoid borrow conflicts while allowing `self.method()` calls during IR building
81−- **Linker** (`linker.rs`): Invokes `cc` with platform-appropriate flags
82−- **CLI** (`main.rs`): `knotc build <file.knot>` entry point
83−
84−Key codegen patterns:
85−- All values are pointer-typed (`ptr_type`) in Cranelift IR — uniform representation
86−- `do` blocks compile to nested loops with SSA block params for iteration counters
87−- `where` clauses become conditional branches; skip blocks jump to the loop's continue block
88−- Functions are constants bound to lambdas (`add = \x y -> x + y`); when a Fun's body is a Lambda, the compiler extracts its params for direct Cranelift function compilation (no closure overhead)
89−- Standalone lambdas compile as separate functions; free variables captured in a record-valued closure environment; multi-param lambdas (`\a b c -> body`) are curried into nested single-param lambdas at compile time
90−- Runtime functions are pre-declared as imports; `call_rt`/`call_rt_void` helpers emit calls
91−- `knot_relation_len` returns raw `usize`, not a boxed `Value` — use directly as loop bound
92−- Trait impl methods compile as mangled functions (`TraitName_TypeName_methodName`); a dispatcher function checks `knot_value_get_tag` at runtime and calls the matching impl; missing impls panic with a clear error message (except operator-mapped methods like `eq`/`compare`/`add`/`sub`/`mul`/`div`/`negate` which fall back to runtime functions for types without explicit impls)
93−- Operator trait dispatch: arithmetic operators (`+`, `-`, `*`, `/`) dispatch through `Num` trait methods (`add`, `sub`, `mul`, `div`); unary negation dispatches through `Num.negate`; `==` dispatches through `Eq.eq`; `!=` dispatches through `Eq.eq` then negates; comparison operators (`<`, `>`, `<=`, `>=`) dispatch through `Ord.compare` and check the resulting `Ordering` constructor tag; `&&`/`||`/`++` remain direct runtime calls (no trait). Primitive impls (Int, Float, Text, Bool) are registered as intrinsic codegen impls that delegate to runtime functions, avoiding circular dependencies (the prelude source does NOT contain these impls)
94−- Default trait methods: if an impl omits a method with a default body, the default is auto-compiled for that type
95−- `deriving (TraitName)` on data types auto-generates impls using the trait's default method bodies
96−- `show` calls `knot_value_show` (converts any value to Text representation)
97−- Standard library functions are registered as 1-param user_fns with currying: 1-param functions delegate directly to runtime; 2-param functions (filter, match, map, take, drop, contains) curry via `define_stdlib_fn_2` — outer function captures first arg in a closure env, inner closure extracts it and calls the runtime; 3-param functions (fold) use double currying via `define_stdlib_fn_3`
98−- Recursive derived relations: detected via `expr_contains_derived_ref` AST walk; compile to a body function `knot_user_<name>_body(db, self_val)` where self-references read from `self_val` param (via `__derived_self_<name>` env key), plus a wrapper that calls `knot_relation_fixpoint(db, body_fn_ptr, empty)` for fixed-point iteration
99−- IO do-blocks: detected via `is_io_do_block` (checks if any statement binds from or is a bare IO-returning builtin); compiled by `compile_io_do_as_thunk` which runs each IO action inline with `knot_io_run(db, io)` and wraps the final result in `knot_io_pure`; `compile_main` calls `knot_io_run(db, result)` on the main function's return value so IO main functions execute their effects
100−- IO runtime representation: `Value::IO(fn_ptr, env)` where fn_ptr has signature `extern "C" fn(db, env) -> *mut Value`; `knot_io_pure` wraps a value in a trivial IO (null fn_ptr, value as env); `knot_io_run` executes thunks or returns non-IO values as-is; `knot_io_bind` and `knot_io_then` compose IO actions via nested thunks with captured environments
101−- Effectful builtins (`println`, `readFile`, `now`, etc.) compile to `_io` wrapper functions (e.g. `knot_println_io`) that capture arguments and return `Value::IO` thunks instead of executing immediately
102−- `groupBy` in do-blocks: compiles as a two-phase loop — phase 1 collects qualifying rows into a temp relation (inside existing loops), closes those loops, then calls `knot_relation_group_by(db, temp, schema, key_cols)` which uses SQLite for grouping: inserts row indices + key column values into a temp table, executes `ORDER BY` on key columns, and groups consecutive rows with matching keys in O(n); phase 2 opens a new loop over the groups and rebinds the primary bind variable to each group (a sub-relation); field access on groups delegates to the first element via `knot_record_field`'s `Value::Relation` handling; codegen extracts key column names from the record key expression and schema from `source_schemas`
103−
104−## Supported Language Features
105−
106−Currently compiled: source declarations, type aliases, data declarations, functions, literals, records, field access, record updates, relation literals, nested relations (record fields holding `[T]` stored as JSON in SQLite, queryable via `m <- t.field` bind), binary/unary operations, if/else, do blocks (bind/where/yield/let/groupBy — pure comprehension do blocks are desugared to `__bind`/`__yield`/`__empty` calls that dispatch through Monad/Applicative/Alternative trait impls — works with any monad, not just `[]` (e.g., `Maybe`); monad type resolved at compile time via HKT unification; mixed/sequential do blocks use direct loop codegen; set-value do blocks preserved for SQL optimization; `groupBy {key}` collects rows, groups by key, and rebinds the variable to each group sub-relation — field access on a group returns the first element's field, aggregates like `count` operate on the group), set expressions, lambdas, closures, function application, case expressions (with exhaustiveness checking for ADTs), constructors, atomic transactions, migrations (schema tracking + `migrate` blocks), schema lockfile (`<name>.schema.lock`), views (`*view = do { ... yield {...} }` with bidirectional read/write, constant column filtering, auto-fill on write), derived relations (`&name = expr` — read-only computed relations, compiled as 0-param functions recomputed on each access; recursive derived relations with self-references use Datalog-style fixpoint iteration via `knot_relation_fixpoint` runtime function, converging when the relation stabilizes), traits and impls (single-dispatch on runtime type tags for primitives and ADTs, default methods, deriving from defaults, supertrait enforcement, associated types with type-level pattern matching), trait bounds (`Display a => [a] -> [Text]` syntax — explicit constraints on function type signatures validated at call sites; trait method calls automatically carry constraints; multiple bounds via `Trait1 a => Trait2 a => Type`; compile-time error when a concrete type lacks a required impl), type inference (Hindley-Milner with row polymorphism for both records and variants, let-generalization, ADT/trait/view-aware, higher-kinded type polymorphism), higher-kinded types (trait params with kind annotations like `(f : Type -> Type)`, type-level application `f a` in method signatures, `[]` and ADT names as bare type constructors in impl args, `TyCon`/`App` internal representation with normalization-based unification), IO monad (`IO {effects} a` type for pure effect descriptions — effectful builtins like `println`, `readFile`, `now` return IO values instead of performing effects immediately; IO do-blocks sequence actions via bind; effects tracked in the type: `IO {console} {}`, `IO {fs} Text`, `IO {clock} Int`; `knot_io_run` executes IO thunks at the top level in `main`; relation comprehensions are unaffected), temporal queries (`*rel : [T] with history` enables history tracking, `*rel @(timestamp)` queries past state, `now` returns `IO {clock} Int`), pattern matching in do-bind (`Circle c <- *shapes` filters relation to matching constructors and destructures; `InProgress ip <- t.status` matches a single value and skips if no match), built-ins (println, print, show, union, count, now, filter, match, map, fold, single, diff, inter, sum, avg, toUpper, toLower, take, drop, length, trim, contains, reverse, chars, id, not, toJson, parseJson, readFile, writeFile, appendFile, fileExists, removeFile, listDir, readLine, randomInt, randomFloat — IO-returning builtins produce `IO {effect} a` values), effect inference (per-declaration effect tracking for reads/writes/console/clock/network/fs/random, IO-in-atomic validation, explicit annotation checking), subset constraints (`*orders.customer <= *people.name` for referential integrity, `*users <= *users.email` for uniqueness — validated at runtime before each write commits), routes (`route Name where` with HTTP method/path/body/query/response declarations, path prefix nesting, `route Name = A | B` composition, desugared to ADTs with `respond` callback for per-branch response type safety, `listen port handler` starts `tiny_http` server with route table matching, JSON request/response serialization).
107−
108−## Version Control
109−
110−This repository uses **jujutsu (jj)**, not git. Use `jj` commands for all VCS operations.
Cargo.lock+2840−450
@@ -2,16 +2,42 @@
22# It is not intended for manual editing.
33version = 4
44
5+[[package]]
6+name = "adler2"
7+version = "2.0.1"
8+source = "registry+https://github.com/rust-lang/crates.io-index"
9+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
10+
511[[package]]
612name = "aead"
713version = "0.5.2"
814source = "registry+https://github.com/rust-lang/crates.io-index"
915checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
1016dependencies = [
11− "crypto-common",
17+ "crypto-common 0.1.7",
1218 "generic-array",
1319]
1420
21+[[package]]
22+name = "aes"
23+version = "0.9.1"
24+source = "registry+https://github.com/rust-lang/crates.io-index"
25+checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
26+dependencies = [
27+ "cipher 0.5.2",
28+ "cpubits",
29+ "cpufeatures 0.3.0",
30+]
31+
32+[[package]]
33+name = "aho-corasick"
34+version = "1.1.4"
35+source = "registry+https://github.com/rust-lang/crates.io-index"
36+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
37+dependencies = [
38+ "memchr",
39+]
40+
1541[[package]]
1642name = "allocator-api2"
1743version = "0.2.21"
@@ -36,22 +62,70 @@ version = "0.6.0"
3662source = "registry+https://github.com/rust-lang/crates.io-index"
3763checksum = "8454c8a44ce2cb9cc7e7fae67fc6128465b343b92c6631e94beca3c8d1524ea5"
3864dependencies = [
39− "unicode-width 0.2.0",
65+ "unicode-width",
4066 "yansi",
4167]
4268
69+[[package]]
70+name = "arrayref"
71+version = "0.3.9"
72+source = "registry+https://github.com/rust-lang/crates.io-index"
73+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
74+
75+[[package]]
76+name = "arrayvec"
77+version = "0.7.6"
78+source = "registry+https://github.com/rust-lang/crates.io-index"
79+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
80+
4381[[package]]
4482name = "ascii"
4583version = "1.1.0"
4684source = "registry+https://github.com/rust-lang/crates.io-index"
4785checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
4886
87+[[package]]
88+name = "atomic"
89+version = "0.6.1"
90+source = "registry+https://github.com/rust-lang/crates.io-index"
91+checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
92+dependencies = [
93+ "bytemuck",
94+]
95+
96+[[package]]
97+name = "atomic-waker"
98+version = "1.1.2"
99+source = "registry+https://github.com/rust-lang/crates.io-index"
100+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
101+
49102[[package]]
50103name = "autocfg"
51104version = "1.5.0"
52105source = "registry+https://github.com/rust-lang/crates.io-index"
53106checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
54107
108+[[package]]
109+name = "base64"
110+version = "0.22.1"
111+source = "registry+https://github.com/rust-lang/crates.io-index"
112+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
113+
114+[[package]]
115+name = "bit-set"
116+version = "0.5.3"
117+source = "registry+https://github.com/rust-lang/crates.io-index"
118+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
119+dependencies = [
120+ "bit-vec",
121+]
122+
123+[[package]]
124+name = "bit-vec"
125+version = "0.6.3"
126+source = "registry+https://github.com/rust-lang/crates.io-index"
127+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
128+
55129[[package]]
56130name = "bitflags"
57131version = "1.3.2"
@@ -60,9 +134,23 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
60134
61135[[package]]
62136name = "bitflags"
63−version = "2.11.0"
137+version = "2.11.1"
138+source = "registry+https://github.com/rust-lang/crates.io-index"
139+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
140+
141+[[package]]
142+name = "blake3"
143+version = "1.8.5"
64144source = "registry+https://github.com/rust-lang/crates.io-index"
65−checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
145+checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
146+dependencies = [
147+ "arrayref",
148+ "arrayvec",
149+ "cc",
150+ "cfg-if",
151+ "constant_time_eq",
152+ "cpufeatures 0.3.0",
153+]
66154
67155[[package]]
68156name = "block-buffer"
@@ -73,6 +161,16 @@ dependencies = [
73161 "generic-array",
74162]
75163
164+[[package]]
165+name = "block-buffer"
166+version = "0.12.1"
167+source = "registry+https://github.com/rust-lang/crates.io-index"
168+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
169+dependencies = [
170+ "hybrid-array",
171+ "zeroize",
172+]
173+
76174[[package]]
77175name = "bumpalo"
78176version = "3.20.2"
@@ -83,10 +181,25 @@ dependencies = [
83181]
84182
85183[[package]]
86−name = "cassowary"
87−version = "0.3.0"
184+name = "bytemuck"
185+version = "1.25.0"
186+source = "registry+https://github.com/rust-lang/crates.io-index"
187+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
188+
189+[[package]]
190+name = "bytes"
191+version = "1.11.1"
88192source = "registry+https://github.com/rust-lang/crates.io-index"
89−checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
193+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
194+
195+[[package]]
196+name = "bzip2"
197+version = "0.6.1"
198+source = "registry+https://github.com/rust-lang/crates.io-index"
199+checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
200+dependencies = [
201+ "libbz2-rs-sys",
202+]
90203
91204[[package]]
92205name = "castaway"
@@ -99,11 +212,13 @@ dependencies = [
99212
100213[[package]]
101214name = "cc"
102−version = "1.2.56"
215+version = "1.2.60"
103216source = "registry+https://github.com/rust-lang/crates.io-index"
104−checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
217+checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
105218dependencies = [
106219 "find-msvc-tools",
220+ "jobserver",
221+ "libc",
107222 "shlex",
108223]
109224
@@ -113,6 +228,12 @@ version = "1.0.4"
113228source = "registry+https://github.com/rust-lang/crates.io-index"
114229checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
115230
231+[[package]]
232+name = "cfg_aliases"
233+version = "0.2.1"
234+source = "registry+https://github.com/rust-lang/crates.io-index"
235+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
236+
116237[[package]]
117238name = "chacha20"
118239version = "0.9.1"
@@ -120,8 +241,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
120241checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
121242dependencies = [
122243 "cfg-if",
123− "cipher",
124− "cpufeatures",
244+ "cipher 0.4.4",
245+ "cpufeatures 0.2.17",
246+]
247+
248+[[package]]
249+name = "chacha20"
250+version = "0.10.1"
251+source = "registry+https://github.com/rust-lang/crates.io-index"
252+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
253+dependencies = [
254+ "cfg-if",
255+ "cpufeatures 0.3.0",
256+ "rand_core 0.10.1",
125257]
126258
127259[[package]]
@@ -131,8 +263,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
131263checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
132264dependencies = [
133265 "aead",
134− "chacha20",
135− "cipher",
266+ "chacha20 0.9.1",
267+ "cipher 0.4.4",
136268 "poly1305",
137269 "zeroize",
138270]
@@ -149,16 +281,32 @@ version = "0.4.4"
149281source = "registry+https://github.com/rust-lang/crates.io-index"
150282checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
151283dependencies = [
152− "crypto-common",
153− "inout",
284+ "crypto-common 0.1.7",
285+ "inout 0.1.4",
154286 "zeroize",
155287]
156288
289+[[package]]
290+name = "cipher"
291+version = "0.5.2"
292+source = "registry+https://github.com/rust-lang/crates.io-index"
293+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
294+dependencies = [
295+ "crypto-common 0.2.2",
296+ "inout 0.2.2",
297+]
298+
299+[[package]]
300+name = "cmov"
301+version = "0.5.4"
302+source = "registry+https://github.com/rust-lang/crates.io-index"
303+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
304+
157305[[package]]
158306name = "compact_str"
159−version = "0.8.1"
307+version = "0.9.0"
160308source = "registry+https://github.com/rust-lang/crates.io-index"
161−checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
309+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
162310dependencies = [
163311 "castaway",
164312 "cfg-if",
@@ -168,6 +316,33 @@ dependencies = [
168316 "static_assertions",
169317]
170318
319+[[package]]
320+name = "const-oid"
321+version = "0.10.2"
322+source = "registry+https://github.com/rust-lang/crates.io-index"
323+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
324+
325+[[package]]
326+name = "constant_time_eq"
327+version = "0.4.2"
328+source = "registry+https://github.com/rust-lang/crates.io-index"
329+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
330+
331+[[package]]
332+name = "convert_case"
333+version = "0.10.0"
334+source = "registry+https://github.com/rust-lang/crates.io-index"
335+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
336+dependencies = [
337+ "unicode-segmentation",
338+]
339+
340+[[package]]
341+name = "cpubits"
342+version = "0.1.1"
343+source = "registry+https://github.com/rust-lang/crates.io-index"
344+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
345+
171346[[package]]
172347name = "cpufeatures"
173348version = "0.2.17"
@@ -177,28 +352,60 @@ dependencies = [
177352 "libc",
178353]
179354
355+[[package]]
356+name = "cpufeatures"
357+version = "0.3.0"
358+source = "registry+https://github.com/rust-lang/crates.io-index"
359+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
360+dependencies = [
361+ "libc",
362+]
363+
364+[[package]]
365+name = "cranelift-assembler-x64"
366+version = "0.131.0"
367+source = "registry+https://github.com/rust-lang/crates.io-index"
368+checksum = "6edb5bdd1af46714e3224a017fabbbd57f70df4e840eb5ad6a7429dc456119d6"
369+dependencies = [
370+ "cranelift-assembler-x64-meta",
371+]
372+
373+[[package]]
374+name = "cranelift-assembler-x64-meta"
375+version = "0.131.0"
376+source = "registry+https://github.com/rust-lang/crates.io-index"
377+checksum = "a819599186e1b1a1f88d464e06045696afc7aa3e0cc018aa0b2999cb63d1d088"
378+dependencies = [
379+ "cranelift-srcgen",
380+]
381+
180382[[package]]
181383name = "cranelift-bforest"
182−version = "0.116.1"
384+version = "0.131.0"
183385source = "registry+https://github.com/rust-lang/crates.io-index"
184−checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4"
386+checksum = "36e2c152d488e03c87b913bc2ed3414416eb1e0d66d61b49af60bf456a9665c7"
185387dependencies = [
186388 "cranelift-entity",
389+ "wasmtime-internal-core",
187390]
188391
189392[[package]]
190393name = "cranelift-bitset"
191−version = "0.116.1"
394+version = "0.131.0"
192395source = "registry+https://github.com/rust-lang/crates.io-index"
193−checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34"
396+checksum = "b6559d4fbc253d1396e1f6beeae57fa88a244f02aaf0cde2a735afd3492d9b2e"
397+dependencies = [
398+ "wasmtime-internal-core",
399+]
194400
195401[[package]]
196402name = "cranelift-codegen"
197−version = "0.116.1"
403+version = "0.131.0"
198404source = "registry+https://github.com/rust-lang/crates.io-index"
199−checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e"
405+checksum = "96d9315d98d6e0a64454d4c83be2ee0e8055c3f80c3b2d7bcad7079f281a06ff"
200406dependencies = [
201407 "bumpalo",
408+ "cranelift-assembler-x64",
202409 "cranelift-bforest",
203410 "cranelift-bitset",
204411 "cranelift-codegen-meta",
@@ -207,53 +414,59 @@ dependencies = [
207414 "cranelift-entity",
208415 "cranelift-isle",
209416 "gimli",
210− "hashbrown 0.14.5",
417+ "hashbrown 0.16.1",
418+ "libm",
211419 "log",
212420 "regalloc2",
213421 "rustc-hash",
214422 "serde",
215423 "smallvec",
216424 "target-lexicon",
425+ "wasmtime-internal-core",
217426]
218427
219428[[package]]
220429name = "cranelift-codegen-meta"
221−version = "0.116.1"
430+version = "0.131.0"
222431source = "registry+https://github.com/rust-lang/crates.io-index"
223−checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8"
432+checksum = "d89c00a88081c55e3087c45bebc77e0cc973de2d7b44ef6a943c7122647b89f5"
224433dependencies = [
434+ "cranelift-assembler-x64-meta",
225435 "cranelift-codegen-shared",
436+ "cranelift-srcgen",
437+ "heck",
226438]
227439
228440[[package]]
229441name = "cranelift-codegen-shared"
230−version = "0.116.1"
442+version = "0.131.0"
231443source = "registry+https://github.com/rust-lang/crates.io-index"
232−checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb"
444+checksum = "879f77c497a1eb6273482aa1ac3b23cb8563ff04edb39ed5dfcfd28c8deff8f5"
233445
234446[[package]]
235447name = "cranelift-control"
236−version = "0.116.1"
448+version = "0.131.0"
237449source = "registry+https://github.com/rust-lang/crates.io-index"
238−checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef"
450+checksum = "498dc1f17a6910c88316d49c7176d8fa97cf10c30859c32a266040449317f963"
239451dependencies = [
240452 "arbitrary",
241453]
242454
243455[[package]]
244456name = "cranelift-entity"
245−version = "0.116.1"
457+version = "0.131.0"
246458source = "registry+https://github.com/rust-lang/crates.io-index"
247−checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323"
459+checksum = "c2acba797f6a46042ce82aaf7680d0c3567fe2001e238db9df649fd104a2727f"
248460dependencies = [
249461 "cranelift-bitset",
462+ "wasmtime-internal-core",
250463]
251464
252465[[package]]
253466name = "cranelift-frontend"
254−version = "0.116.1"
467+version = "0.131.0"
255468source = "registry+https://github.com/rust-lang/crates.io-index"
256−checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57"
469+checksum = "4dca3df1d107d98d88f159ad1d5eaa2d5cdb678b3d5bcfadc6fc83d8ebb448ea"
257470dependencies = [
258471 "cranelift-codegen",
259472 "log",
@@ -263,15 +476,35 @@ dependencies = [
263476
264477[[package]]
265478name = "cranelift-isle"
266−version = "0.116.1"
479+version = "0.131.0"
480+source = "registry+https://github.com/rust-lang/crates.io-index"
481+checksum = "f62dd18116d88bed649871feceda79dad7b59cc685ea8998c2b3e64d0e689602"
482+
483+[[package]]
484+name = "cranelift-jit"
485+version = "0.131.0"
267486source = "registry+https://github.com/rust-lang/crates.io-index"
268−checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d"
487+checksum = "0a4942770ce6662b44d903493d7c5b00f9a986a713a61aae148306eaef21ebd4"
488+dependencies = [
489+ "anyhow",
490+ "cranelift-codegen",
491+ "cranelift-control",
492+ "cranelift-entity",
493+ "cranelift-module",
494+ "cranelift-native",
495+ "libc",
496+ "log",
497+ "region",
498+ "target-lexicon",
499+ "wasmtime-internal-jit-icache-coherence",
500+ "windows-sys 0.61.2",
501+]
269502
270503[[package]]
271504name = "cranelift-module"
272−version = "0.116.1"
505+version = "0.131.0"
273506source = "registry+https://github.com/rust-lang/crates.io-index"
274−checksum = "4d55612bebcf16ff7306c8a6f5bdb6d45662b8aa1ee058ecce8807ad87db719b"
507+checksum = "fb5ca0d214ecee44405ea9f0c65a5318b41ac469e8258fd9fe944e564c1c1b0b"
275508dependencies = [
276509 "anyhow",
277510 "cranelift-codegen",
@@ -280,9 +513,9 @@ dependencies = [
280513
281514[[package]]
282515name = "cranelift-native"
283−version = "0.116.1"
516+version = "0.131.0"
284517source = "registry+https://github.com/rust-lang/crates.io-index"
285−checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7"
518+checksum = "f843b80360d7fdf61a6124642af7597f6d55724cf521210c34af8a1c66daca6e"
286519dependencies = [
287520 "cranelift-codegen",
288521 "libc",
@@ -291,9 +524,9 @@ dependencies = [
291524
292525[[package]]
293526name = "cranelift-object"
294−version = "0.116.1"
527+version = "0.131.0"
295528source = "registry+https://github.com/rust-lang/crates.io-index"
296−checksum = "aad5a6d3e379493c3f8b35dc61c93d0bf5f27003bbe20614e0200b0ec372ef52"
529+checksum = "b9d212d15015c374333b11b833111b7c7e686bfaec02385af53611050bce7e9d"
297530dependencies = [
298531 "anyhow",
299532 "cranelift-codegen",
@@ -304,6 +537,12 @@ dependencies = [
304537 "target-lexicon",
305538]
306539
540+[[package]]
541+name = "cranelift-srcgen"
542+version = "0.131.0"
543+source = "registry+https://github.com/rust-lang/crates.io-index"
544+checksum = "090ee5de58c6f17eb5e3a5ae8cf1695c7efea04ec4dd0ecba6a5b996c9bad7dc"
545+
307546[[package]]
308547name = "crc32fast"
309548version = "1.5.0"
@@ -330,12 +569,14 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
330569
331570[[package]]
332571name = "crossterm"
333−version = "0.28.1"
572+version = "0.29.0"
334573source = "registry+https://github.com/rust-lang/crates.io-index"
335−checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
574+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
336575dependencies = [
337− "bitflags 2.11.0",
576+ "bitflags 2.11.1",
338577 "crossterm_winapi",
578+ "derive_more",
579+ "document-features",
339580 "mio",
340581 "parking_lot",
341582 "rustix",
@@ -360,10 +601,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
360601checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
361602dependencies = [
362603 "generic-array",
363− "rand_core",
604+ "rand_core 0.6.4",
364605 "typenum",
365606]
366607
608+[[package]]
609+name = "crypto-common"
610+version = "0.2.2"
611+source = "registry+https://github.com/rust-lang/crates.io-index"
612+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
613+dependencies = [
614+ "hybrid-array",
615+]
616+
617+[[package]]
618+name = "csscolorparser"
619+version = "0.6.2"
620+source = "registry+https://github.com/rust-lang/crates.io-index"
621+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
622+dependencies = [
623+ "lab",
624+ "phf",
625+]
626+
627+[[package]]
628+name = "ctutils"
629+version = "0.4.2"
630+source = "registry+https://github.com/rust-lang/crates.io-index"
631+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
632+dependencies = [
633+ "cmov",
634+]
635+
367636[[package]]
368637name = "curve25519-dalek"
369638version = "4.1.3"
@@ -371,9 +640,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
371640checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
372641dependencies = [
373642 "cfg-if",
374− "cpufeatures",
643+ "cpufeatures 0.2.17",
375644 "curve25519-dalek-derive",
376− "digest",
645+ "digest 0.10.7",
377646 "fiat-crypto",
378647 "rustc_version",
379648 "subtle",
@@ -388,7 +657,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
388657dependencies = [
389658 "proc-macro2",
390659 "quote",
391− "syn",
660+ "syn 2.0.117",
392661]
393662
394663[[package]]
@@ -411,7 +680,7 @@ dependencies = [
411680 "proc-macro2",
412681 "quote",
413682 "strsim",
414− "syn",
683+ "syn 2.0.117",
415684]
416685
417686[[package]]
@@ -422,7 +691,64 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
422691dependencies = [
423692 "darling_core",
424693 "quote",
425− "syn",
694+ "syn 2.0.117",
695+]
696+
697+[[package]]
698+name = "dashmap"
699+version = "6.1.0"
700+source = "registry+https://github.com/rust-lang/crates.io-index"
701+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
702+dependencies = [
703+ "cfg-if",
704+ "crossbeam-utils",
705+ "hashbrown 0.14.5",
706+ "lock_api",
707+ "once_cell",
708+ "parking_lot_core",
709+]
710+
711+[[package]]
712+name = "deflate64"
713+version = "0.1.12"
714+source = "registry+https://github.com/rust-lang/crates.io-index"
715+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
716+
717+[[package]]
718+name = "deltae"
719+version = "0.3.2"
720+source = "registry+https://github.com/rust-lang/crates.io-index"
721+checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
722+
723+[[package]]
724+name = "deranged"
725+version = "0.5.8"
726+source = "registry+https://github.com/rust-lang/crates.io-index"
727+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
728+dependencies = [
729+ "powerfmt",
730+]
731+
732+[[package]]
733+name = "derive_more"
734+version = "2.1.1"
735+source = "registry+https://github.com/rust-lang/crates.io-index"
736+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
737+dependencies = [
738+ "derive_more-impl",
739+]
740+
741+[[package]]
742+name = "derive_more-impl"
743+version = "2.1.1"
744+source = "registry+https://github.com/rust-lang/crates.io-index"
745+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
746+dependencies = [
747+ "convert_case",
748+ "proc-macro2",
749+ "quote",
750+ "rustc_version",
751+ "syn 2.0.117",
426752]
427753
428754[[package]]
@@ -431,8 +757,41 @@ version = "0.10.7"
431757source = "registry+https://github.com/rust-lang/crates.io-index"
432758checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
433759dependencies = [
434− "block-buffer",
435− "crypto-common",
760+ "block-buffer 0.10.4",
761+ "crypto-common 0.1.7",
762+]
763+
764+[[package]]
765+name = "digest"
766+version = "0.11.3"
767+source = "registry+https://github.com/rust-lang/crates.io-index"
768+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
769+dependencies = [
770+ "block-buffer 0.12.1",
771+ "const-oid",
772+ "crypto-common 0.2.2",
773+ "ctutils",
774+ "zeroize",
775+]
776+
777+[[package]]
778+name = "displaydoc"
779+version = "0.2.6"
780+source = "registry+https://github.com/rust-lang/crates.io-index"
781+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
782+dependencies = [
783+ "proc-macro2",
784+ "quote",
785+ "syn 2.0.117",
786+]
787+
788+[[package]]
789+name = "document-features"
790+version = "0.2.12"
791+source = "registry+https://github.com/rust-lang/crates.io-index"
792+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
793+dependencies = [
794+ "litrs",
436795]
437796
438797[[package]]
@@ -452,7 +811,7 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
452811dependencies = [
453812 "curve25519-dalek",
454813 "ed25519",
455− "sha2",
814+ "sha2 0.10.9",
456815 "subtle",
457816]
458817
@@ -478,6 +837,15 @@ dependencies = [
478837 "windows-sys 0.61.2",
479838]
480839
840+[[package]]
841+name = "euclid"
842+version = "0.22.14"
843+source = "registry+https://github.com/rust-lang/crates.io-index"
844+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
845+dependencies = [
846+ "num-traits",
847+]
848+
481849[[package]]
482850name = "fallible-iterator"
483851version = "0.3.0"
@@ -490,18 +858,62 @@ version = "0.1.9"
490858source = "registry+https://github.com/rust-lang/crates.io-index"
491859checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
492860
861+[[package]]
862+name = "fancy-regex"
863+version = "0.11.0"
864+source = "registry+https://github.com/rust-lang/crates.io-index"
865+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
866+dependencies = [
867+ "bit-set",
868+ "regex",
869+]
870+
493871[[package]]
494872name = "fiat-crypto"
495873version = "0.2.9"
496874source = "registry+https://github.com/rust-lang/crates.io-index"
497875checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
498876
877+[[package]]
878+name = "filedescriptor"
879+version = "0.8.3"
880+source = "registry+https://github.com/rust-lang/crates.io-index"
881+checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
882+dependencies = [
883+ "libc",
884+ "thiserror 1.0.69",
885+ "winapi",
886+]
887+
499888[[package]]
500889name = "find-msvc-tools"
501890version = "0.1.9"
502891source = "registry+https://github.com/rust-lang/crates.io-index"
503892checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
504893
894+[[package]]
895+name = "finl_unicode"
896+version = "1.4.0"
897+source = "registry+https://github.com/rust-lang/crates.io-index"
898+checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
899+
900+[[package]]
901+name = "fixedbitset"
902+version = "0.4.2"
903+source = "registry+https://github.com/rust-lang/crates.io-index"
904+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
905+
906+[[package]]
907+name = "flate2"
908+version = "1.1.9"
909+source = "registry+https://github.com/rust-lang/crates.io-index"
910+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
911+dependencies = [
912+ "crc32fast",
913+ "miniz_oxide",
914+ "zlib-rs",
915+]
916+
505917[[package]]
506918name = "fluent-uri"
507919version = "0.1.4"
@@ -511,6 +923,12 @@ dependencies = [
511923 "bitflags 1.3.2",
512924]
513925
926+[[package]]
927+name = "fnv"
928+version = "1.0.7"
929+source = "registry+https://github.com/rust-lang/crates.io-index"
930+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
931+
514932[[package]]
515933name = "foldhash"
516934version = "0.1.5"
@@ -518,8 +936,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
518936checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
519937
520938[[package]]
521−name = "generic-array"
522−version = "0.14.7"
939+name = "foldhash"
940+version = "0.2.0"
941+source = "registry+https://github.com/rust-lang/crates.io-index"
942+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
943+
944+[[package]]
945+name = "form_urlencoded"
946+version = "1.2.2"
947+source = "registry+https://github.com/rust-lang/crates.io-index"
948+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
949+dependencies = [
950+ "percent-encoding",
951+]
952+
953+[[package]]
954+name = "futures-channel"
955+version = "0.3.33"
956+source = "registry+https://github.com/rust-lang/crates.io-index"
957+checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
958+dependencies = [
959+ "futures-core",
960+ "futures-sink",
961+]
962+
963+[[package]]
964+name = "futures-core"
965+version = "0.3.33"
966+source = "registry+https://github.com/rust-lang/crates.io-index"
967+checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
968+
969+[[package]]
970+name = "futures-io"
971+version = "0.3.33"
972+source = "registry+https://github.com/rust-lang/crates.io-index"
973+checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
974+
975+[[package]]
976+name = "futures-sink"
977+version = "0.3.33"
978+source = "registry+https://github.com/rust-lang/crates.io-index"
979+checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
980+
981+[[package]]
982+name = "futures-task"
983+version = "0.3.33"
984+source = "registry+https://github.com/rust-lang/crates.io-index"
985+checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
986+
987+[[package]]
988+name = "futures-util"
989+version = "0.3.33"
990+source = "registry+https://github.com/rust-lang/crates.io-index"
991+checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
992+dependencies = [
993+ "futures-core",
994+ "futures-io",
995+ "futures-sink",
996+ "futures-task",
997+ "memchr",
998+ "pin-project-lite",
999+ "slab",
1000+]
1001+
1002+[[package]]
1003+name = "generic-array"
1004+version = "0.14.7"
5231005source = "registry+https://github.com/rust-lang/crates.io-index"
5241006checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
5251007dependencies = [
@@ -534,8 +1016,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
5341016checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
5351017dependencies = [
5361018 "cfg-if",
1019+ "js-sys",
5371020 "libc",
5381021 "wasi",
1022+ "wasm-bindgen",
5391023]
5401024
5411025[[package]]
@@ -546,17 +1030,34 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
5461030dependencies = [
5471031 "cfg-if",
5481032 "libc",
549− "r-efi",
1033+ "r-efi 5.3.0",
1034+ "wasip2",
1035+]
1036+
1037+[[package]]
1038+name = "getrandom"
1039+version = "0.4.2"
1040+source = "registry+https://github.com/rust-lang/crates.io-index"
1041+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
1042+dependencies = [
1043+ "cfg-if",
1044+ "js-sys",
1045+ "libc",
1046+ "r-efi 6.0.0",
1047+ "rand_core 0.10.1",
5501048 "wasip2",
1049+ "wasip3",
1050+ "wasm-bindgen",
5511051]
5521052
5531053[[package]]
5541054name = "gimli"
555−version = "0.31.1"
1055+version = "0.33.0"
5561056source = "registry+https://github.com/rust-lang/crates.io-index"
557−checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
1057+checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c"
5581058dependencies = [
559− "fallible-iterator",
1059+ "fnv",
1060+ "hashbrown 0.16.1",
5601061 "indexmap",
5611062 "stable_deref_trait",
5621063]
@@ -573,9 +1074,7 @@ version = "0.15.5"
5731074source = "registry+https://github.com/rust-lang/crates.io-index"
5741075checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
5751076dependencies = [
576− "allocator-api2",
577− "equivalent",
578− "foldhash",
1077+ "foldhash 0.1.5",
5791078]
5801079
5811080[[package]]
@@ -583,14 +1082,28 @@ name = "hashbrown"
5831082version = "0.16.1"
5841083source = "registry+https://github.com/rust-lang/crates.io-index"
5851084checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1085+dependencies = [
1086+ "allocator-api2",
1087+ "equivalent",
1088+ "foldhash 0.2.0",
1089+]
1090+
1091+[[package]]
1092+name = "hashbrown"
1093+version = "0.17.0"
1094+source = "registry+https://github.com/rust-lang/crates.io-index"
1095+checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
1096+dependencies = [
1097+ "foldhash 0.2.0",
1098+]
5861099
5871100[[package]]
5881101name = "hashlink"
589−version = "0.10.0"
1102+version = "0.11.0"
5901103source = "registry+https://github.com/rust-lang/crates.io-index"
591−checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
1104+checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
5921105dependencies = [
593− "hashbrown 0.15.5",
1106+ "hashbrown 0.16.1",
5941107]
5951108
5961109[[package]]
@@ -600,714 +1113,2325 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
6001113checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
6011114
6021115[[package]]
603−name = "httpdate"
604−version = "1.0.3"
1116+name = "hex"
1117+version = "0.4.3"
6051118source = "registry+https://github.com/rust-lang/crates.io-index"
606−checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1119+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
6071120
6081121[[package]]
609−name = "ident_case"
610−version = "1.0.1"
1122+name = "hmac"
1123+version = "0.13.0"
6111124source = "registry+https://github.com/rust-lang/crates.io-index"
612−checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1125+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
1126+dependencies = [
1127+ "digest 0.11.3",
1128+]
6131129
6141130[[package]]
615−name = "indexmap"
616−version = "2.13.0"
1131+name = "http"
1132+version = "1.4.0"
6171133source = "registry+https://github.com/rust-lang/crates.io-index"
618−checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
1134+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
6191135dependencies = [
620− "equivalent",
621− "hashbrown 0.16.1",
1136+ "bytes",
1137+ "itoa",
6221138]
6231139
6241140[[package]]
625−name = "indoc"
626−version = "2.0.7"
1141+name = "http-body"
1142+version = "1.1.0"
6271143source = "registry+https://github.com/rust-lang/crates.io-index"
628−checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1144+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
6291145dependencies = [
630− "rustversion",
1146+ "bytes",
1147+ "http",
6311148]
6321149
6331150[[package]]
634−name = "inout"
1151+name = "http-body-util"
6351152version = "0.1.4"
6361153source = "registry+https://github.com/rust-lang/crates.io-index"
637−checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
1154+checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
6381155dependencies = [
639− "generic-array",
1156+ "bytes",
1157+ "futures-core",
1158+ "http",
1159+ "http-body",
1160+ "pin-project-lite",
6401161]
6411162
6421163[[package]]
643−name = "instability"
644−version = "0.3.12"
1164+name = "httparse"
1165+version = "1.10.1"
6451166source = "registry+https://github.com/rust-lang/crates.io-index"
646−checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971"
1167+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
1168+
1169+[[package]]
1170+name = "httpdate"
1171+version = "1.0.3"
1172+source = "registry+https://github.com/rust-lang/crates.io-index"
1173+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1174+
1175+[[package]]
1176+name = "hybrid-array"
1177+version = "0.4.13"
1178+source = "registry+https://github.com/rust-lang/crates.io-index"
1179+checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
6471180dependencies = [
648− "darling",
649− "indoc",
650− "proc-macro2",
651− "quote",
652− "syn",
1181+ "typenum",
6531182]
6541183
6551184[[package]]
656−name = "itertools"
657−version = "0.13.0"
1185+name = "hyper"
1186+version = "1.11.0"
6581187source = "registry+https://github.com/rust-lang/crates.io-index"
659−checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
1188+checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
6601189dependencies = [
661− "either",
1190+ "atomic-waker",
1191+ "bytes",
1192+ "futures-channel",
1193+ "futures-core",
1194+ "http",
1195+ "http-body",
1196+ "httparse",
1197+ "itoa",
1198+ "pin-project-lite",
1199+ "smallvec",
1200+ "tokio",
1201+ "want",
6621202]
6631203
6641204[[package]]
665−name = "itoa"
666−version = "1.0.17"
1205+name = "hyper-rustls"
1206+version = "0.27.9"
6671207source = "registry+https://github.com/rust-lang/crates.io-index"
668−checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
1208+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
1209+dependencies = [
1210+ "http",
1211+ "hyper",
1212+ "hyper-util",
1213+ "rustls",
1214+ "tokio",
1215+ "tokio-rustls",
1216+ "tower-service",
1217+ "webpki-roots",
1218+]
6691219
6701220[[package]]
671−name = "knot"
672−version = "0.1.0"
1221+name = "hyper-util"
1222+version = "0.1.20"
1223+source = "registry+https://github.com/rust-lang/crates.io-index"
1224+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
6731225dependencies = [
674− "ariadne",
1226+ "base64",
1227+ "bytes",
1228+ "futures-channel",
1229+ "futures-util",
1230+ "http",
1231+ "http-body",
1232+ "hyper",
1233+ "ipnet",
1234+ "libc",
1235+ "percent-encoding",
1236+ "pin-project-lite",
1237+ "socket2",
1238+ "tokio",
1239+ "tower-service",
1240+ "tracing",
6751241]
6761242
6771243[[package]]
678−name = "knot-compiler"
679−version = "0.1.0"
1244+name = "icu_collections"
1245+version = "2.2.0"
1246+source = "registry+https://github.com/rust-lang/crates.io-index"
1247+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
6801248dependencies = [
681− "cranelift-codegen",
682− "cranelift-frontend",
683− "cranelift-module",
684− "cranelift-native",
685− "cranelift-object",
686− "knot",
687− "knot-runtime",
688− "target-lexicon",
1249+ "displaydoc",
1250+ "potential_utf",
1251+ "utf8_iter",
1252+ "yoke",
1253+ "zerofrom",
1254+ "zerovec",
6891255]
6901256
6911257[[package]]
692−name = "knot-lsp"
693−version = "0.1.0"
1258+name = "icu_locale_core"
1259+version = "2.2.0"
1260+source = "registry+https://github.com/rust-lang/crates.io-index"
1261+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
6941262dependencies = [
695− "knot",
696− "knot-compiler",
697− "lsp-server",
698− "lsp-types",
699− "serde",
700− "serde_json",
1263+ "displaydoc",
1264+ "litemap",
1265+ "tinystr",
1266+ "writeable",
1267+ "zerovec",
7011268]
7021269
7031270[[package]]
704−name = "knot-runtime"
705−version = "0.1.0"
1271+name = "icu_normalizer"
1272+version = "2.2.0"
1273+source = "registry+https://github.com/rust-lang/crates.io-index"
1274+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
7061275dependencies = [
707− "chacha20poly1305",
708− "crossterm",
709− "ed25519-dalek",
710− "getrandom 0.3.4",
711− "num-bigint",
712− "num-integer",
713− "num-traits",
714− "ratatui",
715− "rusqlite",
716− "tiny_http",
717− "x25519-dalek",
1276+ "icu_collections",
1277+ "icu_normalizer_data",
1278+ "icu_properties",
1279+ "icu_provider",
1280+ "smallvec",
1281+ "zerovec",
7181282]
7191283
7201284[[package]]
721−name = "libc"
722−version = "0.2.183"
1285+name = "icu_normalizer_data"
1286+version = "2.2.0"
7231287source = "registry+https://github.com/rust-lang/crates.io-index"
724−checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
1288+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
7251289
7261290[[package]]
727−name = "libsqlite3-sys"
728−version = "0.32.0"
1291+name = "icu_properties"
1292+version = "2.2.0"
7291293source = "registry+https://github.com/rust-lang/crates.io-index"
730−checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
1294+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
7311295dependencies = [
732− "cc",
733− "pkg-config",
734− "vcpkg",
1296+ "icu_collections",
1297+ "icu_locale_core",
1298+ "icu_properties_data",
1299+ "icu_provider",
1300+ "zerotrie",
1301+ "zerovec",
7351302]
7361303
7371304[[package]]
738−name = "linux-raw-sys"
739−version = "0.4.15"
1305+name = "icu_properties_data"
1306+version = "2.2.0"
7401307source = "registry+https://github.com/rust-lang/crates.io-index"
741−checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
1308+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
7421309
7431310[[package]]
744−name = "lock_api"
745−version = "0.4.14"
1311+name = "icu_provider"
1312+version = "2.2.0"
7461313source = "registry+https://github.com/rust-lang/crates.io-index"
747−checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
1314+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
7481315dependencies = [
749− "scopeguard",
1316+ "displaydoc",
1317+ "icu_locale_core",
1318+ "writeable",
1319+ "yoke",
1320+ "zerofrom",
1321+ "zerotrie",
1322+ "zerovec",
7501323]
7511324
7521325[[package]]
753−name = "log"
754−version = "0.4.29"
1326+name = "id-arena"
1327+version = "2.3.0"
7551328source = "registry+https://github.com/rust-lang/crates.io-index"
756−checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
1329+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
7571330
7581331[[package]]
759−name = "lru"
760−version = "0.12.5"
1332+name = "ident_case"
1333+version = "1.0.1"
7611334source = "registry+https://github.com/rust-lang/crates.io-index"
762−checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
1335+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1336+
1337+[[package]]
1338+name = "idna"
1339+version = "1.1.0"
1340+source = "registry+https://github.com/rust-lang/crates.io-index"
1341+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
7631342dependencies = [
764− "hashbrown 0.15.5",
1343+ "idna_adapter",
1344+ "smallvec",
1345+ "utf8_iter",
7651346]
7661347
7671348[[package]]
768−name = "lsp-server"
769−version = "0.7.9"
1349+name = "idna_adapter"
1350+version = "1.2.2"
7701351source = "registry+https://github.com/rust-lang/crates.io-index"
771−checksum = "7d6ada348dbc2703cbe7637b2dda05cff84d3da2819c24abcb305dd613e0ba2e"
1352+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
7721353dependencies = [
773− "crossbeam-channel",
774− "log",
775− "serde",
776− "serde_derive",
777− "serde_json",
1354+ "icu_normalizer",
1355+ "icu_properties",
7781356]
7791357
7801358[[package]]
781−name = "lsp-types"
782−version = "0.97.0"
1359+name = "indexmap"
1360+version = "2.14.0"
7831361source = "registry+https://github.com/rust-lang/crates.io-index"
784−checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
1362+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
7851363dependencies = [
786− "bitflags 1.3.2",
787− "fluent-uri",
1364+ "equivalent",
1365+ "hashbrown 0.17.0",
7881366 "serde",
789− "serde_json",
790− "serde_repr",
1367+ "serde_core",
7911368]
7921369
7931370[[package]]
794−name = "memchr"
795−version = "2.8.0"
1371+name = "indoc"
1372+version = "2.0.7"
7961373source = "registry+https://github.com/rust-lang/crates.io-index"
797−checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1374+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1375+dependencies = [
1376+ "rustversion",
1377+]
7981378
7991379[[package]]
800−name = "mio"
801−version = "1.1.1"
1380+name = "inout"
1381+version = "0.1.4"
8021382source = "registry+https://github.com/rust-lang/crates.io-index"
803−checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
1383+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
8041384dependencies = [
805− "libc",
806− "log",
807− "wasi",
808− "windows-sys 0.61.2",
1385+ "generic-array",
8091386]
8101387
8111388[[package]]
812−name = "num-bigint"
813−version = "0.4.6"
1389+name = "inout"
1390+version = "0.2.2"
8141391source = "registry+https://github.com/rust-lang/crates.io-index"
815−checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
1392+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
8161393dependencies = [
817− "num-integer",
818− "num-traits",
1394+ "hybrid-array",
8191395]
8201396
8211397[[package]]
822−name = "num-integer"
823−version = "0.1.46"
1398+name = "instability"
1399+version = "0.3.12"
8241400source = "registry+https://github.com/rust-lang/crates.io-index"
825−checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
1401+checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971"
8261402dependencies = [
827− "num-traits",
1403+ "darling",
1404+ "indoc",
1405+ "proc-macro2",
1406+ "quote",
1407+ "syn 2.0.117",
8281408]
8291409
8301410[[package]]
831−name = "num-traits"
832−version = "0.2.19"
1411+name = "ipnet"
1412+version = "2.12.0"
8331413source = "registry+https://github.com/rust-lang/crates.io-index"
834−checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
835−dependencies = [
836− "autocfg",
837−]
1414+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
8381415
8391416[[package]]
840−name = "object"
841−version = "0.36.7"
1417+name = "itertools"
1418+version = "0.14.0"
8421419source = "registry+https://github.com/rust-lang/crates.io-index"
843−checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
1420+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
8441421dependencies = [
845− "crc32fast",
846− "hashbrown 0.15.5",
847− "indexmap",
848− "memchr",
1422+ "either",
8491423]
8501424
8511425[[package]]
852−name = "opaque-debug"
853−version = "0.3.1"
1426+name = "itoa"
1427+version = "1.0.18"
8541428source = "registry+https://github.com/rust-lang/crates.io-index"
855−checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
1429+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
8561430
8571431[[package]]
858−name = "parking_lot"
859−version = "0.12.5"
1432+name = "jobserver"
1433+version = "0.1.35"
8601434source = "registry+https://github.com/rust-lang/crates.io-index"
861−checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
1435+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
8621436dependencies = [
863− "lock_api",
864− "parking_lot_core",
1437+ "getrandom 0.4.2",
1438+ "libc",
8651439]
8661440
8671441[[package]]
868−name = "parking_lot_core"
869−version = "0.9.12"
1442+name = "js-sys"
1443+version = "0.3.95"
8701444source = "registry+https://github.com/rust-lang/crates.io-index"
871−checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
1445+checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
8721446dependencies = [
8731447 "cfg-if",
874− "libc",
875− "redox_syscall",
876− "smallvec",
877− "windows-link",
1448+ "futures-util",
1449+ "once_cell",
1450+ "wasm-bindgen",
8781451]
8791452
8801453[[package]]
881−name = "paste"
882−version = "1.0.15"
1454+name = "kasuari"
1455+version = "0.4.12"
8831456source = "registry+https://github.com/rust-lang/crates.io-index"
884−checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1457+checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
1458+dependencies = [
1459+ "hashbrown 0.16.1",
1460+ "portable-atomic",
1461+ "thiserror 2.0.18",
1462+]
8851463
8861464[[package]]
887−name = "pkg-config"
888−version = "0.3.32"
889−source = "registry+https://github.com/rust-lang/crates.io-index"
890−checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1465+name = "knot"
1466+version = "0.1.0"
1467+dependencies = [
1468+ "ariadne",
1469+]
1470+
1471+[[package]]
1472+name = "knot-compile-rt"
1473+version = "0.1.0"
1474+dependencies = [
1475+ "knot-jit",
1476+ "knot-runtime",
1477+ "libc",
1478+]
1479+
1480+[[package]]
1481+name = "knot-compiler"
1482+version = "0.1.0"
1483+dependencies = [
1484+ "blake3",
1485+ "cranelift-codegen",
1486+ "cranelift-frontend",
1487+ "cranelift-jit",
1488+ "cranelift-module",
1489+ "cranelift-native",
1490+ "cranelift-object",
1491+ "gimli",
1492+ "indexmap",
1493+ "knot",
1494+ "knot-descriptor",
1495+ "knot-runtime",
1496+ "libc",
1497+ "object",
1498+ "target-lexicon",
1499+ "z3",
1500+]
1501+
1502+[[package]]
1503+name = "knot-descriptor"
1504+version = "0.1.0"
1505+
1506+[[package]]
1507+name = "knot-jit"
1508+version = "0.1.0"
1509+dependencies = [
1510+ "cranelift-codegen",
1511+ "cranelift-frontend",
1512+ "cranelift-jit",
1513+ "cranelift-module",
1514+ "cranelift-native",
1515+ "knot",
1516+ "knot-compile-rt",
1517+ "knot-compiler",
1518+ "knot-runtime",
1519+ "libc",
1520+]
1521+
1522+[[package]]
1523+name = "knot-lsp"
1524+version = "0.1.0"
1525+dependencies = [
1526+ "crossbeam-channel",
1527+ "knot",
1528+ "knot-compiler",
1529+ "lsp-server",
1530+ "lsp-types",
1531+ "serde",
1532+ "serde_json",
1533+]
1534+
1535+[[package]]
1536+name = "knot-runtime"
1537+version = "0.1.0"
1538+dependencies = [
1539+ "blake3",
1540+ "chacha20poly1305",
1541+ "crossterm",
1542+ "dashmap",
1543+ "ed25519-dalek",
1544+ "getrandom 0.4.2",
1545+ "knot-descriptor",
1546+ "libc",
1547+ "ratatui",
1548+ "rusqlite",
1549+ "serde_json",
1550+ "tiny_http",
1551+ "unicode-width",
1552+ "ureq",
1553+ "x25519-dalek",
1554+]
1555+
1556+[[package]]
1557+name = "lab"
1558+version = "0.11.0"
1559+source = "registry+https://github.com/rust-lang/crates.io-index"
1560+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
1561+
1562+[[package]]
1563+name = "lazy_static"
1564+version = "1.5.0"
1565+source = "registry+https://github.com/rust-lang/crates.io-index"
1566+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1567+
1568+[[package]]
1569+name = "leb128fmt"
1570+version = "0.1.0"
1571+source = "registry+https://github.com/rust-lang/crates.io-index"
1572+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
1573+
1574+[[package]]
1575+name = "libbz2-rs-sys"
1576+version = "0.2.5"
1577+source = "registry+https://github.com/rust-lang/crates.io-index"
1578+checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
1579+
1580+[[package]]
1581+name = "libc"
1582+version = "0.2.185"
1583+source = "registry+https://github.com/rust-lang/crates.io-index"
1584+checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
1585+
1586+[[package]]
1587+name = "libm"
1588+version = "0.2.16"
1589+source = "registry+https://github.com/rust-lang/crates.io-index"
1590+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
1591+
1592+[[package]]
1593+name = "libsqlite3-sys"
1594+version = "0.37.0"
1595+source = "registry+https://github.com/rust-lang/crates.io-index"
1596+checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
1597+dependencies = [
1598+ "cc",
1599+ "pkg-config",
1600+ "vcpkg",
1601+]
1602+
1603+[[package]]
1604+name = "line-clipping"
1605+version = "0.3.7"
1606+source = "registry+https://github.com/rust-lang/crates.io-index"
1607+checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8"
1608+dependencies = [
1609+ "bitflags 2.11.1",
1610+]
1611+
1612+[[package]]
1613+name = "linux-raw-sys"
1614+version = "0.12.1"
1615+source = "registry+https://github.com/rust-lang/crates.io-index"
1616+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
1617+
1618+[[package]]
1619+name = "litemap"
1620+version = "0.8.2"
1621+source = "registry+https://github.com/rust-lang/crates.io-index"
1622+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
1623+
1624+[[package]]
1625+name = "litrs"
1626+version = "1.0.0"
1627+source = "registry+https://github.com/rust-lang/crates.io-index"
1628+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
1629+
1630+[[package]]
1631+name = "lock_api"
1632+version = "0.4.14"
1633+source = "registry+https://github.com/rust-lang/crates.io-index"
1634+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
1635+dependencies = [
1636+ "scopeguard",
1637+]
1638+
1639+[[package]]
1640+name = "log"
1641+version = "0.4.29"
1642+source = "registry+https://github.com/rust-lang/crates.io-index"
1643+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
1644+
1645+[[package]]
1646+name = "lru"
1647+version = "0.16.4"
1648+source = "registry+https://github.com/rust-lang/crates.io-index"
1649+checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
1650+dependencies = [
1651+ "hashbrown 0.16.1",
1652+]
1653+
1654+[[package]]
1655+name = "lru-slab"
1656+version = "0.1.2"
1657+source = "registry+https://github.com/rust-lang/crates.io-index"
1658+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
1659+
1660+[[package]]
1661+name = "lsp-server"
1662+version = "0.7.9"
1663+source = "registry+https://github.com/rust-lang/crates.io-index"
1664+checksum = "7d6ada348dbc2703cbe7637b2dda05cff84d3da2819c24abcb305dd613e0ba2e"
1665+dependencies = [
1666+ "crossbeam-channel",
1667+ "log",
1668+ "serde",
1669+ "serde_derive",
1670+ "serde_json",
1671+]
1672+
1673+[[package]]
1674+name = "lsp-types"
1675+version = "0.97.0"
1676+source = "registry+https://github.com/rust-lang/crates.io-index"
1677+checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
1678+dependencies = [
1679+ "bitflags 1.3.2",
1680+ "fluent-uri",
1681+ "serde",
1682+ "serde_json",
1683+ "serde_repr",
1684+]
1685+
1686+[[package]]
1687+name = "lzma-rust2"
1688+version = "0.16.5"
1689+source = "registry+https://github.com/rust-lang/crates.io-index"
1690+checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b"
1691+dependencies = [
1692+ "sha2 0.11.0",
1693+]
1694+
1695+[[package]]
1696+name = "mac_address"
1697+version = "1.1.8"
1698+source = "registry+https://github.com/rust-lang/crates.io-index"
1699+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
1700+dependencies = [
1701+ "nix",
1702+ "winapi",
1703+]
1704+
1705+[[package]]
1706+name = "mach2"
1707+version = "0.4.3"
1708+source = "registry+https://github.com/rust-lang/crates.io-index"
1709+checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
1710+dependencies = [
1711+ "libc",
1712+]
1713+
1714+[[package]]
1715+name = "memchr"
1716+version = "2.8.0"
1717+source = "registry+https://github.com/rust-lang/crates.io-index"
1718+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1719+
1720+[[package]]
1721+name = "memmem"
1722+version = "0.1.1"
1723+source = "registry+https://github.com/rust-lang/crates.io-index"
1724+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
1725+
1726+[[package]]
1727+name = "memoffset"
1728+version = "0.9.1"
1729+source = "registry+https://github.com/rust-lang/crates.io-index"
1730+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
1731+dependencies = [
1732+ "autocfg",
1733+]
1734+
1735+[[package]]
1736+name = "minimal-lexical"
1737+version = "0.2.1"
1738+source = "registry+https://github.com/rust-lang/crates.io-index"
1739+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1740+
1741+[[package]]
1742+name = "miniz_oxide"
1743+version = "0.8.9"
1744+source = "registry+https://github.com/rust-lang/crates.io-index"
1745+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1746+dependencies = [
1747+ "adler2",
1748+ "simd-adler32",
1749+]
1750+
1751+[[package]]
1752+name = "mio"
1753+version = "1.2.0"
1754+source = "registry+https://github.com/rust-lang/crates.io-index"
1755+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
1756+dependencies = [
1757+ "libc",
1758+ "log",
1759+ "wasi",
1760+ "windows-sys 0.61.2",
1761+]
1762+
1763+[[package]]
1764+name = "nix"
1765+version = "0.29.0"
1766+source = "registry+https://github.com/rust-lang/crates.io-index"
1767+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
1768+dependencies = [
1769+ "bitflags 2.11.1",
1770+ "cfg-if",
1771+ "cfg_aliases",
1772+ "libc",
1773+ "memoffset",
1774+]
1775+
1776+[[package]]
1777+name = "nom"
1778+version = "7.1.3"
1779+source = "registry+https://github.com/rust-lang/crates.io-index"
1780+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1781+dependencies = [
1782+ "memchr",
1783+ "minimal-lexical",
1784+]
1785+
1786+[[package]]
1787+name = "num-conv"
1788+version = "0.2.1"
1789+source = "registry+https://github.com/rust-lang/crates.io-index"
1790+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
1791+
1792+[[package]]
1793+name = "num-derive"
1794+version = "0.4.2"
1795+source = "registry+https://github.com/rust-lang/crates.io-index"
1796+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
1797+dependencies = [
1798+ "proc-macro2",
1799+ "quote",
1800+ "syn 2.0.117",
1801+]
1802+
1803+[[package]]
1804+name = "num-traits"
1805+version = "0.2.19"
1806+source = "registry+https://github.com/rust-lang/crates.io-index"
1807+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1808+dependencies = [
1809+ "autocfg",
1810+]
1811+
1812+[[package]]
1813+name = "num_threads"
1814+version = "0.1.7"
1815+source = "registry+https://github.com/rust-lang/crates.io-index"
1816+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
1817+dependencies = [
1818+ "libc",
1819+]
1820+
1821+[[package]]
1822+name = "object"
1823+version = "0.39.1"
1824+source = "registry+https://github.com/rust-lang/crates.io-index"
1825+checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b"
1826+dependencies = [
1827+ "crc32fast",
1828+ "hashbrown 0.17.0",
1829+ "indexmap",
1830+ "memchr",
1831+]
1832+
1833+[[package]]
1834+name = "once_cell"
1835+version = "1.21.4"
1836+source = "registry+https://github.com/rust-lang/crates.io-index"
1837+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
1838+
1839+[[package]]
1840+name = "opaque-debug"
1841+version = "0.3.1"
1842+source = "registry+https://github.com/rust-lang/crates.io-index"
1843+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
1844+
1845+[[package]]
1846+name = "ordered-float"
1847+version = "4.6.0"
1848+source = "registry+https://github.com/rust-lang/crates.io-index"
1849+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
1850+dependencies = [
1851+ "num-traits",
1852+]
1853+
1854+[[package]]
1855+name = "parking_lot"
1856+version = "0.12.5"
1857+source = "registry+https://github.com/rust-lang/crates.io-index"
1858+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
1859+dependencies = [
1860+ "lock_api",
1861+ "parking_lot_core",
1862+]
1863+
1864+[[package]]
1865+name = "parking_lot_core"
1866+version = "0.9.12"
1867+source = "registry+https://github.com/rust-lang/crates.io-index"
1868+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
1869+dependencies = [
1870+ "cfg-if",
1871+ "libc",
1872+ "redox_syscall",
1873+ "smallvec",
1874+ "windows-link",
1875+]
1876+
1877+[[package]]
1878+name = "pbkdf2"
1879+version = "0.13.0"
1880+source = "registry+https://github.com/rust-lang/crates.io-index"
1881+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
1882+dependencies = [
1883+ "digest 0.11.3",
1884+ "hmac",
1885+]
1886+
1887+[[package]]
1888+name = "percent-encoding"
1889+version = "2.3.2"
1890+source = "registry+https://github.com/rust-lang/crates.io-index"
1891+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1892+
1893+[[package]]
1894+name = "pest"
1895+version = "2.8.6"
1896+source = "registry+https://github.com/rust-lang/crates.io-index"
1897+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
1898+dependencies = [
1899+ "memchr",
1900+ "ucd-trie",
1901+]
1902+
1903+[[package]]
1904+name = "pest_derive"
1905+version = "2.8.6"
1906+source = "registry+https://github.com/rust-lang/crates.io-index"
1907+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
1908+dependencies = [
1909+ "pest",
1910+ "pest_generator",
1911+]
1912+
1913+[[package]]
1914+name = "pest_generator"
1915+version = "2.8.6"
1916+source = "registry+https://github.com/rust-lang/crates.io-index"
1917+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
1918+dependencies = [
1919+ "pest",
1920+ "pest_meta",
1921+ "proc-macro2",
1922+ "quote",
1923+ "syn 2.0.117",
1924+]
1925+
1926+[[package]]
1927+name = "pest_meta"
1928+version = "2.8.6"
1929+source = "registry+https://github.com/rust-lang/crates.io-index"
1930+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
1931+dependencies = [
1932+ "pest",
1933+ "sha2 0.10.9",
1934+]
1935+
1936+[[package]]
1937+name = "phf"
1938+version = "0.11.3"
1939+source = "registry+https://github.com/rust-lang/crates.io-index"
1940+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
1941+dependencies = [
1942+ "phf_macros",
1943+ "phf_shared",
1944+]
1945+
1946+[[package]]
1947+name = "phf_codegen"
1948+version = "0.11.3"
1949+source = "registry+https://github.com/rust-lang/crates.io-index"
1950+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
1951+dependencies = [
1952+ "phf_generator",
1953+ "phf_shared",
1954+]
1955+
1956+[[package]]
1957+name = "phf_generator"
1958+version = "0.11.3"
1959+source = "registry+https://github.com/rust-lang/crates.io-index"
1960+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
1961+dependencies = [
1962+ "phf_shared",
1963+ "rand 0.8.6",
1964+]
1965+
1966+[[package]]
1967+name = "phf_macros"
1968+version = "0.11.3"
1969+source = "registry+https://github.com/rust-lang/crates.io-index"
1970+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
1971+dependencies = [
1972+ "phf_generator",
1973+ "phf_shared",
1974+ "proc-macro2",
1975+ "quote",
1976+ "syn 2.0.117",
1977+]
1978+
1979+[[package]]
1980+name = "phf_shared"
1981+version = "0.11.3"
1982+source = "registry+https://github.com/rust-lang/crates.io-index"
1983+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
1984+dependencies = [
1985+ "siphasher",
1986+]
1987+
1988+[[package]]
1989+name = "pin-project-lite"
1990+version = "0.2.17"
1991+source = "registry+https://github.com/rust-lang/crates.io-index"
1992+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
1993+
1994+[[package]]
1995+name = "pkg-config"
1996+version = "0.3.33"
1997+source = "registry+https://github.com/rust-lang/crates.io-index"
1998+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
1999+
2000+[[package]]
2001+name = "poly1305"
2002+version = "0.8.0"
2003+source = "registry+https://github.com/rust-lang/crates.io-index"
2004+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
2005+dependencies = [
2006+ "cpufeatures 0.2.17",
2007+ "opaque-debug",
2008+ "universal-hash",
2009+]
2010+
2011+[[package]]
2012+name = "portable-atomic"
2013+version = "1.13.1"
2014+source = "registry+https://github.com/rust-lang/crates.io-index"
2015+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
2016+
2017+[[package]]
2018+name = "potential_utf"
2019+version = "0.1.5"
2020+source = "registry+https://github.com/rust-lang/crates.io-index"
2021+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
2022+dependencies = [
2023+ "zerovec",
2024+]
2025+
2026+[[package]]
2027+name = "powerfmt"
2028+version = "0.2.0"
2029+source = "registry+https://github.com/rust-lang/crates.io-index"
2030+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
2031+
2032+[[package]]
2033+name = "ppmd-rust"
2034+version = "1.4.0"
2035+source = "registry+https://github.com/rust-lang/crates.io-index"
2036+checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
2037+
2038+[[package]]
2039+name = "prettyplease"
2040+version = "0.2.37"
2041+source = "registry+https://github.com/rust-lang/crates.io-index"
2042+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
2043+dependencies = [
2044+ "proc-macro2",
2045+ "syn 2.0.117",
2046+]
2047+
2048+[[package]]
2049+name = "proc-macro2"
2050+version = "1.0.106"
2051+source = "registry+https://github.com/rust-lang/crates.io-index"
2052+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
2053+dependencies = [
2054+ "unicode-ident",
2055+]
2056+
2057+[[package]]
2058+name = "quinn"
2059+version = "0.11.11"
2060+source = "registry+https://github.com/rust-lang/crates.io-index"
2061+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
2062+dependencies = [
2063+ "bytes",
2064+ "cfg_aliases",
2065+ "pin-project-lite",
2066+ "quinn-proto",
2067+ "quinn-udp",
2068+ "rustc-hash",
2069+ "rustls",
2070+ "socket2",
2071+ "thiserror 2.0.18",
2072+ "tokio",
2073+ "tracing",
2074+ "web-time",
2075+]
2076+
2077+[[package]]
2078+name = "quinn-proto"
2079+version = "0.11.16"
2080+source = "registry+https://github.com/rust-lang/crates.io-index"
2081+checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
2082+dependencies = [
2083+ "bytes",
2084+ "getrandom 0.4.2",
2085+ "lru-slab",
2086+ "rand 0.10.2",
2087+ "rand_pcg",
2088+ "ring",
2089+ "rustc-hash",
2090+ "rustls",
2091+ "rustls-pki-types",
2092+ "slab",
2093+ "thiserror 2.0.18",
2094+ "tinyvec",
2095+ "tracing",
2096+ "web-time",
2097+]
2098+
2099+[[package]]
2100+name = "quinn-udp"
2101+version = "0.5.15"
2102+source = "registry+https://github.com/rust-lang/crates.io-index"
2103+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
2104+dependencies = [
2105+ "cfg_aliases",
2106+ "libc",
2107+ "once_cell",
2108+ "socket2",
2109+ "tracing",
2110+ "windows-sys 0.61.2",
2111+]
2112+
2113+[[package]]
2114+name = "quote"
2115+version = "1.0.45"
2116+source = "registry+https://github.com/rust-lang/crates.io-index"
2117+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
2118+dependencies = [
2119+ "proc-macro2",
2120+]
2121+
2122+[[package]]
2123+name = "r-efi"
2124+version = "5.3.0"
2125+source = "registry+https://github.com/rust-lang/crates.io-index"
2126+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
2127+
2128+[[package]]
2129+name = "r-efi"
2130+version = "6.0.0"
2131+source = "registry+https://github.com/rust-lang/crates.io-index"
2132+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
2133+
2134+[[package]]
2135+name = "rand"
2136+version = "0.8.6"
2137+source = "registry+https://github.com/rust-lang/crates.io-index"
2138+checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
2139+dependencies = [
2140+ "rand_core 0.6.4",
2141+]
2142+
2143+[[package]]
2144+name = "rand"
2145+version = "0.10.2"
2146+source = "registry+https://github.com/rust-lang/crates.io-index"
2147+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
2148+dependencies = [
2149+ "chacha20 0.10.1",
2150+ "getrandom 0.4.2",
2151+ "rand_core 0.10.1",
2152+]
2153+
2154+[[package]]
2155+name = "rand_core"
2156+version = "0.6.4"
2157+source = "registry+https://github.com/rust-lang/crates.io-index"
2158+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
2159+dependencies = [
2160+ "getrandom 0.2.17",
2161+]
2162+
2163+[[package]]
2164+name = "rand_core"
2165+version = "0.10.1"
2166+source = "registry+https://github.com/rust-lang/crates.io-index"
2167+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
2168+
2169+[[package]]
2170+name = "rand_pcg"
2171+version = "0.10.2"
2172+source = "registry+https://github.com/rust-lang/crates.io-index"
2173+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
2174+dependencies = [
2175+ "rand_core 0.10.1",
2176+]
2177+
2178+[[package]]
2179+name = "ratatui"
2180+version = "0.30.0"
2181+source = "registry+https://github.com/rust-lang/crates.io-index"
2182+checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
2183+dependencies = [
2184+ "instability",
2185+ "ratatui-core",
2186+ "ratatui-crossterm",
2187+ "ratatui-macros",
2188+ "ratatui-termwiz",
2189+ "ratatui-widgets",
2190+]
2191+
2192+[[package]]
2193+name = "ratatui-core"
2194+version = "0.1.0"
2195+source = "registry+https://github.com/rust-lang/crates.io-index"
2196+checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
2197+dependencies = [
2198+ "bitflags 2.11.1",
2199+ "compact_str",
2200+ "hashbrown 0.16.1",
2201+ "indoc",
2202+ "itertools",
2203+ "kasuari",
2204+ "lru",
2205+ "strum",
2206+ "thiserror 2.0.18",
2207+ "unicode-segmentation",
2208+ "unicode-truncate",
2209+ "unicode-width",
2210+]
2211+
2212+[[package]]
2213+name = "ratatui-crossterm"
2214+version = "0.1.0"
2215+source = "registry+https://github.com/rust-lang/crates.io-index"
2216+checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3"
2217+dependencies = [
2218+ "cfg-if",
2219+ "crossterm",
2220+ "instability",
2221+ "ratatui-core",
2222+]
2223+
2224+[[package]]
2225+name = "ratatui-macros"
2226+version = "0.7.0"
2227+source = "registry+https://github.com/rust-lang/crates.io-index"
2228+checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4"
2229+dependencies = [
2230+ "ratatui-core",
2231+ "ratatui-widgets",
2232+]
2233+
2234+[[package]]
2235+name = "ratatui-termwiz"
2236+version = "0.1.0"
2237+source = "registry+https://github.com/rust-lang/crates.io-index"
2238+checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c"
2239+dependencies = [
2240+ "ratatui-core",
2241+ "termwiz",
2242+]
2243+
2244+[[package]]
2245+name = "ratatui-widgets"
2246+version = "0.3.0"
2247+source = "registry+https://github.com/rust-lang/crates.io-index"
2248+checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
2249+dependencies = [
2250+ "bitflags 2.11.1",
2251+ "hashbrown 0.16.1",
2252+ "indoc",
2253+ "instability",
2254+ "itertools",
2255+ "line-clipping",
2256+ "ratatui-core",
2257+ "strum",
2258+ "time",
2259+ "unicode-segmentation",
2260+ "unicode-width",
2261+]
2262+
2263+[[package]]
2264+name = "redox_syscall"
2265+version = "0.5.18"
2266+source = "registry+https://github.com/rust-lang/crates.io-index"
2267+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
2268+dependencies = [
2269+ "bitflags 2.11.1",
2270+]
2271+
2272+[[package]]
2273+name = "regalloc2"
2274+version = "0.15.1"
2275+source = "registry+https://github.com/rust-lang/crates.io-index"
2276+checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186"
2277+dependencies = [
2278+ "allocator-api2",
2279+ "bumpalo",
2280+ "hashbrown 0.17.0",
2281+ "log",
2282+ "rustc-hash",
2283+ "smallvec",
2284+]
2285+
2286+[[package]]
2287+name = "regex"
2288+version = "1.12.3"
2289+source = "registry+https://github.com/rust-lang/crates.io-index"
2290+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
2291+dependencies = [
2292+ "aho-corasick",
2293+ "memchr",
2294+ "regex-automata",
2295+ "regex-syntax",
2296+]
2297+
2298+[[package]]
2299+name = "regex-automata"
2300+version = "0.4.14"
2301+source = "registry+https://github.com/rust-lang/crates.io-index"
2302+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
2303+dependencies = [
2304+ "aho-corasick",
2305+ "memchr",
2306+ "regex-syntax",
2307+]
2308+
2309+[[package]]
2310+name = "regex-syntax"
2311+version = "0.8.10"
2312+source = "registry+https://github.com/rust-lang/crates.io-index"
2313+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
2314+
2315+[[package]]
2316+name = "region"
2317+version = "3.0.2"
2318+source = "registry+https://github.com/rust-lang/crates.io-index"
2319+checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7"
2320+dependencies = [
2321+ "bitflags 1.3.2",
2322+ "libc",
2323+ "mach2",
2324+ "windows-sys 0.52.0",
2325+]
2326+
2327+[[package]]
2328+name = "reqwest"
2329+version = "0.12.28"
2330+source = "registry+https://github.com/rust-lang/crates.io-index"
2331+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
2332+dependencies = [
2333+ "base64",
2334+ "bytes",
2335+ "futures-channel",
2336+ "futures-core",
2337+ "futures-util",
2338+ "http",
2339+ "http-body",
2340+ "http-body-util",
2341+ "hyper",
2342+ "hyper-rustls",
2343+ "hyper-util",
2344+ "js-sys",
2345+ "log",
2346+ "percent-encoding",
2347+ "pin-project-lite",
2348+ "quinn",
2349+ "rustls",
2350+ "rustls-pki-types",
2351+ "serde",
2352+ "serde_json",
2353+ "serde_urlencoded",
2354+ "sync_wrapper",
2355+ "tokio",
2356+ "tokio-rustls",
2357+ "tower",
2358+ "tower-http",
2359+ "tower-service",
2360+ "url",
2361+ "wasm-bindgen",
2362+ "wasm-bindgen-futures",
2363+ "web-sys",
2364+ "webpki-roots",
2365+]
2366+
2367+[[package]]
2368+name = "ring"
2369+version = "0.17.14"
2370+source = "registry+https://github.com/rust-lang/crates.io-index"
2371+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
2372+dependencies = [
2373+ "cc",
2374+ "cfg-if",
2375+ "getrandom 0.2.17",
2376+ "libc",
2377+ "untrusted",
2378+ "windows-sys 0.52.0",
2379+]
2380+
2381+[[package]]
2382+name = "rsqlite-vfs"
2383+version = "0.1.0"
2384+source = "registry+https://github.com/rust-lang/crates.io-index"
2385+checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
2386+dependencies = [
2387+ "hashbrown 0.16.1",
2388+ "thiserror 2.0.18",
2389+]
2390+
2391+[[package]]
2392+name = "rusqlite"
2393+version = "0.39.0"
2394+source = "registry+https://github.com/rust-lang/crates.io-index"
2395+checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
2396+dependencies = [
2397+ "bitflags 2.11.1",
2398+ "fallible-iterator",
2399+ "fallible-streaming-iterator",
2400+ "hashlink",
2401+ "libsqlite3-sys",
2402+ "smallvec",
2403+ "sqlite-wasm-rs",
2404+]
2405+
2406+[[package]]
2407+name = "rustc-hash"
2408+version = "2.1.2"
2409+source = "registry+https://github.com/rust-lang/crates.io-index"
2410+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
2411+
2412+[[package]]
2413+name = "rustc_version"
2414+version = "0.4.1"
2415+source = "registry+https://github.com/rust-lang/crates.io-index"
2416+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2417+dependencies = [
2418+ "semver",
2419+]
2420+
2421+[[package]]
2422+name = "rustix"
2423+version = "1.1.4"
2424+source = "registry+https://github.com/rust-lang/crates.io-index"
2425+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
2426+dependencies = [
2427+ "bitflags 2.11.1",
2428+ "errno",
2429+ "libc",
2430+ "linux-raw-sys",
2431+ "windows-sys 0.61.2",
2432+]
2433+
2434+[[package]]
2435+name = "rustls"
2436+version = "0.23.38"
2437+source = "registry+https://github.com/rust-lang/crates.io-index"
2438+checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
2439+dependencies = [
2440+ "log",
2441+ "once_cell",
2442+ "ring",
2443+ "rustls-pki-types",
2444+ "rustls-webpki",
2445+ "subtle",
2446+ "zeroize",
2447+]
2448+
2449+[[package]]
2450+name = "rustls-pki-types"
2451+version = "1.14.0"
2452+source = "registry+https://github.com/rust-lang/crates.io-index"
2453+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
2454+dependencies = [
2455+ "web-time",
2456+ "zeroize",
2457+]
2458+
2459+[[package]]
2460+name = "rustls-webpki"
2461+version = "0.103.13"
2462+source = "registry+https://github.com/rust-lang/crates.io-index"
2463+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
2464+dependencies = [
2465+ "ring",
2466+ "rustls-pki-types",
2467+ "untrusted",
2468+]
2469+
2470+[[package]]
2471+name = "rustversion"
2472+version = "1.0.22"
2473+source = "registry+https://github.com/rust-lang/crates.io-index"
2474+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
2475+
2476+[[package]]
2477+name = "ryu"
2478+version = "1.0.23"
2479+source = "registry+https://github.com/rust-lang/crates.io-index"
2480+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
2481+
2482+[[package]]
2483+name = "scopeguard"
2484+version = "1.2.0"
2485+source = "registry+https://github.com/rust-lang/crates.io-index"
2486+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
2487+
2488+[[package]]
2489+name = "semver"
2490+version = "1.0.28"
2491+source = "registry+https://github.com/rust-lang/crates.io-index"
2492+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
2493+
2494+[[package]]
2495+name = "serde"
2496+version = "1.0.228"
2497+source = "registry+https://github.com/rust-lang/crates.io-index"
2498+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
2499+dependencies = [
2500+ "serde_core",
2501+ "serde_derive",
2502+]
2503+
2504+[[package]]
2505+name = "serde_core"
2506+version = "1.0.228"
2507+source = "registry+https://github.com/rust-lang/crates.io-index"
2508+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
2509+dependencies = [
2510+ "serde_derive",
2511+]
2512+
2513+[[package]]
2514+name = "serde_derive"
2515+version = "1.0.228"
2516+source = "registry+https://github.com/rust-lang/crates.io-index"
2517+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
2518+dependencies = [
2519+ "proc-macro2",
2520+ "quote",
2521+ "syn 2.0.117",
2522+]
2523+
2524+[[package]]
2525+name = "serde_json"
2526+version = "1.0.149"
2527+source = "registry+https://github.com/rust-lang/crates.io-index"
2528+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
2529+dependencies = [
2530+ "itoa",
2531+ "memchr",
2532+ "serde",
2533+ "serde_core",
2534+ "zmij",
2535+]
2536+
2537+[[package]]
2538+name = "serde_repr"
2539+version = "0.1.20"
2540+source = "registry+https://github.com/rust-lang/crates.io-index"
2541+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
2542+dependencies = [
2543+ "proc-macro2",
2544+ "quote",
2545+ "syn 2.0.117",
2546+]
2547+
2548+[[package]]
2549+name = "serde_urlencoded"
2550+version = "0.7.1"
2551+source = "registry+https://github.com/rust-lang/crates.io-index"
2552+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
2553+dependencies = [
2554+ "form_urlencoded",
2555+ "itoa",
2556+ "ryu",
2557+ "serde",
2558+]
2559+
2560+[[package]]
2561+name = "sha1"
2562+version = "0.11.0"
2563+source = "registry+https://github.com/rust-lang/crates.io-index"
2564+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
2565+dependencies = [
2566+ "cfg-if",
2567+ "cpufeatures 0.3.0",
2568+ "digest 0.11.3",
2569+]
2570+
2571+[[package]]
2572+name = "sha2"
2573+version = "0.10.9"
2574+source = "registry+https://github.com/rust-lang/crates.io-index"
2575+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
2576+dependencies = [
2577+ "cfg-if",
2578+ "cpufeatures 0.2.17",
2579+ "digest 0.10.7",
2580+]
2581+
2582+[[package]]
2583+name = "sha2"
2584+version = "0.11.0"
2585+source = "registry+https://github.com/rust-lang/crates.io-index"
2586+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
2587+dependencies = [
2588+ "cfg-if",
2589+ "cpufeatures 0.3.0",
2590+ "digest 0.11.3",
2591+]
2592+
2593+[[package]]
2594+name = "shlex"
2595+version = "1.3.0"
2596+source = "registry+https://github.com/rust-lang/crates.io-index"
2597+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
2598+
2599+[[package]]
2600+name = "signal-hook"
2601+version = "0.3.18"
2602+source = "registry+https://github.com/rust-lang/crates.io-index"
2603+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
2604+dependencies = [
2605+ "libc",
2606+ "signal-hook-registry",
2607+]
2608+
2609+[[package]]
2610+name = "signal-hook-mio"
2611+version = "0.2.5"
2612+source = "registry+https://github.com/rust-lang/crates.io-index"
2613+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
2614+dependencies = [
2615+ "libc",
2616+ "mio",
2617+ "signal-hook",
2618+]
2619+
2620+[[package]]
2621+name = "signal-hook-registry"
2622+version = "1.4.8"
2623+source = "registry+https://github.com/rust-lang/crates.io-index"
2624+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
2625+dependencies = [
2626+ "errno",
2627+ "libc",
2628+]
2629+
2630+[[package]]
2631+name = "signature"
2632+version = "2.2.0"
2633+source = "registry+https://github.com/rust-lang/crates.io-index"
2634+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
2635+
2636+[[package]]
2637+name = "simd-adler32"
2638+version = "0.3.9"
2639+source = "registry+https://github.com/rust-lang/crates.io-index"
2640+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
2641+
2642+[[package]]
2643+name = "siphasher"
2644+version = "1.0.2"
2645+source = "registry+https://github.com/rust-lang/crates.io-index"
2646+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
2647+
2648+[[package]]
2649+name = "slab"
2650+version = "0.4.12"
2651+source = "registry+https://github.com/rust-lang/crates.io-index"
2652+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
2653+
2654+[[package]]
2655+name = "smallvec"
2656+version = "1.15.1"
2657+source = "registry+https://github.com/rust-lang/crates.io-index"
2658+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
2659+
2660+[[package]]
2661+name = "socket2"
2662+version = "0.6.5"
2663+source = "registry+https://github.com/rust-lang/crates.io-index"
2664+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
2665+dependencies = [
2666+ "libc",
2667+ "windows-sys 0.61.2",
2668+]
2669+
2670+[[package]]
2671+name = "sqlite-wasm-rs"
2672+version = "0.5.3"
2673+source = "registry+https://github.com/rust-lang/crates.io-index"
2674+checksum = "1b2c760607300407ddeaee518acf28c795661b7108c75421303dbefb237d3a36"
2675+dependencies = [
2676+ "cc",
2677+ "js-sys",
2678+ "rsqlite-vfs",
2679+ "wasm-bindgen",
2680+]
2681+
2682+[[package]]
2683+name = "stable_deref_trait"
2684+version = "1.2.1"
2685+source = "registry+https://github.com/rust-lang/crates.io-index"
2686+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
2687+
2688+[[package]]
2689+name = "static_assertions"
2690+version = "1.1.0"
2691+source = "registry+https://github.com/rust-lang/crates.io-index"
2692+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
2693+
2694+[[package]]
2695+name = "strsim"
2696+version = "0.11.1"
2697+source = "registry+https://github.com/rust-lang/crates.io-index"
2698+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
2699+
2700+[[package]]
2701+name = "strum"
2702+version = "0.27.2"
2703+source = "registry+https://github.com/rust-lang/crates.io-index"
2704+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
2705+dependencies = [
2706+ "strum_macros",
2707+]
2708+
2709+[[package]]
2710+name = "strum_macros"
2711+version = "0.27.2"
2712+source = "registry+https://github.com/rust-lang/crates.io-index"
2713+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
2714+dependencies = [
2715+ "heck",
2716+ "proc-macro2",
2717+ "quote",
2718+ "syn 2.0.117",
2719+]
2720+
2721+[[package]]
2722+name = "subtle"
2723+version = "2.6.1"
2724+source = "registry+https://github.com/rust-lang/crates.io-index"
2725+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
2726+
2727+[[package]]
2728+name = "syn"
2729+version = "1.0.109"
2730+source = "registry+https://github.com/rust-lang/crates.io-index"
2731+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
2732+dependencies = [
2733+ "proc-macro2",
2734+ "quote",
2735+ "unicode-ident",
2736+]
2737+
2738+[[package]]
2739+name = "syn"
2740+version = "2.0.117"
2741+source = "registry+https://github.com/rust-lang/crates.io-index"
2742+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
2743+dependencies = [
2744+ "proc-macro2",
2745+ "quote",
2746+ "unicode-ident",
2747+]
2748+
2749+[[package]]
2750+name = "sync_wrapper"
2751+version = "1.0.2"
2752+source = "registry+https://github.com/rust-lang/crates.io-index"
2753+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
2754+dependencies = [
2755+ "futures-core",
2756+]
2757+
2758+[[package]]
2759+name = "synstructure"
2760+version = "0.13.2"
2761+source = "registry+https://github.com/rust-lang/crates.io-index"
2762+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
2763+dependencies = [
2764+ "proc-macro2",
2765+ "quote",
2766+ "syn 2.0.117",
2767+]
2768+
2769+[[package]]
2770+name = "target-lexicon"
2771+version = "0.13.5"
2772+source = "registry+https://github.com/rust-lang/crates.io-index"
2773+checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
8912774
8922775[[package]]
893−name = "poly1305"
894−version = "0.8.0"
2776+name = "terminfo"
2777+version = "0.9.0"
8952778source = "registry+https://github.com/rust-lang/crates.io-index"
896−checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
2779+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
8972780dependencies = [
898− "cpufeatures",
899− "opaque-debug",
900− "universal-hash",
2781+ "fnv",
2782+ "nom",
2783+ "phf",
2784+ "phf_codegen",
9012785]
9022786
9032787[[package]]
904−name = "proc-macro2"
905−version = "1.0.106"
2788+name = "termios"
2789+version = "0.3.3"
9062790source = "registry+https://github.com/rust-lang/crates.io-index"
907−checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
2791+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
9082792dependencies = [
909− "unicode-ident",
2793+ "libc",
9102794]
9112795
9122796[[package]]
913−name = "quote"
914−version = "1.0.45"
2797+name = "termwiz"
2798+version = "0.23.3"
9152799source = "registry+https://github.com/rust-lang/crates.io-index"
916−checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
2800+checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
9172801dependencies = [
918− "proc-macro2",
2802+ "anyhow",
2803+ "base64",
2804+ "bitflags 2.11.1",
2805+ "fancy-regex",
2806+ "filedescriptor",
2807+ "finl_unicode",
2808+ "fixedbitset",
2809+ "hex",
2810+ "lazy_static",
2811+ "libc",
2812+ "log",
2813+ "memmem",
2814+ "nix",
2815+ "num-derive",
2816+ "num-traits",
2817+ "ordered-float",
2818+ "pest",
2819+ "pest_derive",
2820+ "phf",
2821+ "sha2 0.10.9",
2822+ "signal-hook",
2823+ "siphasher",
2824+ "terminfo",
2825+ "termios",
2826+ "thiserror 1.0.69",
2827+ "ucd-trie",
2828+ "unicode-segmentation",
2829+ "vtparse",
2830+ "wezterm-bidi",
2831+ "wezterm-blob-leases",
2832+ "wezterm-color-types",
2833+ "wezterm-dynamic",
2834+ "wezterm-input-types",
2835+ "winapi",
9192836]
9202837
9212838[[package]]
922−name = "r-efi"
923−version = "5.3.0"
2839+name = "thiserror"
2840+version = "1.0.69"
9242841source = "registry+https://github.com/rust-lang/crates.io-index"
925−checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
2842+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
2843+dependencies = [
2844+ "thiserror-impl 1.0.69",
2845+]
9262846
9272847[[package]]
928−name = "rand_core"
929−version = "0.6.4"
2848+name = "thiserror"
2849+version = "2.0.18"
9302850source = "registry+https://github.com/rust-lang/crates.io-index"
931−checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
2851+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
9322852dependencies = [
933− "getrandom 0.2.17",
2853+ "thiserror-impl 2.0.18",
9342854]
9352855
9362856[[package]]
937−name = "ratatui"
938−version = "0.29.0"
2857+name = "thiserror-impl"
2858+version = "1.0.69"
9392859source = "registry+https://github.com/rust-lang/crates.io-index"
940−checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
2860+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
9412861dependencies = [
942− "bitflags 2.11.0",
943− "cassowary",
944− "compact_str",
945− "crossterm",
946− "indoc",
947− "instability",
948− "itertools",
949− "lru",
950− "paste",
951− "strum",
952− "unicode-segmentation",
953− "unicode-truncate",
954− "unicode-width 0.2.0",
2862+ "proc-macro2",
2863+ "quote",
2864+ "syn 2.0.117",
9552865]
9562866
9572867[[package]]
958−name = "redox_syscall"
959−version = "0.5.18"
2868+name = "thiserror-impl"
2869+version = "2.0.18"
9602870source = "registry+https://github.com/rust-lang/crates.io-index"
961−checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
2871+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
9622872dependencies = [
963− "bitflags 2.11.0",
2873+ "proc-macro2",
2874+ "quote",
2875+ "syn 2.0.117",
9642876]
9652877
9662878[[package]]
967−name = "regalloc2"
968−version = "0.11.2"
2879+name = "time"
2880+version = "0.3.47"
9692881source = "registry+https://github.com/rust-lang/crates.io-index"
970−checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a"
2882+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
9712883dependencies = [
972− "allocator-api2",
973− "bumpalo",
974− "hashbrown 0.15.5",
975− "log",
976− "rustc-hash",
977− "smallvec",
2884+ "deranged",
2885+ "js-sys",
2886+ "libc",
2887+ "num-conv",
2888+ "num_threads",
2889+ "powerfmt",
2890+ "serde_core",
2891+ "time-core",
9782892]
9792893
9802894[[package]]
981−name = "rusqlite"
982−version = "0.34.0"
2895+name = "time-core"
2896+version = "0.1.8"
2897+source = "registry+https://github.com/rust-lang/crates.io-index"
2898+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
2899+
2900+[[package]]
2901+name = "tiny_http"
2902+version = "0.12.0"
9832903source = "registry+https://github.com/rust-lang/crates.io-index"
984−checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
2904+checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
9852905dependencies = [
986− "bitflags 2.11.0",
987− "fallible-iterator",
988− "fallible-streaming-iterator",
989− "hashlink",
990− "libsqlite3-sys",
991− "smallvec",
2906+ "ascii",
2907+ "chunked_transfer",
2908+ "httpdate",
2909+ "log",
9922910]
9932911
9942912[[package]]
995−name = "rustc-hash"
996−version = "2.1.1"
2913+name = "tinystr"
2914+version = "0.8.3"
9972915source = "registry+https://github.com/rust-lang/crates.io-index"
998−checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
2916+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
2917+dependencies = [
2918+ "displaydoc",
2919+ "zerovec",
2920+]
9992921
10002922[[package]]
1001−name = "rustc_version"
1002−version = "0.4.1"
2923+name = "tinyvec"
2924+version = "1.12.0"
10032925source = "registry+https://github.com/rust-lang/crates.io-index"
1004−checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2926+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
10052927dependencies = [
1006− "semver",
2928+ "tinyvec_macros",
10072929]
10082930
10092931[[package]]
1010−name = "rustix"
1011−version = "0.38.44"
2932+name = "tinyvec_macros"
2933+version = "0.1.1"
2934+source = "registry+https://github.com/rust-lang/crates.io-index"
2935+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2936+
2937+[[package]]
2938+name = "tokio"
2939+version = "1.53.1"
10122940source = "registry+https://github.com/rust-lang/crates.io-index"
1013−checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
2941+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
10142942dependencies = [
1015− "bitflags 2.11.0",
1016− "errno",
2943+ "bytes",
10172944 "libc",
1018− "linux-raw-sys",
1019− "windows-sys 0.59.0",
2945+ "mio",
2946+ "pin-project-lite",
2947+ "socket2",
2948+ "windows-sys 0.61.2",
10202949]
10212950
10222951[[package]]
1023−name = "rustversion"
1024−version = "1.0.22"
2952+name = "tokio-rustls"
2953+version = "0.26.4"
10252954source = "registry+https://github.com/rust-lang/crates.io-index"
1026−checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
2955+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
2956+dependencies = [
2957+ "rustls",
2958+ "tokio",
2959+]
10272960
10282961[[package]]
1029−name = "ryu"
1030−version = "1.0.23"
2962+name = "tower"
2963+version = "0.5.3"
10312964source = "registry+https://github.com/rust-lang/crates.io-index"
1032−checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
2965+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
2966+dependencies = [
2967+ "futures-core",
2968+ "futures-util",
2969+ "pin-project-lite",
2970+ "sync_wrapper",
2971+ "tokio",
2972+ "tower-layer",
2973+ "tower-service",
2974+]
10332975
10342976[[package]]
1035−name = "scopeguard"
1036−version = "1.2.0"
2977+name = "tower-http"
2978+version = "0.6.11"
10372979source = "registry+https://github.com/rust-lang/crates.io-index"
1038−checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
2980+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
2981+dependencies = [
2982+ "bitflags 2.11.1",
2983+ "bytes",
2984+ "futures-util",
2985+ "http",
2986+ "http-body",
2987+ "pin-project-lite",
2988+ "tower",
2989+ "tower-layer",
2990+ "tower-service",
2991+ "url",
2992+]
10392993
10402994[[package]]
1041−name = "semver"
1042−version = "1.0.27"
2995+name = "tower-layer"
2996+version = "0.3.3"
10432997source = "registry+https://github.com/rust-lang/crates.io-index"
1044−checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
2998+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
10452999
10463000[[package]]
1047−name = "serde"
1048−version = "1.0.228"
3001+name = "tower-service"
3002+version = "0.3.3"
10493003source = "registry+https://github.com/rust-lang/crates.io-index"
1050−checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
3004+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
3005+
3006+[[package]]
3007+name = "tracing"
3008+version = "0.1.44"
3009+source = "registry+https://github.com/rust-lang/crates.io-index"
3010+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
10513011dependencies = [
1052− "serde_core",
1053− "serde_derive",
3012+ "pin-project-lite",
3013+ "tracing-core",
10543014]
10553015
10563016[[package]]
1057−name = "serde_core"
1058−version = "1.0.228"
3017+name = "tracing-core"
3018+version = "0.1.36"
10593019source = "registry+https://github.com/rust-lang/crates.io-index"
1060−checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
3020+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
10613021dependencies = [
1062− "serde_derive",
3022+ "once_cell",
10633023]
10643024
10653025[[package]]
1066−name = "serde_derive"
1067−version = "1.0.228"
3026+name = "try-lock"
3027+version = "0.2.5"
10683028source = "registry+https://github.com/rust-lang/crates.io-index"
1069−checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
3029+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
3030+
3031+[[package]]
3032+name = "typed-path"
3033+version = "0.12.3"
3034+source = "registry+https://github.com/rust-lang/crates.io-index"
3035+checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
3036+
3037+[[package]]
3038+name = "typenum"
3039+version = "1.20.0"
3040+source = "registry+https://github.com/rust-lang/crates.io-index"
3041+checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
3042+
3043+[[package]]
3044+name = "ucd-trie"
3045+version = "0.1.7"
3046+source = "registry+https://github.com/rust-lang/crates.io-index"
3047+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
3048+
3049+[[package]]
3050+name = "unicode-ident"
3051+version = "1.0.24"
3052+source = "registry+https://github.com/rust-lang/crates.io-index"
3053+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
3054+
3055+[[package]]
3056+name = "unicode-segmentation"
3057+version = "1.13.2"
3058+source = "registry+https://github.com/rust-lang/crates.io-index"
3059+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
3060+
3061+[[package]]
3062+name = "unicode-truncate"
3063+version = "2.0.1"
3064+source = "registry+https://github.com/rust-lang/crates.io-index"
3065+checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
10703066dependencies = [
1071− "proc-macro2",
1072− "quote",
1073− "syn",
3067+ "itertools",
3068+ "unicode-segmentation",
3069+ "unicode-width",
10743070]
10753071
10763072[[package]]
1077−name = "serde_json"
1078−version = "1.0.149"
3073+name = "unicode-width"
3074+version = "0.2.2"
10793075source = "registry+https://github.com/rust-lang/crates.io-index"
1080−checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
3076+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
3077+
3078+[[package]]
3079+name = "unicode-xid"
3080+version = "0.2.6"
3081+source = "registry+https://github.com/rust-lang/crates.io-index"
3082+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
3083+
3084+[[package]]
3085+name = "universal-hash"
3086+version = "0.5.1"
3087+source = "registry+https://github.com/rust-lang/crates.io-index"
3088+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
10813089dependencies = [
1082− "itoa",
1083− "memchr",
1084− "serde",
1085− "serde_core",
1086− "zmij",
3090+ "crypto-common 0.1.7",
3091+ "subtle",
10873092]
10883093
10893094[[package]]
1090−name = "serde_repr"
1091−version = "0.1.20"
3095+name = "untrusted"
3096+version = "0.9.0"
10923097source = "registry+https://github.com/rust-lang/crates.io-index"
1093−checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
3098+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
3099+
3100+[[package]]
3101+name = "ureq"
3102+version = "3.3.0"
3103+source = "registry+https://github.com/rust-lang/crates.io-index"
3104+checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0"
10943105dependencies = [
1095− "proc-macro2",
1096− "quote",
1097− "syn",
3106+ "base64",
3107+ "flate2",
3108+ "log",
3109+ "percent-encoding",
3110+ "rustls",
3111+ "rustls-pki-types",
3112+ "ureq-proto",
3113+ "utf8-zero",
3114+ "webpki-roots",
10983115]
10993116
11003117[[package]]
1101−name = "sha2"
1102−version = "0.10.9"
3118+name = "ureq-proto"
3119+version = "0.6.0"
11033120source = "registry+https://github.com/rust-lang/crates.io-index"
1104−checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
3121+checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
11053122dependencies = [
1106− "cfg-if",
1107− "cpufeatures",
1108− "digest",
3123+ "base64",
3124+ "http",
3125+ "httparse",
3126+ "log",
11093127]
11103128
11113129[[package]]
1112−name = "shlex"
1113−version = "1.3.0"
3130+name = "url"
3131+version = "2.5.8"
11143132source = "registry+https://github.com/rust-lang/crates.io-index"
1115−checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
3133+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
3134+dependencies = [
3135+ "form_urlencoded",
3136+ "idna",
3137+ "percent-encoding",
3138+ "serde",
3139+]
11163140
11173141[[package]]
1118−name = "signal-hook"
1119−version = "0.3.18"
3142+name = "utf8-zero"
3143+version = "0.8.1"
11203144source = "registry+https://github.com/rust-lang/crates.io-index"
1121−checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
3145+checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
3146+
3147+[[package]]
3148+name = "utf8_iter"
3149+version = "1.0.4"
3150+source = "registry+https://github.com/rust-lang/crates.io-index"
3151+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
3152+
3153+[[package]]
3154+name = "utf8parse"
3155+version = "0.2.2"
3156+source = "registry+https://github.com/rust-lang/crates.io-index"
3157+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
3158+
3159+[[package]]
3160+name = "uuid"
3161+version = "1.23.1"
3162+source = "registry+https://github.com/rust-lang/crates.io-index"
3163+checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
11223164dependencies = [
1123− "libc",
1124− "signal-hook-registry",
3165+ "atomic",
3166+ "getrandom 0.4.2",
3167+ "js-sys",
3168+ "wasm-bindgen",
11253169]
11263170
11273171[[package]]
1128−name = "signal-hook-mio"
1129−version = "0.2.5"
3172+name = "vcpkg"
3173+version = "0.2.15"
11303174source = "registry+https://github.com/rust-lang/crates.io-index"
1131−checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
3175+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
3176+
3177+[[package]]
3178+name = "version_check"
3179+version = "0.9.5"
3180+source = "registry+https://github.com/rust-lang/crates.io-index"
3181+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
3182+
3183+[[package]]
3184+name = "vtparse"
3185+version = "0.6.2"
3186+source = "registry+https://github.com/rust-lang/crates.io-index"
3187+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
11323188dependencies = [
1133− "libc",
1134− "mio",
1135− "signal-hook",
3189+ "utf8parse",
11363190]
11373191
11383192[[package]]
1139−name = "signal-hook-registry"
1140−version = "1.4.8"
3193+name = "want"
3194+version = "0.3.1"
11413195source = "registry+https://github.com/rust-lang/crates.io-index"
1142−checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
3196+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
11433197dependencies = [
1144− "errno",
1145− "libc",
3198+ "try-lock",
11463199]
11473200
11483201[[package]]
1149−name = "signature"
1150−version = "2.2.0"
3202+name = "wasi"
3203+version = "0.11.1+wasi-snapshot-preview1"
11513204source = "registry+https://github.com/rust-lang/crates.io-index"
1152−checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
3205+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
11533206
11543207[[package]]
1155−name = "smallvec"
1156−version = "1.15.1"
3208+name = "wasip2"
3209+version = "1.0.3+wasi-0.2.9"
11573210source = "registry+https://github.com/rust-lang/crates.io-index"
1158−checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
3211+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
3212+dependencies = [
3213+ "wit-bindgen 0.57.1",
3214+]
11593215
11603216[[package]]
1161−name = "stable_deref_trait"
1162−version = "1.2.1"
3217+name = "wasip3"
3218+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
11633219source = "registry+https://github.com/rust-lang/crates.io-index"
1164−checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
3220+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
3221+dependencies = [
3222+ "wit-bindgen 0.51.0",
3223+]
11653224
11663225[[package]]
1167−name = "static_assertions"
1168−version = "1.1.0"
3226+name = "wasm-bindgen"
3227+version = "0.2.118"
11693228source = "registry+https://github.com/rust-lang/crates.io-index"
1170−checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
3229+checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
3230+dependencies = [
3231+ "cfg-if",
3232+ "once_cell",
3233+ "rustversion",
3234+ "wasm-bindgen-macro",
3235+ "wasm-bindgen-shared",
3236+]
11713237
11723238[[package]]
1173−name = "strsim"
1174−version = "0.11.1"
3239+name = "wasm-bindgen-futures"
3240+version = "0.4.68"
11753241source = "registry+https://github.com/rust-lang/crates.io-index"
1176−checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
3242+checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
3243+dependencies = [
3244+ "js-sys",
3245+ "wasm-bindgen",
3246+]
11773247
11783248[[package]]
1179−name = "strum"
1180−version = "0.26.3"
3249+name = "wasm-bindgen-macro"
3250+version = "0.2.118"
11813251source = "registry+https://github.com/rust-lang/crates.io-index"
1182−checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
3252+checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
11833253dependencies = [
1184− "strum_macros",
3254+ "quote",
3255+ "wasm-bindgen-macro-support",
11853256]
11863257
11873258[[package]]
1188−name = "strum_macros"
1189−version = "0.26.4"
3259+name = "wasm-bindgen-macro-support"
3260+version = "0.2.118"
11903261source = "registry+https://github.com/rust-lang/crates.io-index"
1191−checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
3262+checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
11923263dependencies = [
1193− "heck",
3264+ "bumpalo",
11943265 "proc-macro2",
11953266 "quote",
1196− "rustversion",
1197− "syn",
3267+ "syn 2.0.117",
3268+ "wasm-bindgen-shared",
11983269]
11993270
12003271[[package]]
1201−name = "subtle"
1202−version = "2.6.1"
3272+name = "wasm-bindgen-shared"
3273+version = "0.2.118"
12033274source = "registry+https://github.com/rust-lang/crates.io-index"
1204−checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
3275+checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
3276+dependencies = [
3277+ "unicode-ident",
3278+]
12053279
12063280[[package]]
1207−name = "syn"
1208−version = "2.0.117"
3281+name = "wasm-encoder"
3282+version = "0.244.0"
12093283source = "registry+https://github.com/rust-lang/crates.io-index"
1210−checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
3284+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
12113285dependencies = [
1212− "proc-macro2",
1213− "quote",
1214− "unicode-ident",
3286+ "leb128fmt",
3287+ "wasmparser",
12153288]
12163289
12173290[[package]]
1218−name = "target-lexicon"
1219−version = "0.13.5"
3291+name = "wasm-metadata"
3292+version = "0.244.0"
12203293source = "registry+https://github.com/rust-lang/crates.io-index"
1221−checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
3294+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
3295+dependencies = [
3296+ "anyhow",
3297+ "indexmap",
3298+ "wasm-encoder",
3299+ "wasmparser",
3300+]
12223301
12233302[[package]]
1224−name = "tiny_http"
1225−version = "0.12.0"
3303+name = "wasmparser"
3304+version = "0.244.0"
12263305source = "registry+https://github.com/rust-lang/crates.io-index"
1227−checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
3306+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
12283307dependencies = [
1229− "ascii",
1230− "chunked_transfer",
1231− "httpdate",
1232− "log",
3308+ "bitflags 2.11.1",
3309+ "hashbrown 0.15.5",
3310+ "indexmap",
3311+ "semver",
12333312]
12343313
12353314[[package]]
1236−name = "typenum"
1237−version = "1.19.0"
3315+name = "wasmtime-internal-core"
3316+version = "44.0.0"
12383317source = "registry+https://github.com/rust-lang/crates.io-index"
1239−checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
3318+checksum = "816a61a75275c6be435131fc625a4f5956daf24d9f9f59443e81cbef228929b3"
3319+dependencies = [
3320+ "hashbrown 0.16.1",
3321+ "libm",
3322+]
12403323
12413324[[package]]
1242−name = "unicode-ident"
1243−version = "1.0.24"
3325+name = "wasmtime-internal-jit-icache-coherence"
3326+version = "44.0.0"
12443327source = "registry+https://github.com/rust-lang/crates.io-index"
1245−checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
3328+checksum = "2fd683a94490bf755d016a09697b0955602c50106b1ded97d16983ab2ded9fed"
3329+dependencies = [
3330+ "cfg-if",
3331+ "libc",
3332+ "wasmtime-internal-core",
3333+ "windows-sys 0.61.2",
3334+]
12463335
12473336[[package]]
1248−name = "unicode-segmentation"
1249−version = "1.12.0"
3337+name = "web-sys"
3338+version = "0.3.95"
12503339source = "registry+https://github.com/rust-lang/crates.io-index"
1251−checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
3340+checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
3341+dependencies = [
3342+ "js-sys",
3343+ "wasm-bindgen",
3344+]
12523345
12533346[[package]]
1254−name = "unicode-truncate"
3347+name = "web-time"
12553348version = "1.1.0"
12563349source = "registry+https://github.com/rust-lang/crates.io-index"
1257−checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
3350+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
12583351dependencies = [
1259− "itertools",
1260− "unicode-segmentation",
1261− "unicode-width 0.1.14",
3352+ "js-sys",
3353+ "wasm-bindgen",
12623354]
12633355
12643356[[package]]
1265−name = "unicode-width"
1266−version = "0.1.14"
3357+name = "webpki-roots"
3358+version = "1.0.7"
12673359source = "registry+https://github.com/rust-lang/crates.io-index"
1268−checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
3360+checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
3361+dependencies = [
3362+ "rustls-pki-types",
3363+]
12693364
12703365[[package]]
1271−name = "unicode-width"
1272−version = "0.2.0"
3366+name = "wezterm-bidi"
3367+version = "0.2.3"
12733368source = "registry+https://github.com/rust-lang/crates.io-index"
1274−checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
3369+checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec"
3370+dependencies = [
3371+ "log",
3372+ "wezterm-dynamic",
3373+]
12753374
12763375[[package]]
1277−name = "universal-hash"
1278−version = "0.5.1"
3376+name = "wezterm-blob-leases"
3377+version = "0.1.1"
12793378source = "registry+https://github.com/rust-lang/crates.io-index"
1280−checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
3379+checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
12813380dependencies = [
1282− "crypto-common",
1283− "subtle",
3381+ "getrandom 0.3.4",
3382+ "mac_address",
3383+ "sha2 0.10.9",
3384+ "thiserror 1.0.69",
3385+ "uuid",
12843386]
12853387
12863388[[package]]
1287−name = "vcpkg"
1288−version = "0.2.15"
3389+name = "wezterm-color-types"
3390+version = "0.3.0"
12893391source = "registry+https://github.com/rust-lang/crates.io-index"
1290−checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
3392+checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
3393+dependencies = [
3394+ "csscolorparser",
3395+ "deltae",
3396+ "lazy_static",
3397+ "wezterm-dynamic",
3398+]
12913399
12923400[[package]]
1293−name = "version_check"
1294−version = "0.9.5"
3401+name = "wezterm-dynamic"
3402+version = "0.2.1"
12953403source = "registry+https://github.com/rust-lang/crates.io-index"
1296−checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
3404+checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac"
3405+dependencies = [
3406+ "log",
3407+ "ordered-float",
3408+ "strsim",
3409+ "thiserror 1.0.69",
3410+ "wezterm-dynamic-derive",
3411+]
12973412
12983413[[package]]
1299−name = "wasi"
1300−version = "0.11.1+wasi-snapshot-preview1"
3414+name = "wezterm-dynamic-derive"
3415+version = "0.1.1"
13013416source = "registry+https://github.com/rust-lang/crates.io-index"
1302−checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3417+checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b"
3418+dependencies = [
3419+ "proc-macro2",
3420+ "quote",
3421+ "syn 1.0.109",
3422+]
13033423
13043424[[package]]
1305−name = "wasip2"
1306−version = "1.0.2+wasi-0.2.9"
3425+name = "wezterm-input-types"
3426+version = "0.1.0"
13073427source = "registry+https://github.com/rust-lang/crates.io-index"
1308−checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
3428+checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e"
13093429dependencies = [
1310− "wit-bindgen",
3430+ "bitflags 1.3.2",
3431+ "euclid",
3432+ "lazy_static",
3433+ "serde",
3434+ "wezterm-dynamic",
13113435]
13123436
13133437[[package]]
@@ -1340,9 +3464,9 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
13403464
13413465[[package]]
13423466name = "windows-sys"
1343−version = "0.59.0"
3467+version = "0.52.0"
13443468source = "registry+https://github.com/rust-lang/crates.io-index"
1345−checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
3469+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
13463470dependencies = [
13473471 "windows-targets",
13483472]
@@ -1425,6 +3549,100 @@ name = "wit-bindgen"
14253549version = "0.51.0"
14263550source = "registry+https://github.com/rust-lang/crates.io-index"
14273551checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
3552+dependencies = [
3553+ "wit-bindgen-rust-macro",
3554+]
3555+
3556+[[package]]
3557+name = "wit-bindgen"
3558+version = "0.57.1"
3559+source = "registry+https://github.com/rust-lang/crates.io-index"
3560+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
3561+
3562+[[package]]
3563+name = "wit-bindgen-core"
3564+version = "0.51.0"
3565+source = "registry+https://github.com/rust-lang/crates.io-index"
3566+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
3567+dependencies = [
3568+ "anyhow",
3569+ "heck",
3570+ "wit-parser",
3571+]
3572+
3573+[[package]]
3574+name = "wit-bindgen-rust"
3575+version = "0.51.0"
3576+source = "registry+https://github.com/rust-lang/crates.io-index"
3577+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
3578+dependencies = [
3579+ "anyhow",
3580+ "heck",
3581+ "indexmap",
3582+ "prettyplease",
3583+ "syn 2.0.117",
3584+ "wasm-metadata",
3585+ "wit-bindgen-core",
3586+ "wit-component",
3587+]
3588+
3589+[[package]]
3590+name = "wit-bindgen-rust-macro"
3591+version = "0.51.0"
3592+source = "registry+https://github.com/rust-lang/crates.io-index"
3593+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
3594+dependencies = [
3595+ "anyhow",
3596+ "prettyplease",
3597+ "proc-macro2",
3598+ "quote",
3599+ "syn 2.0.117",
3600+ "wit-bindgen-core",
3601+ "wit-bindgen-rust",
3602+]
3603+
3604+[[package]]
3605+name = "wit-component"
3606+version = "0.244.0"
3607+source = "registry+https://github.com/rust-lang/crates.io-index"
3608+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
3609+dependencies = [
3610+ "anyhow",
3611+ "bitflags 2.11.1",
3612+ "indexmap",
3613+ "log",
3614+ "serde",
3615+ "serde_derive",
3616+ "serde_json",
3617+ "wasm-encoder",
3618+ "wasm-metadata",
3619+ "wasmparser",
3620+ "wit-parser",
3621+]
3622+
3623+[[package]]
3624+name = "wit-parser"
3625+version = "0.244.0"
3626+source = "registry+https://github.com/rust-lang/crates.io-index"
3627+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
3628+dependencies = [
3629+ "anyhow",
3630+ "id-arena",
3631+ "indexmap",
3632+ "log",
3633+ "semver",
3634+ "serde",
3635+ "serde_derive",
3636+ "serde_json",
3637+ "unicode-xid",
3638+ "wasmparser",
3639+]
3640+
3641+[[package]]
3642+name = "writeable"
3643+version = "0.6.3"
3644+source = "registry+https://github.com/rust-lang/crates.io-index"
3645+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
14283646
14293647[[package]]
14303648name = "x25519-dalek"
@@ -1433,7 +3651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
14333651checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
14343652dependencies = [
14353653 "curve25519-dalek",
1436− "rand_core",
3654+ "rand_core 0.6.4",
14373655 "serde",
14383656 "zeroize",
14393657]
@@ -1444,6 +3662,72 @@ version = "1.0.1"
14443662source = "registry+https://github.com/rust-lang/crates.io-index"
14453663checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
14463664
3665+[[package]]
3666+name = "yoke"
3667+version = "0.8.3"
3668+source = "registry+https://github.com/rust-lang/crates.io-index"
3669+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
3670+dependencies = [
3671+ "stable_deref_trait",
3672+ "yoke-derive",
3673+ "zerofrom",
3674+]
3675+
3676+[[package]]
3677+name = "yoke-derive"
3678+version = "0.8.2"
3679+source = "registry+https://github.com/rust-lang/crates.io-index"
3680+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
3681+dependencies = [
3682+ "proc-macro2",
3683+ "quote",
3684+ "syn 2.0.117",
3685+ "synstructure",
3686+]
3687+
3688+[[package]]
3689+name = "z3"
3690+version = "0.20.2"
3691+source = "registry+https://github.com/rust-lang/crates.io-index"
3692+checksum = "80c4de445f5c9e3013703a6b8a40c80b4a64c925f0a19e7d0a23a7a9b70e854d"
3693+dependencies = [
3694+ "log",
3695+ "z3-sys",
3696+]
3697+
3698+[[package]]
3699+name = "z3-sys"
3700+version = "0.11.0"
3701+source = "registry+https://github.com/rust-lang/crates.io-index"
3702+checksum = "c18b0a91a13522d21b3414847667de2b2056a721a3edcb5b6ee6858352d58db4"
3703+dependencies = [
3704+ "pkg-config",
3705+ "reqwest",
3706+ "serde_json",
3707+ "zip",
3708+]
3709+
3710+[[package]]
3711+name = "zerofrom"
3712+version = "0.1.8"
3713+source = "registry+https://github.com/rust-lang/crates.io-index"
3714+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
3715+dependencies = [
3716+ "zerofrom-derive",
3717+]
3718+
3719+[[package]]
3720+name = "zerofrom-derive"
3721+version = "0.1.7"
3722+source = "registry+https://github.com/rust-lang/crates.io-index"
3723+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
3724+dependencies = [
3725+ "proc-macro2",
3726+ "quote",
3727+ "syn 2.0.117",
3728+ "synstructure",
3729+]
3730+
14473731[[package]]
14483732name = "zeroize"
14493733version = "1.8.2"
@@ -1461,11 +3745,117 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
14613745dependencies = [
14623746 "proc-macro2",
14633747 "quote",
1464− "syn",
3748+ "syn 2.0.117",
3749+]
3750+
3751+[[package]]
3752+name = "zerotrie"
3753+version = "0.2.4"
3754+source = "registry+https://github.com/rust-lang/crates.io-index"
3755+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
3756+dependencies = [
3757+ "displaydoc",
3758+ "yoke",
3759+ "zerofrom",
3760+]
3761+
3762+[[package]]
3763+name = "zerovec"
3764+version = "0.11.6"
3765+source = "registry+https://github.com/rust-lang/crates.io-index"
3766+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
3767+dependencies = [
3768+ "yoke",
3769+ "zerofrom",
3770+ "zerovec-derive",
3771+]
3772+
3773+[[package]]
3774+name = "zerovec-derive"
3775+version = "0.11.3"
3776+source = "registry+https://github.com/rust-lang/crates.io-index"
3777+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
3778+dependencies = [
3779+ "proc-macro2",
3780+ "quote",
3781+ "syn 2.0.117",
3782+]
3783+
3784+[[package]]
3785+name = "zip"
3786+version = "8.6.0"
3787+source = "registry+https://github.com/rust-lang/crates.io-index"
3788+checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
3789+dependencies = [
3790+ "aes",
3791+ "bzip2",
3792+ "constant_time_eq",
3793+ "crc32fast",
3794+ "deflate64",
3795+ "flate2",
3796+ "getrandom 0.4.2",
3797+ "hmac",
3798+ "indexmap",
3799+ "lzma-rust2",
3800+ "memchr",
3801+ "pbkdf2",
3802+ "ppmd-rust",
3803+ "sha1",
3804+ "time",
3805+ "typed-path",
3806+ "zeroize",
3807+ "zopfli",
3808+ "zstd",
14653809]
14663810
3811+[[package]]
3812+name = "zlib-rs"
3813+version = "0.6.6"
3814+source = "registry+https://github.com/rust-lang/crates.io-index"
3815+checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
3816+
14673817[[package]]
14683818name = "zmij"
14693819version = "1.0.21"
14703820source = "registry+https://github.com/rust-lang/crates.io-index"
14713821checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
3822+
3823+[[package]]
3824+name = "zopfli"
3825+version = "0.8.3"
3826+source = "registry+https://github.com/rust-lang/crates.io-index"
3827+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
3828+dependencies = [
3829+ "bumpalo",
3830+ "crc32fast",
3831+ "log",
3832+ "simd-adler32",
3833+]
3834+
3835+[[package]]
3836+name = "zstd"
3837+version = "0.13.3"
3838+source = "registry+https://github.com/rust-lang/crates.io-index"
3839+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
3840+dependencies = [
3841+ "zstd-safe",
3842+]
3843+
3844+[[package]]
3845+name = "zstd-safe"
3846+version = "7.2.4"
3847+source = "registry+https://github.com/rust-lang/crates.io-index"
3848+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
3849+dependencies = [
3850+ "zstd-sys",
3851+]
3852+
3853+[[package]]
3854+name = "zstd-sys"
3855+version = "2.0.16+zstd.1.5.7"
3856+source = "registry+https://github.com/rust-lang/crates.io-index"
3857+checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
3858+dependencies = [
3859+ "cc",
3860+ "pkg-config",
3861+]
Cargo.toml+31−9
@@ -6,19 +6,41 @@ resolver = "3"
66version = "0.1.0"
77edition = "2024"
88
9+# panic = "unwind" (the default) is load-bearing: the runtime's catch_unwind
10+# recovery sites (HTTP worker 500 responses, race-loser cancellation, atomic
11+# rollback on handler panic) depend on it. Unwinding across the FFI boundary
12+# into Cranelift-generated code is sound because (a) every runtime function
13+# callable from generated code is `extern "C-unwind"`, and (b) the compiler
14+# emits .eh_frame unwind info for every generated function (knot-compiler's
15+# unwind.rs), so the system unwinder can walk those frames.
16+
917[workspace.dependencies]
18+libc = "0.2"
1019knot = { path = "crates/knot" }
20+knot-descriptor = { path = "crates/knot-descriptor" }
1121knot-compiler = { path = "crates/knot-compiler" }
1222knot-runtime = { path = "crates/knot-runtime" }
13−cranelift-codegen = "0.116"
14−cranelift-frontend = "0.116"
15−cranelift-module = "0.116"
16−cranelift-object = "0.116"
17−cranelift-native = "0.116"
23+knot-jit = { path = "crates/knot-jit" }
24+knot-compile-rt = { path = "crates/knot-compile-rt" }
25+cranelift-codegen = "0.131"
26+cranelift-frontend = "0.131"
27+cranelift-module = "0.131"
28+cranelift-object = "0.131"
29+cranelift-jit = "0.131"
30+cranelift-native = "0.131"
1831target-lexicon = "0.13"
19−rusqlite = { version = "0.34", features = ["bundled", "collation"] }
32+rusqlite = { version = "0.39", features = ["bundled", "collation"] }
2033tiny_http = "0.12"
21−num-bigint = "0.4"
22−num-traits = "0.2"
23−num-integer = "0.1"
34+ureq = "3"
35+serde_json = "1"
2436ariadne = "0.6"
37+
38+# Strip DEBUG INFO (not symbols) from release binaries, incl. the released
39+# `knot` compiler. Debug info is the dominant size cost and is useless in a
40+# shipped compiler. Symbols are KEPT so panic backtraces stay readable — the
41+# runtime recovers from panics via catch_unwind (see the panic=unwind note
42+# above), and a stripped-of-symbols compiler would make those backtraces
43+# opaque. This is independent of the `-s` the linker adds to PRODUCED knot
44+# binaries (those need --export-dynamic/.dynsym for JIT; this is the compiler).
45+[profile.release]
46+strip = "debuginfo"
DESIGN.mddeleted−1165
@@ -1,1165 +0,0 @@
1−# Knot Language Design
2−
3−Knot is a functional relational programming language. Relations are the primary data structure, computation is pure and functional, and state is automatically persisted.
4−
5−## Core Principles
6−
7−1. **Relations are the data structure** — not lists, not arrays. `[T]` is a typed set of `T` values.
8−2. **Effects are inferred** — the compiler tracks reads and writes. No annotations needed.
9−3. **ADTs are native to relations** — a `[Shape]` holds circles and rects in one relation. The tag is an implementation detail.
10−4. **No keys** — relations are sets. Identity is structural. The runtime handles indexing.
11−5. **State is visible** — source relations (mutable, persisted) are prefixed with `*`, derived relations (read-only) with `&`. Every reference site shows whether you're touching state. No ORM, no SQL.
12−
13−## Data Model
14−
15−### Relations
16−
17−A relation is a typed set of values. Duplicate values cannot exist — it's a set.
18−
19−```knot
20−-- Literal relation (constant — pure, no DB references)
21−names = ["Alice", "Bob", "Carol"]
22−
23−-- Empty relation
24−none = []
25−```
26−
27−### Declarations
28−
29−There are five kinds of top-level declarations:
30−
31−```knot
32−-- Source: stored in DB, mutable via `set`
33−*people : [Person]
34−*orders : [{customer: Text, amount: Int}]
35−
36−-- View: defined by a query over source relations, settable (writes propagate back)
37−*openTodos = do
38− t <- *todos
39− yield {title: t.title, owner: t.owner, priority: t.priority, status: Open {}}
40−
41−-- Constant: a pure expression with no DB references (zero-argument function)
42−maxRetries = 3
43−defaultPriority = Low {}
44−httpCodes = [{code: 200, name: "OK"}, {code: 404, name: "Not Found"}]
45−
46−-- Derived: references source relations, recomputed on access (read-only)
47−&seniors = *people |> filter (\p -> p.age > 65)
48−
49−-- Type alias: just a name for a type
50−type Person = {name: Text, age: Int}
51−```
52−
53−The prefix determines mutability, the presence of a body determines whether it's stored or computed:
54−
55−| Declaration | Category | How the compiler knows |
56−|---|---|---|
57−| `*foo : [T]` | Source (persisted) | `*` prefix, no body |
58−| `*foo = expr` | View (read/write) | `*` prefix, has body |
59−| `&foo = expr` | Derived (read-only) | `&` prefix, has body |
60−| `foo = expr` (pure) | Constant | No prefix, no effects inferred |
61−| `type Foo = T` | Type alias | `type` keyword |
62−
63−### ADTs as Relation Schemas
64−
65−Every ADT defines a relation schema. Each constructor is a record variant. A `[T]` holds values of any variant of `T` in the same relation.
66−
67−```knot
68−data Shape
69− = Circle {radius: Float}
70− | Rect {width: Float, height: Float}
71−
72−*shapes : [Shape] -- source (no body)
73−```
74−
75−Single-variant types are equivalent to bare records:
76−
77−```knot
78−-- These are the same:
79−*people : [{name: Text, age: Int}]
80−*people : [Person]
81−```
82−
83−Constructors are the interface for building values, inserting, and querying. The tag/discriminator is an internal storage detail that never appears in the language.
84−
85−Every constructor requires `{}` — even those with no fields. This keeps the syntax uniform: a constructor is always `Name {fields}`, whether it has fields or not. There is no distinction between "a constructor" and "a constructor applied to a record."
86−
87−```knot
88−data Bool = True {} | False {}
89−data Maybe a = Nothing {} | Just {value: a}
90−data List a = Nil {} | Cons {head: a, tail: List a}
91−```
92−
93−### ADTs, Records, and Relations Compose Freely
94−
95−Any type can be a column type — including sum types, nested records, and nested relations.
96−
97−```knot
98−data Priority = Low {} | Medium {} | High {} | Critical {}
99−
100−data Status
101− = Open {}
102− | InProgress {assignee: Text}
103− | Resolved {resolution: Text}
104− | Blocked {reason: Text, dependencies: [{title: Text}]}
105−
106−*tickets : [{title: Text, priority: Priority, status: Status}]
107−```
108−
109−### Nested Relations
110−
111−A field can hold a `[]` — a set nested inside a row. This departs from SQL's first normal form restriction.
112−
113−```knot
114−type Person = {name: Text, age: Int}
115−
116−*teams : [{name: Text, members: [Person]}]
117−```
118−
119−#### Querying into Nested Relations
120−
121−Bind through multiple levels with `<-`:
122−
123−```knot
124−-- All people across all teams
125−&allMembers = do
126− t <- *teams
127− m <- t.members
128− yield {team: t.name, member: m.name}
129−
130−-- Engineers on large teams
131−&engineers = do
132− t <- *teams
133− where (count t.members) > 10
134− m <- t.members
135− where m.role == "engineer"
136− yield {team: t.name, name: m.name}
137−```
138−
139−#### Updating Nested Relations
140−
141−Use `set` with a `map` over the outer relation that transforms the nested relation:
142−
143−```knot
144−-- Add a member to a team
145−addMember = \teamName person ->
146− set *teams = do
147− t <- *teams
148− yield (if t.name == teamName
149− then {t | members: union t.members [person]}
150− else t)
151−
152−-- Remove a member from all teams
153−removePerson = \personName ->
154− set *teams = do
155− t <- *teams
156− yield {t | members: do
157− m <- t.members
158− where m.name != personName
159− yield m}
160−```
161−
162−#### Flattening and Nesting
163−
164−Convert between flat and nested representations:
165−
166−```knot
167−-- Flat relation
168−type FlatMembership = {team: Text, member: Text, age: Int}
169−*memberships : [FlatMembership]
170−
171−-- Nest: group a flat relation into nested structure
172−&nested = do
173− t <- do m <- *memberships; yield m.team
174− yield {name: t, members: do
175− m <- *memberships
176− where m.team == t
177− yield {name: m.member, age: m.age}}
178−
179−-- Flatten: expand nested relation into flat rows
180−&flat = do
181− t <- *teams
182− m <- t.members
183− yield {team: t.name, member: m.name, age: m.age}
184−```
185−
186−#### Deeply Nested Relations
187−
188−Nesting is arbitrarily deep:
189−
190−```knot
191−type Course = {name: Text, students: [{name: Text, grades: [{subject: Text, score: Int}]}]}
192−
193−*departments : [{name: Text, courses: [Course]}]
194−
195−-- Find all failing grades across all departments
196−&failing = do
197− d <- *departments
198− c <- d.courses
199− s <- c.students
200− g <- s.grades
201− where g.score < 50
202− yield {dept: d.name, course: c.name, student: s.name, subject: g.subject, score: g.score}
203−```
204−
205−## Primitives
206−
207−### Trait Hierarchy
208−
209−`do` syntax is not hardcoded to `[]`. It desugars to trait methods, so any type implementing `Monad` gets `do`/`yield`/`<-` for free. This requires higher-kinded types in the type system.
210−
211−```knot
212−trait Functor (f : Type -> Type) where
213− map : (a -> b) -> f a -> f b
214−
215−trait Functor f => Applicative (f : Type -> Type) where
216− yield : a -> f a
217− ap : f (a -> b) -> f a -> f b
218−
219−trait Applicative m => Monad (m : Type -> Type) where
220− bind : (a -> m b) -> m a -> m b
221−
222−trait Applicative f => Alternative (f : Type -> Type) where
223− empty : f a
224− alt : f a -> f a -> f a
225−
226−trait Foldable (t : Type -> Type) where
227− fold : (b -> a -> b) -> b -> t a -> b
228−```
229−
230−### `do` Desugaring
231−
232−`do` syntax works for any `Monad`:
233−
234−- `x <- expr` desugars to `bind (\x -> ...) expr`
235−- `yield x` is `Applicative.yield`
236−- `where cond` desugars to `if cond then yield {} else empty` (requires `Alternative`)
237−
238−```knot
239−-- do with [] (relation comprehension)
240−&richEmployees = do
241− e <- *employees
242− d <- *departments
243− where e.dept == d.name
244− yield {e.name, e.salary, d.budget}
245−
246−-- do with Maybe
247−safeDivide = \a b -> do
248− where b != 0
249− yield (a / b)
250−
251−-- do with Result
252−parseConfig = \text -> do
253− json <- parseJson text
254− name <- getField "name" json
255− yield name
256−```
257−
258−### `[]` Trait Implementations
259−
260−`[]` implements the full hierarchy:
261−
262−```knot
263−impl Functor [] where
264− map f rel = do x <- rel; yield (f x)
265−
266−impl Applicative [] where
267− yield x = [x]
268− ap fs xs = do f <- fs; x <- xs; yield (f x)
269−
270−impl Monad [] where
271− bind = ... -- built-in
272−
273−impl Alternative [] where
274− empty = []
275− alt = union
276−
277−impl Foldable [] where
278− fold = ... -- built-in
279−```
280−
281−### The Only `[]`-Specific Primitive
282−
283−| Primitive | Type | Description |
284−|-----------|------|-------------|
285−| `set` | `[a] -> [a] -> {}` | Set a persistent relation to a new value |
286−
287−Everything else comes from traits:
288−
289−| Operation | Trait method |
290−|-----------|-------------|
291−| `empty` | `Alternative.empty` |
292−| `yield` | `Applicative.yield` |
293−| `<-` (bind) | `Monad.bind` |
294−| `union` | `Alternative.alt` |
295−| `fold` | `Foldable.fold` |
296−| `map` | `Functor.map` |
297−
298−### Derived Operations
299−
300−Everything else is built from trait methods + `set`. The compiler recognizes these patterns and executes them as efficient set operations (hash joins, indexed lookups, etc.) — the traits define semantics, the runtime chooses the strategy.
301−
302−**`where`** — conditional empty (requires `Alternative`):
303−
304−```knot
305−where = \cond -> if cond then yield {} else empty
306−```
307−
308−**`filter`** — filter rows:
309−
310−```knot
311−filter = \p rel -> do
312− x <- rel
313− where (p x)
314− yield x
315−```
316−
317−**`join`** — combine relations on a condition:
318−
319−```knot
320−join = \a b -> do
321− x <- a
322− y <- b
323− where (x.id == y.id)
324− yield {x, y}
325−```
326−
327−**`diff`** — rows in one relation but not another:
328−
329−```knot
330−contains = \x rel -> fold (\acc r -> acc || r == x) False {} rel
331−
332−diff = \a b -> do
333− x <- a
334− where (not (contains x b))
335− yield x
336−```
337−
338−**`inter`** — rows in both relations:
339−
340−```knot
341−inter = \a b -> do
342− x <- a
343− where (contains x b)
344− yield x
345−```
346−
347−**`insert`** — add a value (union with a singleton):
348−
349−```knot
350−insert = \x rel -> set rel (union rel (yield x))
351−```
352−
353−**`delete`** — remove matching rows:
354−
355−```knot
356−delete = \p rel -> set rel (filter (\x -> not (p x)) rel)
357−```
358−
359−**`update`** — transform matching rows:
360−
361−```knot
362−update = \p f rel -> set rel (map (\x -> if p x then f x else x) rel)
363−```
364−
365−**`count`**, **`sum`**, **`avg`** — folds:
366−
367−```knot
368−count = \rel -> fold (\n _ -> n + 1) 0 rel
369−sum = \f rel -> fold (\acc x -> acc + f x) 0 rel
370−```
371−
372−**`match`** — filter to one variant:
373−
374−```knot
375−match = \Circle shapes -> do
376− Circle c <- shapes
377− yield c
378−```
379−
380−## Querying
381−
382−### Comprehensions
383−
384−Relation comprehensions use `do` syntax with `yield` to produce rows:
385−
386−```knot
387−&richEmployees = do
388− e <- *employees
389− d <- *departments
390− where e.dept == d.name
391− where d.budget > 1_000_000
392− yield {e.name, e.salary, d.budget}
393−```
394−
395−`<-` draws from a relation (like a `FROM` clause). `where` filters (like a `WHERE` clause). `yield` emits a row into the result relation. This desugars to relational algebra so the optimizer can work on it.
396−
397−### Pipe-Forward Composition
398−
399−Derived combinators like `filter` compose with `|>`:
400−
401−```knot
402−&highEarners =
403− *employees
404− |> filter (\e -> e.salary > 150000)
405− |> map (\e -> {name: e.name, salary: e.salary})
406−```
407−
408−### Querying by Variant: `match`
409−
410−`match` filters to one variant and exposes its fields:
411−
412−```knot
413−&circles = *shapes |> match Circle -- : [{radius: Float}]
414−&rects = *shapes |> match Rect -- : [{width: Float, height: Float}]
415−
416−&bigCircles = &circles |> filter (\c -> c.radius > 10)
417−```
418−
419−### Pattern Matching in Comprehensions
420−
421−Pattern matching on `<-` filters and binds in one step:
422−
423−```knot
424−&bigCircleAreas = do
425− Circle c <- *shapes
426− where c.radius > 10
427− yield {area: pi * c.radius * c.radius}
428−
429−&blockedDetails = do
430− t <- *tickets
431− Blocked {dependencies} <- t.status
432− dep <- dependencies
433− yield {t.title, dep}
434−```
435−
436−### Cross-Variant Operations
437−
438−Operate on the whole relation with `case`:
439−
440−```knot
441−scale = \factor ->
442− set *shapes = do
443− s <- *shapes
444− yield (case s of
445− Circle {radius} -> Circle {radius: radius * factor}
446− Rect {width, height} -> Rect {width: width * factor, height: height * factor})
447−```
448−
449−### Pattern Matching on Relations
450−
451−```knot
452−describe = \rel -> case rel of
453− [] -> "empty"
454− [{name: n}] -> "just " ++ n
455− _ -> show (count rel) ++ " rows"
456−```
457−
458−### Grouping
459−
460−`groupBy` partitions a relation by key fields, like SQL's `GROUP BY`. After `groupBy`, the bound variable becomes a sub-relation (the group), enabling aggregation:
461−
462−```knot
463−&workload = do
464− t <- *todos
465− where t.done == 0
466− groupBy {t.owner}
467− yield {owner: t.owner, count: count t}
468−```
469−
470−The key expression is a record literal whose fields select the grouping columns. After `groupBy {t.owner}`, `t` is rebound from a single row to a sub-relation of all rows sharing that `owner` value. Field access on a group (e.g. `t.owner`) returns the shared key value. Aggregate functions like `count` operate on the whole group.
471−
472−Multiple key fields group by their combination:
473−
474−```knot
475−&summary = do
476− o <- *orders
477− groupBy {o.region, o.status}
478− yield {region: o.region, status: o.status, total: count o}
479−```
480−
481−Grouping is executed via SQLite — key columns are inserted into a temp table and sorted with `ORDER BY`, then consecutive rows with matching keys are collected into groups.
482−
483−## Effects and the IO Monad
484−
485−### Two Kinds of Effects
486−
487−Knot distinguishes two kinds of effects:
488−
489−1. **DB effects** (`reads`, `writes`) — implicit, inferred by the compiler, part of the relational core. These are not wrapped in IO.
490−2. **External effects** (`console`, `fs`, `network`, `clock`, `random`) — tracked in the `IO` type. Functions that perform external effects return `IO {effects} a` values instead of executing immediately.
491−
492−### The IO Type
493−
494−External effects are pure — effectful functions return descriptions of effects (`IO {effects} a`) rather than performing them. IO values are thunks that execute when run.
495−
496−```knot
497−-- println returns an IO action, doesn't print immediately
498−println : a -> IO {console} {}
499−
500−-- readFile returns an IO action
501−readFile : Text -> IO {fs} Text
502−
503−-- now returns an IO action
504−now : IO {clock} Int
505−```
506−
507−### IO Do-Blocks
508−
509−IO do-blocks sequence effects. The `<-` operator runs an IO action and binds its result:
510−
511−```knot
512−main = do
513− content <- readFile "input.txt" -- IO {fs} Text → binds Text
514− println content -- IO {console} {}
515− t <- now -- IO {clock} Int → binds Int
516− println ("time: " ++ show t)
517− yield {}
518−-- overall type: IO {fs, console, clock} {}
519−```
520−
521−### Relation Comprehensions Are Unaffected
522−
523−Relation do-blocks (`<-` from `[T]`) still work exactly as before — no IO wrapping:
524−
525−```knot
526−&seniors = do
527− p <- *people -- [Person] → binds Person
528− where p.age > 65
529− yield p
530−-- type: [Person]
531−```
532−
533−The compiler detects whether a do-block is IO or relational based on the types of bound expressions.
534−
535−### DB Effect Inference
536−
537−DB effects are still inferred as fine-grained capabilities:
538−
539−```knot
540−-- Pure (inferred: no effects)
541−formatName = \n -> toUpper (take 1 n) ++ drop 1 n
542−
543−-- DB read (inferred: {reads *people})
544−&seniors = *people |> filter (\p -> p.age > 65)
545−
546−-- DB write (inferred: {reads *people, writes *people})
547−birthday = \name ->
548− set *people = do
549− p <- *people
550− yield (if p.name == name then {p | age: p.age + 1} else p)
551−```
552−
553−### Effect Annotations
554−
555−Effect signatures are inferred but can be written explicitly:
556−
557−```knot
558−birthday : {reads *people, writes *people} Text -> {}
559−birthday = \name ->
560− set *people = do
561− p <- *people
562− yield (if p.name == name then {p | age: p.age + 1} else p)
563−```
564−
565−If the body uses a capability not listed in the signature, the compiler rejects it.
566−
567−### IO and Transactions
568−
569−DB writes are transactional — they roll back on failure. IO cannot be undone. The compiler enforces: **IO effects and DB writes cannot mix in the same `atomic` block**.
570−
571−```knot
572−-- DB writes go in `atomic`, IO happens after commit
573−handleOrder = \req -> do
574− orderId <- atomic do
575− set *orders = union *orders [{item: req.body.item, qty: 1}]
576− yield (count *orders)
577− println ("New order #" ++ show orderId)
578− yield {orderId}
579−```
580−
581−### File System
582−
583−Built-in functions for file I/O. All return `IO {fs}` values.
584−
585−| Function | Type | Description |
586−|----------|------|-------------|
587−| `readFile` | `Text -> IO {fs} Text` | Read entire file contents as text |
588−| `writeFile` | `Text -> Text -> IO {fs} {}` | Write text to a file (creates or overwrites) |
589−| `appendFile` | `Text -> Text -> IO {fs} {}` | Append text to a file |
590−| `fileExists` | `Text -> IO {fs} Bool` | Check whether a path exists |
591−| `removeFile` | `Text -> IO {fs} {}` | Delete a file |
592−| `listDir` | `Text -> IO {fs} [Text]` | List directory entries as a relation of filenames |
593−
594−```knot
595−-- Copy a file (IO do-block)
596−copyFile = \src dst -> do
597− content <- readFile src
598− writeFile dst content
599−
600−-- Append a log line
601−log = \msg -> appendFile "app.log" (msg ++ "\n")
602−
603−-- List .knot files
604−knotFiles = do
605− files <- listDir "."
606− yield (filter (\f -> contains ".knot" f) files)
607−
608−-- Conditional read
609−loadConfig = \path -> do
610− exists <- fileExists path
611− if exists
612− then readFile path
613− else yield "{}"
614−```
615−
616−### Routes
617−
618−Routes are first-class. A `route` declaration defines an ADT and its HTTP mapping in one place. Each line maps a method + typed path to a constructor. The constructor's fields are the union of path params, query params, and body fields.
619−
620−- `/{name: Type}` in the path — path parameter
621−- `?{name: Type, ...}` after the path — query parameters
622−- `{name: Type, ...}` after the verb — request body
623−
624−Constructors are bare names — their fields are automatically the union of path, query, and body params.
625−
626−```knot
627−route Api where
628− GET /todos/{user: Text}?{page: Int, limit: Int} = GetTodos
629− POST {title: Text, owner: Text, priority: Priority} /todos = AddTodo
630− PUT {owner: Text, person: Text} /todos/{title: Text}/assign = AssignTodo
631− GET /workload = GetWorkload
632−```
633−
634−Dispatch is pattern matching — the compiler ensures exhaustive handling:
635−
636−```knot
637−serve : Api -> Response
638−serve = \req -> case req of
639− GetTodos {user, page, limit} -> pendingFor user page limit
640− AddTodo {title, owner, priority} -> do
641− atomic (add title owner priority)
642− yield {ok: True {}}
643− AssignTodo {title, owner, person} -> do
644− atomic (assign title owner person)
645− yield {ok: True {}}
646− GetWorkload {} -> &workload
647−
648−main = listen 8080 serve
649−```
650−
651−No string routes, no untyped params, no missing handlers.
652−
653−#### Typed Responses
654−
655−Return types can be declared per-endpoint:
656−
657−```knot
658−route Api where
659− GET /todos/{user: Text} -> [{title: Text, priority: Priority}] = GetTodos
660− POST {title: Text, owner: Text} /todos -> {ok: Bool} = AddTodo
661− GET /workload -> [{owner: Text, count: Int}] = GetWorkload
662−```
663−
664−The compiler checks that each `serve` branch returns the declared type.
665−
666−#### Path Prefixes
667−
668−Factor out common path prefixes with nesting:
669−
670−```knot
671−route Api where
672− /todos
673− GET /{user: Text} = GetTodos
674− POST {title: Text, owner: Text} / = AddTodo
675− PUT {owner: Text, person: Text} /{title: Text}/assign = AssignTodo
676− /admin
677− GET /stats = Stats
678− POST /reset = Reset
679−```
680−
681−Prefixes nest arbitrarily:
682−
683−```knot
684−route Api where
685− /api/v1
686− /users
687− GET / = ListUsers
688− GET /{id: Int} = GetUser
689− POST {name: Text, email: Text} / = CreateUser
690− /teams
691− GET / = ListTeams
692− GET /{id: Int}/members = GetMembers
693−```
694−
695−#### Route Composition
696−
697−Routes compose — combine multiple route types:
698−
699−```knot
700−route TodoApi where
701− /todos
702− GET /{user: Text} = ListTodos
703− POST {title: Text, owner: Text} / = CreateTodo
704−
705−route AdminApi where
706− /admin
707− GET /stats = Stats
708− POST /reset = Reset
709−
710−route Api = TodoApi | AdminApi
711−```
712−
713−### Transaction Boundaries
714−
715−DB writes within handlers must use `atomic`. IO happens outside `atomic`:
716−
717−```knot
718−serve = \req -> case req of
719− CreateOrder {item, qty} -> do
720− orderId <- atomic do
721− set *orders = union *orders [{item, qty}]
722− yield (count *orders)
723− println ("New order #" ++ show orderId)
724− yield {orderId}
725−```
726−
727−For sub-transaction boundaries:
728−
729−```knot
730−batchTransfer = \transfers ->
731− map (\t -> atomic (transfer t.from t.to t.amount)) transfers
732−```
733−
734−## Persistence
735−
736−### Mutation
737−
738−All mutation is done through `set`, which replaces a persistent relation with a new value. The runtime diffs the old and new sets to apply minimal changes.
739−
740−```knot
741−-- Insert: union with a singleton
742−set *people = union *people [{name: "Alice", age: 30}]
743−
744−-- Update: map with a conditional
745−set *people = do
746− p <- *people
747− yield (if p.name == "Alice" then {p | age: p.age + 1} else p)
748−
749−-- Delete: filter to keep the rest
750−set *people = filter (\p -> p.age >= 0) *people
751−```
752−
753−### Identity is Structural
754−
755−Relations are sets. Two rows are the same row iff all their fields are equal. Setting a relation to a value that includes a duplicate is a no-op for that row.
756−
757−```knot
758−-- Adding an already-existing row changes nothing
759−set *people = union *people [{name: "Alice", age: 30}]
760−set *people = union *people [{name: "Alice", age: 30}] -- no change
761−```
762−
763−No surrogate IDs, no key declarations. Data identifies itself.
764−
765−### Subset Constraints (Optional)
766−
767−For referential integrity and uniqueness, express value relationships:
768−
769−```knot
770−*orders : [{customer: Text, amount: Int}]
771−*users : [{email: Text, name: Text}]
772−
773−-- Referential integrity: every order's customer must appear in people's names
774−*orders.customer <= *people.name
775−
776−-- Uniqueness: email determines the full row (each email maps to at most one row)
777−*users <= *users.email
778−```
779−
780−### Indexing
781−
782−Automatic. The runtime observes query patterns and indexes accordingly. No `CREATE INDEX`, no key declarations.
783−
784−## Views
785−
786−A `*`-prefixed relation with a body is a **view** — a bidirectional query over source relations. Reads compute the query; writes propagate back to the underlying sources.
787−
788−```knot
789−&seniorStaff = filter (\e -> e.salary > 100000) *employees -- read-only (& prefix)
790−
791−*openTodos = do -- settable (* prefix)
792− t <- *todos
793− yield {title: t.title, owner: t.owner, priority: t.priority, status: Open {}}
794−```
795−
796−### Column Provenance
797−
798−The compiler tracks each column in a view's `yield`:
799−
800−| Kind | Syntax in `yield` | On read | On write | In view type |
801−|------|-------------------|---------|----------|--------------|
802−| **source** | `t.column` | passthrough | passthrough | yes |
803−| **constant** | literal or constructor | filter | auto-fill | no |
804−| **computed** | `expr` | computed | error | yes (read-only) |
805−
806−For `*openTodos` above:
807−
808−| Column | Kind | Read | Write |
809−|--------|------|------|-------|
810−| `title` | source (`t.title`) | passthrough | passthrough |
811−| `owner` | source (`t.owner`) | passthrough | passthrough |
812−| `priority` | source (`t.priority`) | passthrough | passthrough |
813−| `status` | constant (`Open {}`) | filter | auto-fill |
814−
815−The constant column is hidden from the type — its value is fixed by definition:
816−
817−```knot
818−*openTodos : [{title: Text, owner: Text, priority: Priority}]
819−```
820−
821−Writing through a view auto-fills constants and propagates source columns:
822−
823−```knot
824−-- Insert through view — status auto-filled as Open {}
825−set *openTodos = union *openTodos [{title: "New task", owner: "Alice", priority: High {}}]
826−-- Compiler rewrites →
827−-- set *todos = union *todos [{title: "New task", owner: "Alice", priority: High {}, status: Open {}}]
828−
829−-- Delete through view — only affects rows matching the constant
830−set *openTodos = do
831− t <- *openTodos
832− where t.owner != "Alice"
833− yield t
834−-- Only removes Alice's Open todos; resolved/in-progress ones are untouched
835−```
836−
837−Multiple constants create narrow slices:
838−
839−```knot
840−*criticalOpen = do
841− t <- *todos
842− yield {title: t.title, owner: t.owner, status: Open {}, priority: Critical {}}
843−
844−-- Type: [{title: Text, owner: Text}]
845−-- Reads: only critical open todos
846−-- Writes: auto-fills status=Open, priority=Critical
847−```
848−
849−### Recursive Derived Relations
850−
851−Datalog-style transitive closure:
852−
853−```knot
854−*manages : [{manager: Text, report: Text}]
855−
856−&reportsTo : [{ancestor: Text, descendant: Text}] =
857− union
858− (do m <- *manages
859− yield {m.manager, m.report})
860− (do r <- &reportsTo
861− m <- *manages
862− where r.descendant == m.manager
863− yield {r.ancestor, m.report})
864−```
865−
866−The compiler checks stratification.
867−
868−## Schema Evolution
869−
870−The compiler maintains a **schema lockfile** (`schema.lock`) that records the persisted schema. The lockfile uses the same syntax as source code — it's valid Knot containing only type declarations, data definitions, and relation signatures.
871−
872−### The Lockfile
873−
874−```knot
875−-- schema.lock (auto-generated, do not edit)
876−-- Commit to source control.
877−
878−data Priority = Low {} | Medium {} | High {} | Critical {}
879−
880−data Status
881− = Open {}
882− | InProgress {assignee: Text}
883− | Resolved {resolution: Text}
884−
885−*people : [{name: Text, age: Int, email: Text}]
886−
887−*todos : [{title: Text, owner: Text, priority: Priority, status: Status}]
888−
889−migrate *people
890− from {name: Text, age: Int}
891− to {name: Text, age: Int, email: Text}
892− using (\old -> {old | email: old.name ++ "@unknown.com"})
893−```
894−
895−Since it's valid Knot, it can be parsed by the same compiler frontend — no separate schema format. Migrations are recorded in the lockfile so the compiler can detect if a migration is accidentally removed from source.
896−
897−### How It Works
898−
899−On each compile, the compiler diffs the source types against `schema.lock`:
900−
901−| Change | Compiler action |
902−|--------|-----------------|
903−| Add `Maybe` field to record | Auto-update lockfile |
904−| Add variant to ADT | Auto-update lockfile |
905−| Add new relation | Auto-add to lockfile |
906−| Add new `migrate` block | Auto-add to lockfile |
907−| Remove field or variant | Error: require `migrate` |
908−| Add non-Maybe field | Error: require `migrate` |
909−| Change field type | Error: require `migrate` |
910−| Remove a `migrate` block | Error: migration exists in lockfile |
911−| Remove relation | Warning (data will be orphaned) |
912−
913−### Migrations
914−
915−Breaking changes require a `migrate` block:
916−
917−```knot
918−migrate *people
919− from {name: Text, age: Int}
920− to {name: Text, age: Int, email: Text}
921− using (\old -> {old | email: old.name ++ "@unknown.com"})
922−```
923−
924−ADT migrations use pattern matching:
925−
926−```knot
927−migrate *todos
928− from {title: Text, owner: Text, priority: Priority, status: <Open {} | InProgress {assignee: Text} | Resolved {resolution: Text}>}
929− to {title: Text, owner: Text, priority: Priority, status: Status}
930− using (\old -> {old | status: case old.status of
931− InProgress {assignee} -> Resolved {resolution: "closed by " ++ assignee}
932− other -> other})
933−```
934−
935−After a successful compile, `schema.lock` is updated.
936−
937−### Runtime
938−
939−The runtime stores the compiled schema version in the database. On startup it compares against the stored version and applies any pending migrations in order. Already-applied migrations are skipped.
940−
941−`migrate` blocks accumulate in source code. The lockfile tracks all migrations — if a migration present in the lockfile is missing from source, the compiler rejects the build. This prevents accidental deletion. Old migrations can be pruned only by explicitly removing them from both source and lockfile.
942−
943−## Temporal Queries
944−
945−Optional history tracking:
946−
947−```knot
948−*employees : [{name: Text, salary: Int}]
949− with history
950−
951−salaryLastYear = \name -> do
952− t <- now
953− yield (*employees @(t - 365 days)
954− |> filter (\e -> e.name == name)
955− |> map (\e -> e.salary)
956− |> single)
957−```
958−
959−## Type System
960−
961−### Row Polymorphism
962−
963−Functions can be generic over records and relations with specific fields:
964−
965−```knot
966−getName : {name: Text | r} -> Text
967−getName = \r -> r.name
968−```
969−
970−### Row-Polymorphic Variants
971−
972−Functions can be generic over any ADT that has a particular variant:
973−
974−```knot
975−countOpen = \rel ->
976− rel |> filter (\r -> case r.status of Open {} -> True {}; _ -> False {}) |> count
977−
978−-- Inferred: [{status: <Open {} | r> | s}] -> Int
979−-- Works on tickets, issues, orders — anything with an Open status variant
980−```
981−
982−### Traits
983−
984−Traits define shared behavior that types can implement. Syntax follows Rust: `trait` for definition, `impl` for implementation.
985−
986−```knot
987−trait Display a where
988− display : a -> Text
989−
990−impl Display Int where
991− display n = showInt n
992−
993−impl Display Text where
994− display t = t
995−```
996−
997−#### Trait Bounds
998−
999−Trait bounds constrain type variables. They are inferred but can be written explicitly:
1000−
1001−```knot
1002−-- Bounds are inferred from usage
1003−printAll = \rel -> do
1004− r <- rel
1005− yield (display r)
1006−
1007−-- Inferred: Display a => [a] -> [Text]
1008−
1009−-- Explicit bound (optional)
1010−printAll : Display a => [a] -> [Text]
1011−```
1012−
1013−#### Multiple Bounds
1014−
1015−```knot
1016−sortAndShow : Ord a => Display a => [a] -> [Text]
1017−```
1018−
1019−#### Associated Types
1020−
1021−Traits can have associated types:
1022−
1023−```knot
1024−trait Collection c where
1025− type Item c
1026− empty : c
1027− add : Item c -> c -> c
1028− toRel : c -> [Item c]
1029−
1030−impl Collection [a] where
1031− type Item [a] = a
1032− empty = []
1033− add x rel = union rel (yield x)
1034− toRel = id
1035−```
1036−
1037−#### Deriving
1038−
1039−Common traits are auto-derived:
1040−
1041−```knot
1042−data Priority = Low {} | Medium {} | High {} | Critical {}
1043− deriving (Eq, Ord, Display)
1044−
1045−data Shape
1046− = Circle {radius: Float}
1047− | Rect {width: Float, height: Float}
1048− deriving (Eq, Display)
1049−```
1050−
1051−#### Relation-Specific Traits
1052−
1053−The standard library defines traits that interact with the relational model:
1054−
1055−```knot
1056−-- Types that can be used in where clauses and joins
1057−trait Eq a where
1058− (==) : a -> a -> Bool
1059−
1060−-- Types that support ordering
1061−trait Ord a where
1062− compare : a -> a -> Ordering
1063−```
1064−
1065−#### Trait-Polymorphic Queries
1066−
1067−Traits compose naturally with relational queries:
1068−
1069−```knot
1070−-- Works on any relation whose rows are displayable
1071−report = \rel -> do
1072− r <- rel
1073− yield {summary: display r}
1074−
1075−-- Works on any relation with a numeric column named `amount`
1076−totalAmount : Num n => [{amount: n | r}] -> n
1077−totalAmount = \rel -> fold (\acc r -> acc + r.amount) 0 rel
1078−```
1079−
1080−#### Default Implementations
1081−
1082−```knot
1083−trait Summarize a where
1084− summary : a -> Text
1085− detailed : a -> Text
1086− detailed x = summary x -- default: same as summary
1087−
1088−impl Summarize Shape where
1089− summary s = case s of
1090− Circle {radius} -> "circle r=" ++ display radius
1091− Rect {width, height} -> display width ++ "x" ++ display height
1092− -- detailed uses the default
1093−```
1094−
1095−### Type Inference
1096−
1097−Full Hindley-Milner style inference extended with row polymorphism and trait bounds. Type signatures are always optional — the compiler infers trait bounds from usage just like it infers everything else.
1098−
1099−## Full Example
1100−
1101−```knot
1102−data Priority = Low {} | Medium {} | High {} | Critical {}
1103−
1104−data Status
1105− = Open {}
1106− | InProgress {assignee: Text}
1107− | Resolved {resolution: Text}
1108−
1109−*todos : [{title: Text, owner: Text, priority: Priority, status: Status}]
1110−
1111−route Api where
1112− GET /todos/{user: Text} -> [{title: Text, priority: Priority}] = GetTodos
1113− POST {title: Text, owner: Text, priority: Priority}
1114− /todos -> {ok: Bool} = AddTodo
1115− PUT {owner: Text, person: Text} /todos/{title: Text}/assign -> {ok: Bool} = AssignTodo
1116− PUT {owner: Text, msg: Text} /todos/{title: Text}/resolve -> {ok: Bool} = ResolveTodo
1117− GET /workload -> [{owner: Text, count: Int}] = GetWorkload
1118−
1119−formatTitle = \title -> toUpper (take 1 title) ++ drop 1 title
1120−
1121−pendingFor = \user -> do
1122− t <- *todos
1123− where t.owner == user
1124− Open {} <- t.status
1125− yield {t.title, t.priority}
1126−
1127−add = \title owner priority ->
1128− set *todos = union *todos [{title: formatTitle title, owner, priority, status: Open {}}]
1129−
1130−assign = \title owner person ->
1131− set *todos = do
1132− t <- *todos
1133− yield (if t.title == title && t.owner == owner
1134− then {t | status: InProgress {assignee: person}}
1135− else t)
1136−
1137−resolve = \title owner msg ->
1138− set *todos = do
1139− t <- *todos
1140− yield (if t.title == title && t.owner == owner
1141− then {t | status: Resolved {resolution: msg}}
1142− else t)
1143−
1144−&workload = do
1145− t <- *todos
1146− Open {} <- t.status
1147− groupBy {t.owner}
1148− yield {owner: t.owner, count: count t}
1149−
1150−serve : Api -> Response
1151−serve = \req -> case req of
1152− GetTodos {user} -> pendingFor user
1153− AddTodo {title, owner, priority} -> do
1154− atomic (add title owner priority)
1155− yield {ok: True {}}
1156− AssignTodo {title, owner, person} -> do
1157− atomic (assign title owner person)
1158− yield {ok: True {}}
1159− ResolveTodo {title, owner, msg} -> do
1160− atomic (resolve title owner msg)
1161− yield {ok: True {}}
1162− GetWorkload {} -> &workload
1163−
1164−main = listen 8080 serve
1165−```
README.md+6−42
@@ -1,53 +1,17 @@
11# Knot
22
3−Knot is a functional relational programming language. Relations are the primary data structure, computation is pure and functional, and state is automatically persisted to SQLite.
3+Knot is a functional relational programming language. Relations are the
4+primary data structure, computation is pure and functional, and state is
5+automatically persisted to SQLite. Effects are inferred and tracked in the
6+type system.
47
58## Quick Start
69
710```sh
8−# Build
911cargo build
10−
11−# Compile and run a Knot program
1212cargo run -p knot-compiler -- build examples/hello.knot
1313./examples/hello
1414```
1515
16−## Documentation
17−
18−- [Language Design](DESIGN.md) — full language specification
19−- [Standard Library](stdlib.md) — all built-in functions, traits, and types
20−
21−## Project Structure
22−
23−```
24−crates/
25− knot/ Frontend (lexer, parser, AST, diagnostics)
26− knot-runtime/ Runtime library (value representation, SQLite persistence)
27− knot-compiler/ Cranelift-based compiler (CLI: knotc)
28−examples/ Example .knot programs
29−```
30−
31−## Example
32−
33−```knot
34−type Person = {name: Text, age: Int}
35−*people : [Person]
36−
37−&seniors = *people |> filter (\p -> p.age > 65)
38−
39−main = do
40− set *people = [
41− {name: "Alice", age: 30},
42− {name: "Bob", age: 70}
43− ]
44− println (count &seniors)
45− yield {}
46−```
47−
48−## Tests
49−
50−```sh
51−cargo test # All tests
52−cargo test -p knot # Frontend only
53−```
16+A compiled binary creates a `<name>.db` SQLite file for persistence and a
17+`<name>.schema.lock` file for migration tracking.
base.mddeleted−551
@@ -1,551 +0,0 @@
1−# Knot Standard Library
2−
3−Every function listed here is built in to the compiler. No imports needed.
4−
5−## IO
6−
7−Effectful functions return `IO` values — pure descriptions of side effects. IO actions are executed when the program runs (via `main`). Use do-blocks with `<-` to sequence IO actions and extract their results.
8−
9−```knot
10−println : a -> IO {console} {}
11−```
12−Print a value followed by a newline. Works on any type. Returns an IO action.
13−
14−```knot
15−print : a -> IO {console} {}
16−```
17−Print a value without a trailing newline.
18−
19−```knot
20−readLine : IO {console} Text
21−```
22−Read a line from stdin.
23−
24−```knot
25−show : a -> Text
26−```
27−Convert any value to its text representation. Records print as `{field: value, ...}`, relations as `[v1, v2, ...]`, constructors as `Tag {fields}`. This is a pure function (no IO).
28−
29−### IO Do-Blocks
30−
31−Use `do` to sequence IO actions:
32−
33−```knot
34−main = do
35− println "What is your name?"
36− name <- readLine
37− println ("Hello, " ++ name)
38− yield {}
39−```
40−
41−The `<-` operator runs an IO action and binds its result. Bare IO expressions (like `println`) are also executed. The overall block type is `IO {union of effects} result`.
42−
43−## Relations
44−
45−```knot
46−union : [a] -> [a] -> [a]
47−```
48−Set union of two relations. Duplicates (by structural equality) are removed.
49−
50−```knot
51−count : [a] -> Int
52−```
53−Number of rows in a relation.
54−
55−```knot
56−filter : (a -> Bool) -> [a] -> [a]
57−```
58−Keep rows where the predicate returns `True`.
59−
60−```knot
61−-- Direct call
62−filter (\p -> p.age > 65) *people
63−
64−-- With pipe
65−*people |> filter (\p -> p.age > 65)
66−```
67−
68−```knot
69−map : (a -> b) -> [a] -> [b]
70−```
71−Apply a function to every row. Duplicates in the result are removed (relations are sets).
72−
73−```knot
74−map (\p -> {p.name, p.age}) *people
75−```
76−
77−```knot
78−fold : (b -> a -> b) -> b -> [a] -> b
79−```
80−Left fold over a relation. The accumulator function is curried: `fold f init rel`.
81−
82−```knot
83−-- Sum
84−fold (\acc x -> acc + x) 0 [1, 2, 3] -- 6
85−
86−-- Build text
87−fold (\acc p -> acc ++ p.name ++ " ") "" *people
88−```
89−
90−```knot
91−single : [a] -> a
92−```
93−Extract the single element from a one-element relation. Panics if the relation is empty or has more than one element.
94−
95−```knot
96−single [{name: "Alice"}] -- {name: "Alice"}
97−```
98−
99−```knot
100−diff : [a] -> [a] -> [a]
101−```
102−Set difference. Returns rows in the first relation that are not in the second.
103−
104−```knot
105−diff [1, 2, 3, 4, 5] [2, 4] -- [1, 3, 5]
106−
107−-- Remove known users from a list
108−diff *allUsers *bannedUsers
109−```
110−
111−```knot
112−inter : [a] -> [a] -> [a]
113−```
114−Set intersection. Returns rows that appear in both relations.
115−
116−```knot
117−inter [1, 2, 3, 4, 5] [2, 4, 6] -- [2, 4]
118−
119−-- Find users who are also admins
120−inter *users *admins
121−```
122−
123−```knot
124−sum : (a -> b) -> [a] -> b
125−```
126−Sum a numeric projection over a relation. The projection function extracts the value to sum from each row. Works with both `Int` and `Float`.
127−
128−```knot
129−sum (\x -> x) [10, 20, 30] -- 60
130−sum (\o -> o.amount) *orders -- total of all order amounts
131−*orders |> sum (\o -> o.amount) -- same with pipe
132−```
133−
134−```knot
135−avg : (a -> Float) -> [a] -> Float
136−```
137−Average a numeric projection over a relation. Always returns `Float`. Returns `0.0` for an empty relation.
138−
139−```knot
140−avg (\x -> x) [10.0, 20.0, 30.0] -- 20.0
141−avg (\e -> e.salary) *employees -- mean salary
142−```
143−
144−## Text
145−
146−```knot
147−toUpper : Text -> Text
148−```
149−Convert text to uppercase.
150−
151−```knot
152−toLower : Text -> Text
153−```
154−Convert text to lowercase.
155−
156−```knot
157−take : Int -> Text -> Text
158−```
159−First *n* characters of a text value.
160−
161−```knot
162−take 5 "hello world" -- "hello"
163−```
164−
165−```knot
166−drop : Int -> Text -> Text
167−```
168−Skip the first *n* characters.
169−
170−```knot
171−drop 6 "hello world" -- "world"
172−```
173−
174−```knot
175−length : Text -> Int
176−```
177−Number of characters in a text value.
178−
179−```knot
180−trim : Text -> Text
181−```
182−Strip leading and trailing whitespace.
183−
184−```knot
185−contains : Text -> Text -> Bool
186−```
187−Check whether a text value contains a substring. The first argument is the needle, the second is the haystack.
188−
189−```knot
190−contains "ell" "hello" -- True
191−"hello" |> contains "ell" -- True
192−```
193−
194−```knot
195−reverse : Text -> Text
196−```
197−Reverse a text value.
198−
199−```knot
200−chars : Text -> [Text]
201−```
202−Convert text into a relation of single-character text values.
203−
204−```knot
205−chars "abc" -- ["a", "b", "c"]
206−```
207−
208−## Utility
209−
210−```knot
211−id : a -> a
212−```
213−Identity function. Returns its argument unchanged.
214−
215−```knot
216−not : Bool -> Bool
217−```
218−Boolean negation. Function form of the `!` operator.
219−
220−```knot
221−now : IO {clock} Int
222−```
223−Current time in milliseconds since the Unix epoch. Returns an IO action. Use `<-` in a do-block to get the value:
224−
225−```knot
226−main = do
227− t <- now
228− println ("Time: " ++ show t)
229− yield {}
230−```
231−
232−```knot
233−randomInt : Int -> IO {random} Int
234−```
235−Random integer in `[0, bound)`. Returns an IO action.
236−
237−```knot
238−randomFloat : IO {random} Float
239−```
240−Random float in `[0.0, 1.0)`. Returns an IO action.
241−
242−## JSON
243−
244−```knot
245−toJson : a -> Text
246−```
247−Convert any value to its JSON text representation. Records become JSON objects, relations become JSON arrays, `Int`/`Float` become numbers, `Text` becomes a JSON string, `Bool` becomes `true`/`false`, and `{}` becomes `{}`.
248−
249−```knot
250−toJson {name: "Alice", age: 30} -- "{\"name\":\"Alice\",\"age\":30}"
251−toJson [1, 2, 3] -- "[1,2,3]"
252−```
253−
254−```knot
255−parseJson : Text -> a
256−```
257−Parse a JSON string into a Knot value. JSON objects become records, arrays become relations, strings become `Text`, integers become `Int`, decimals become `Float`, booleans become `Bool`, and `null` becomes `{}`. Handles standard JSON escape sequences.
258−
259−```knot
260−parseJson "{\"x\": 10}" -- {x: 10}
261−parseJson "[1, 2, 3]" -- [1, 2, 3]
262−
263−-- Round-trip
264−let json = toJson {name: "Bob"}
265−parseJson json -- {name: "Bob"}
266−```
267−
268−## Bytes
269−
270−```knot
271−bytesLength : Bytes -> Int
272−```
273−Number of bytes in a byte string.
274−
275−```knot
276−bytesSlice : Int -> Int -> Bytes -> Bytes
277−```
278−Extract a sub-range. `bytesSlice start len bytes` returns `len` bytes starting at offset `start`.
279−
280−```knot
281−bytesConcat : Bytes -> Bytes -> Bytes
282−```
283−Concatenate two byte strings.
284−
285−```knot
286−bytesGet : Int -> Bytes -> Int
287−```
288−Get the byte value (0–255) at the given index.
289−
290−```knot
291−textToBytes : Text -> Bytes
292−```
293−UTF-8 encode a text value into bytes.
294−
295−```knot
296−bytesToText : Bytes -> Text
297−```
298−UTF-8 decode bytes into text.
299−
300−```knot
301−bytesToHex : Bytes -> Text
302−```
303−Encode bytes as a hexadecimal text string.
304−
305−```knot
306−bytesFromHex : Text -> Bytes
307−```
308−Decode a hexadecimal text string into bytes.
309−
310−```knot
311−bytesToHex (textToBytes "hi") -- "6869"
312−bytesFromHex "6869" |> bytesToText -- "hi"
313−```
314−
315−## File System
316−
317−All file system functions return `IO {fs}` values.
318−
319−```knot
320−readFile : Text -> IO {fs} Text
321−```
322−Read the entire contents of a file as text. Returns an IO action.
323−
324−```knot
325−main = do
326− content <- readFile "config.json"
327− println content
328− yield {}
329−```
330−
331−```knot
332−writeFile : Text -> Text -> IO {fs} {}
333−```
334−Write text to a file. Creates the file if it doesn't exist, overwrites if it does. The first argument is the path, the second is the contents.
335−
336−```knot
337−writeFile "output.txt" "hello"
338−```
339−
340−```knot
341−appendFile : Text -> Text -> IO {fs} {}
342−```
343−Append text to a file. Creates the file if it doesn't exist.
344−
345−```knot
346−main = do
347− t <- now
348− appendFile "app.log" ("event at " ++ show t ++ "\n")
349−```
350−
351−```knot
352−fileExists : Text -> IO {fs} Bool
353−```
354−Check whether a file or directory exists at the given path.
355−
356−```knot
357−loadConfig = \path -> do
358− exists <- fileExists path
359− if exists
360− then readFile path
361− else yield "{}"
362−```
363−
364−```knot
365−removeFile : Text -> IO {fs} {}
366−```
367−Delete a file.
368−
369−```knot
370−listDir : Text -> IO {fs} [Text]
371−```
372−List the entries of a directory as a relation of filenames.
373−
374−```knot
375−main = do
376− files <- listDir "."
377− println files
378− yield {}
379−```
380−
381−## Server
382−
383−```knot
384−listen : Int -> (a -> b) -> {}
385−```
386−Start an HTTP server on the given port with a handler function. The handler receives a route ADT value and returns a response. Has the `{network}` effect. See `route` declarations in the language spec for defining typed endpoints.
387−
388−## IO Type Syntax
389−
390−IO types can be annotated in type signatures:
391−
392−```knot
393−IO {console} {} -- IO action with console effect, returns unit
394−IO {fs} Text -- IO action with fs effect, returns Text
395−IO {clock, random} Int -- IO action with multiple effects
396−```
397−
398−Effects tracked in IO types: `console`, `fs`, `network`, `clock`, `random`.
399−
400−## Traits
401−
402−The following traits are defined in the prelude. All types that implement a trait can use its methods without imports.
403−
404−### Eq
405−
406−```knot
407−trait Eq a where
408− eq : a -> a -> Bool
409−```
410−Structural equality as a trait method. Built-in impls for `Int`, `Float`, `Text`, `Bool`.
411−
412−### Ord
413−
414−```knot
415−trait Eq a => Ord a where
416− compare : a -> a -> Int
417−```
418−Ordering comparison. Returns `-1`, `0`, or `1`. Requires `Eq`. Built-in impls for `Int`, `Float`, `Text`.
419−
420−### Num
421−
422−```knot
423−trait Eq a => Num a where
424− add : a -> a -> a
425− sub : a -> a -> a
426− mul : a -> a -> a
427− div : a -> a -> a
428− negate : a -> a
429−```
430−Numeric operations as trait methods. Requires `Eq`. Built-in impls for `Int` and `Float`. Use as a trait bound for generic numeric code:
431−
432−```knot
433−double : Num a => a -> a
434−double = \x -> add x x
435−
436−double 21 -- 42
437−double 1.5 -- 3.0
438−```
439−
440−The methods wrap the corresponding built-in operators (`+`, `-`, `*`, `/`, unary `-`), but can be passed as values and used in higher-order functions:
441−
442−```knot
443−fold add 0 [1, 2, 3] -- 6
444−```
445−
446−### Display
447−
448−```knot
449−trait Display a where
450− display : a -> Text
451−```
452−Convert a value to its text representation. Built-in impls for `Int`, `Float`, `Text`, `Bool`. The `Text` impl returns the value as-is; the others delegate to `show`.
453−
454−Use as a trait bound for generic formatting:
455−
456−```knot
457−displayAll : Display a => [a] -> [Text]
458−displayAll = \rel -> do
459− r <- rel
460− yield (display r)
461−```
462−
463−Implement for your own types:
464−
465−```knot
466−data Color = Red {} | Green {} | Blue {}
467−
468−impl Display Color where
469− display c = case c of
470− Red {} -> "red"
471− Green {} -> "green"
472− Blue {} -> "blue"
473−```
474−
475−### Functor
476−
477−```knot
478−trait Functor (f : Type -> Type) where
479− map : (a -> b) -> f a -> f b
480−```
481−
482−### Applicative
483−
484−```knot
485−trait Functor f => Applicative (f : Type -> Type) where
486− yield : a -> f a
487− ap : f (a -> b) -> f a -> f b
488−```
489−
490−### Monad
491−
492−```knot
493−trait Applicative m => Monad (m : Type -> Type) where
494− bind : (a -> m b) -> m a -> m b
495−```
496−
497−### Alternative
498−
499−```knot
500−trait Applicative f => Alternative (f : Type -> Type) where
501− empty : f a
502− alt : f a -> f a -> f a
503−```
504−Built-in impl for `[]`: `empty = []`, `alt = union`.
505−
506−### Foldable
507−
508−```knot
509−trait Foldable (t : Type -> Type) where
510− fold : (b -> a -> b) -> b -> t a -> b
511−```
512−
513−## Operators
514−
515−These are not functions but are available as infix operators:
516−
517−| Operator | Type | Description |
518−|----------|------|-------------|
519−| `+` | `Int -> Int -> Int` | Addition (also `Float`) |
520−| `-` | `Int -> Int -> Int` | Subtraction |
521−| `*` | `Int -> Int -> Int` | Multiplication |
522−| `/` | `Int -> Int -> Int` | Division |
523−| `==` | `a -> a -> Bool` | Structural equality |
524−| `!=` | `a -> a -> Bool` | Structural inequality |
525−| `<` | `a -> a -> Bool` | Less than |
526−| `>` | `a -> a -> Bool` | Greater than |
527−| `<=` | `a -> a -> Bool` | Less than or equal |
528−| `>=` | `a -> a -> Bool` | Greater than or equal |
529−| `&&` | `Bool -> Bool -> Bool` | Logical and |
530−| `\|\|` | `Bool -> Bool -> Bool` | Logical or |
531−| `++` | `Text -> Text -> Text` | Text concatenation |
532−| `\|>` | `a -> (a -> b) -> b` | Pipe forward |
533−| `-` (unary) | `Int -> Int` | Negation |
534−| `!` (unary) | `Bool -> Bool` | Logical not |
535−
536−## Currying
537−
538−All multi-argument standard library functions support partial application. You can pass fewer arguments to get back a function:
539−
540−```knot
541−-- Partially apply filter
542−isOld = filter (\p -> p.age > 65)
543−isOld *people -- same as: filter (\p -> p.age > 65) *people
544−
545−-- Pipe-forward works because filter returns a function after one arg
546−*people |> filter (\p -> p.age > 65) |> map (\p -> p.name)
547−
548−-- Partially apply fold
549−sum = fold (\acc x -> acc + x) 0
550−sum [1, 2, 3] -- 6
551−```
crates/knot-compile-rt/Cargo.tomlnew file+14
@@ -0,0 +1,14 @@
1+[package]
2+name = "knot-compile-rt"
3+version.workspace = true
4+edition.workspace = true
5+
6+[lib]
7+# Staticlib so the linker can embed this into compiled knot programs alongside
8+# libknot_runtime.a; rlib so the knot binary / tests can use it as a normal dep.
9+crate-type = ["staticlib", "rlib"]
10+
11+[dependencies]
12+knot-jit = { workspace = true }
13+knot-runtime = { workspace = true }
14+libc = { workspace = true }
crates/knot-compile-rt/src/lib.rsnew file+117
@@ -0,0 +1,117 @@
1+//! The `compile` builtin's JIT glue, linked into compiled knot programs as a
2+//! separate staticlib archive (alongside libknot_runtime.a). knot-runtime
3+//! cannot depend on the compiler directly (knot-compiler's build.rs builds
4+//! knot-runtime — a dep here would recurse), so this crate bridges: it depends
5+//! on knot-jit (→ knot-compiler) and knot-runtime, exposes the compile
6+//! implementation as a `#[no_mangle]` extern, and registers it into
7+//! knot-runtime's `COMPILE_IMPL` slot at program startup.
8+
9+use std::ffi::c_void;
10+
11+/// Adapter bridging the JIT's `c_void`-typed set-code signature to the
12+/// runtime's `knot_value_set_code`. Both pointer args are opaque to the JIT
13+/// (a `Value*` and a `CodeHandle*`), so the casts are just pointer repoints.
14+unsafe extern "C" fn set_code(v: *mut c_void, handle: *const c_void) {
15+ unsafe {
16+ knot_runtime::knot_value_set_code(v.cast(), handle.cast());
17+ }
18+}
19+
20+/// The compile implementation registered into knot-runtime. Matches the
21+/// `CompileImpl` signature in knot-runtime: takes the program source and a db
22+/// handle, writes the inferred type into the out-params, returns the forced
23+/// value (or null on any compile error).
24+///
25+/// `out_ty` is a freshly `malloc`'d, NUL-free buffer of length `*out_ty_len`
26+/// (caller frees with `libc::free`), or null when the type is unknown.
27+///
28+/// # Safety
29+///
30+/// Called from knot-runtime via the registered function pointer. `src_ptr` /
31+/// `expected_ptr` must point to `src_len` / `expected_len` valid bytes (need
32+/// not be NUL-terminated); `db` must be a live knot-runtime db handle. Any
33+/// non-null out-param pointer must be valid for writes of its payload.
34+#[unsafe(no_mangle)]
35+pub unsafe extern "C" fn knot_compile_impl(
36+ src_ptr: *const u8,
37+ src_len: usize,
38+ db: *mut c_void,
39+ expected_ptr: *const u8,
40+ expected_len: usize,
41+ out_ty_ptr: *mut *mut u8,
42+ out_ty_len: *mut usize,
43+ out_rels_ptr: *mut *mut u8,
44+ out_rels_len: *mut usize,
45+ out_err_ptr: *mut *mut u8,
46+ out_err_len: *mut usize,
47+) -> *mut knot_runtime::Value {
48+ // Null out-params up front so a compile error leaves them well-defined.
49+ unsafe {
50+ knot_runtime::compile_out_params_init([
51+ (out_ty_ptr, out_ty_len),
52+ (out_rels_ptr, out_rels_len),
53+ (out_err_ptr, out_err_len),
54+ ]);
55+ }
56+
57+ let src_bytes = unsafe { std::slice::from_raw_parts(src_ptr, src_len) };
58+ let Ok(source) = std::str::from_utf8(src_bytes) else {
59+ return std::ptr::null_mut();
60+ };
61+ // The host's expected type as a source-annotation string; empty = unpinned
62+ // (no subsumption check). Forwarded to the JIT so it runs the real-type
63+ // subsumption inside its own inference context.
64+ let expected: Option<&str> = if expected_ptr.is_null() || expected_len == 0 {
65+ None
66+ } else {
67+ let b = unsafe { std::slice::from_raw_parts(expected_ptr, expected_len) };
68+ std::str::from_utf8(b).ok()
69+ };
70+
71+ // This is the `knot_compile_impl` a *compiled program* links — the
72+ // `base.compile` boundary that runs in the host's process. Contain any
73+ // panic (in compilation or the JIT'd snippet) so it surfaces as an Err
74+ // instead of unwinding across `extern "C"` into the host. Shared with the
75+ // compiler binary's impl via `run_compile_contained` so the two can't drift.
76+ unsafe {
77+ knot_runtime::run_compile_contained(
78+ db,
79+ out_ty_ptr,
80+ out_ty_len,
81+ out_rels_ptr,
82+ out_rels_len,
83+ out_err_ptr,
84+ out_err_len,
85+ |db| {
86+ // A clean compile error: report it via out_err (as null), which
87+ // the shared helper leaves to the runtime's `Result.Err`. We
88+ // signal it by writing the message here and returning None.
89+ match knot_jit::compile_and_run(
90+ source,
91+ db,
92+ expected,
93+ Some(set_code as unsafe extern "C" fn(*mut c_void, *const c_void)),
94+ ) {
95+ Ok(cv) => Some(knot_runtime::CompileOutcome {
96+ // knot-jit treats the value as opaque *mut c_void; here
97+ // it's a knot-runtime Value again.
98+ value: cv.value as *mut knot_runtime::Value,
99+ ty: cv.ty,
100+ relations: cv.relations,
101+ }),
102+ Err(e) => {
103+ knot_runtime::compile_write_out(&e.0, out_err_ptr, out_err_len);
104+ None
105+ }
106+ }
107+ },
108+ )
109+ }
110+}
111+
112+/// Install `knot_compile_impl` into knot-runtime's `COMPILE_IMPL` slot. Called
113+/// once from the generated program's `main` before user code runs.
114+#[unsafe(no_mangle)]
115+pub extern "C-unwind" fn knot_compile_rt_init() {
116+ knot_runtime::knot_register_compile_impl(knot_compile_impl as *mut c_void);
117+}
crates/knot-compiler/Cargo.toml+26−1
@@ -8,15 +8,40 @@ name = "knot_compiler"
88path = "src/lib.rs"
99
1010[[bin]]
11−name = "knotc"
11+name = "knot"
1212path = "src/main.rs"
1313
1414[dependencies]
1515knot = { workspace = true }
16+knot-descriptor = { workspace = true }
1617knot-runtime = { workspace = true }
1718cranelift-codegen = { workspace = true }
1819cranelift-frontend = { workspace = true }
1920cranelift-module = { workspace = true }
2021cranelift-object = { workspace = true }
2122cranelift-native = { workspace = true }
23+libc = { workspace = true }
2224target-lexicon = { workspace = true }
25+# .eh_frame emission for generated functions (see src/unwind.rs) — versions
26+# pinned to what cranelift 0.131 itself uses.
27+gimli = { version = "0.33", default-features = false, features = ["write"] }
28+object = { version = "0.39", default-features = false, features = ["write"] }
29+# Z3 for compile-time refinement-predicate implication (SMT subtyping between
30+# refined types). `gh-release` statically links a prebuilt libz3 (downloaded at
31+# build time), so the compiler binary is self-contained — no runtime Z3 dep.
32+z3 = { version = "0.20", default-features = false, features = ["gh-release"] }
33+# Content-hash of the embedded runtime archive, used at link time to reject a
34+# stale on-disk libknot_runtime.a (replaces the old mtime heuristic).
35+blake3 = { version = "1", default-features = false }
36+# Order-preserving map for record/variant field storage: source definition order
37+# is retained so implicit-field (^name) resolution can try fields in
38+# bottom-to-top declaration order instead of BTreeMap's alphabetical order.
39+indexmap = { version = "2", default-features = false }
40+cranelift-jit = "0.131"
41+
42+[build-dependencies]
43+blake3 = { version = "1", default-features = false }
44+
45+[dev-dependencies]
46+knot-descriptor = { workspace = true }
47+
crates/knot-compiler/build.rsnew file+188
@@ -0,0 +1,188 @@
1+use std::path::{Path, PathBuf};
2+
3+fn is_valid_lib(p: &Path) -> bool {
4+ p.exists() && std::fs::metadata(p).map(|m| m.len() > 0).unwrap_or(false)
5+}
6+
7+/// Max mtime across all runtime source files: every `*.rs` under
8+/// `crates/knot-runtime/src` (recursively — the crate has lib.rs, log.rs,
9+/// tui.rs and may gain more) plus the runtime crate's Cargo.toml.
10+fn runtime_src_mtime(workspace_root: &Path) -> Option<std::time::SystemTime> {
11+ fn consider(p: &Path, newest: &mut Option<std::time::SystemTime>) {
12+ if let Ok(m) = std::fs::metadata(p).and_then(|m| m.modified())
13+ && newest.map(|n| m > n).unwrap_or(true)
14+ {
15+ *newest = Some(m);
16+ }
17+ }
18+ fn walk(dir: &Path, newest: &mut Option<std::time::SystemTime>) {
19+ if let Ok(rd) = std::fs::read_dir(dir) {
20+ for entry in rd.flatten() {
21+ let p = entry.path();
22+ if p.is_dir() {
23+ walk(&p, newest);
24+ } else if p.extension().map(|e| e == "rs").unwrap_or(false) {
25+ consider(&p, newest);
26+ }
27+ }
28+ }
29+ }
30+ let mut newest = None;
31+ walk(&workspace_root.join("crates/knot-runtime/src"), &mut newest);
32+ consider(
33+ &workspace_root.join("crates/knot-runtime/Cargo.toml"),
34+ &mut newest,
35+ );
36+ newest
37+}
38+
39+/// Check that `lib` is at least as new as every runtime source file.
40+fn is_fresh_lib(lib: &Path, workspace_root: &Path) -> bool {
41+ if !is_valid_lib(lib) {
42+ return false;
43+ }
44+ let src_mtime = runtime_src_mtime(workspace_root);
45+ let lib_mtime = std::fs::metadata(lib).and_then(|m| m.modified()).ok();
46+ match (src_mtime, lib_mtime) {
47+ (Some(s), Some(l)) => l >= s,
48+ _ => true, // can't compare, assume valid
49+ }
50+}
51+
52+fn main() {
53+ let out_dir = std::env::var("OUT_DIR").unwrap();
54+ let out_path = PathBuf::from(&out_dir);
55+ let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
56+ let workspace_root = PathBuf::from(&manifest_dir).join("../..");
57+
58+ // Remove stale dest file so it doesn't interfere with the search
59+ let dest = out_path.join("libknot_runtime.a");
60+ let _ = std::fs::remove_file(&dest);
61+
62+ // Find libknot_runtime.a — try multiple strategies. NOTE: this build
63+ // script deliberately does NOT spawn a nested `cargo build` — doing so
64+ // deadlocks on the package-cache lock whenever the outer build holds it
65+ // (the common case). Staleness is instead handled at LINK time by
66+ // `rebuild_runtime_at_exe` in main.rs, which runs as its own process and
67+ // can safely invoke cargo. Here we only locate an existing archive.
68+ let runtime_path = None
69+ // 1. Explicit env var override
70+ .or_else(|| {
71+ std::env::var("KNOT_RUNTIME_LIB")
72+ .ok()
73+ .map(PathBuf::from)
74+ .filter(|p| is_valid_lib(p))
75+ })
76+ // 2. Walk up from OUT_DIR (works in normal `cargo build` within
77+ // workspace), but stop at the cargo target directory boundary. Going
78+ // past it would let a stray libknot_runtime.a in an unrelated
79+ // ancestor (e.g. $HOME) be embedded on mtime alone.
80+ .or_else(|| {
81+ let target_dir = std::env::var("CARGO_TARGET_DIR")
82+ .map(PathBuf::from)
83+ .unwrap_or_else(|_| workspace_root.join("target"));
84+ // Canonicalize so the `..`-laden workspace_root path compares
85+ // correctly against cargo's canonical OUT_DIR ancestors.
86+ let boundary = std::fs::canonicalize(&target_dir).unwrap_or(target_dir.clone());
87+ out_path
88+ .ancestors()
89+ .take_while(|p| p.starts_with(&boundary))
90+ .map(|p| p.join("libknot_runtime.a"))
91+ .chain(
92+ ["release", "debug"]
93+ .iter()
94+ .map(|profile| target_dir.join(profile).join("libknot_runtime.a")),
95+ )
96+ .find(|p| is_fresh_lib(p, &workspace_root))
97+ });
98+
99+ // Copy the runtime into OUT_DIR so include_bytes! can find it with a stable path
100+ if let Some(src) = runtime_path {
101+ std::fs::copy(&src, &dest).expect("failed to copy libknot_runtime.a to OUT_DIR");
102+ println!("cargo:rustc-cfg=has_embedded_runtime");
103+ // Stamp the content hash of the archive we're embedding. At link time
104+ // the compiler hashes any on-disk candidate and compares — a match
105+ // means byte-identical bits (fresh), regardless of mtimes or which
106+ // archive produced them.
107+ let bytes = std::fs::read(&dest).expect("failed to read embedded runtime for hashing");
108+ let hash = blake3::hash(&bytes).to_hex();
109+ println!("cargo:rustc-env=KNOT_EMBEDDED_RUNTIME_HASH={hash}");
110+ }
111+
112+ println!("cargo:rustc-check-cfg=cfg(has_embedded_runtime)");
113+
114+ // ── JIT compile-runtime archive (knot-compile-rt) ──
115+ // Always linked into compiled programs so `base.compile` works. Found in
116+ // the target dir (the `knot` binary depends on knot-compile-rt, so cargo
117+ // builds it first); embedded so the compiler binary stays self-contained.
118+ // No self-spawned build here (unlike the runtime fallback) — knot-compile-rt
119+ // is always a normal dependency of the binary that drives linking.
120+ {
121+ let crt_dest = out_path.join("libknot_compile_rt.a");
122+ let _ = std::fs::remove_file(&crt_dest);
123+ let target_dir = std::env::var("CARGO_TARGET_DIR")
124+ .map(PathBuf::from)
125+ .unwrap_or_else(|_| workspace_root.join("target"));
126+ let crt_src = out_path
127+ .ancestors()
128+ .take_while(|p| p.starts_with(&target_dir))
129+ .map(|p| p.join("libknot_compile_rt.a"))
130+ .find(|p| is_valid_lib(p))
131+ .or_else(|| {
132+ ["release", "debug"]
133+ .iter()
134+ .map(|prof| target_dir.join(prof).join("libknot_compile_rt.a"))
135+ .find(|p| is_valid_lib(p))
136+ });
137+ if let Some(src) = crt_src {
138+ std::fs::copy(&src, &crt_dest).expect("failed to copy libknot_compile_rt.a to OUT_DIR");
139+ println!("cargo:rustc-cfg=has_embedded_compile_rt");
140+ }
141+ }
142+ println!("cargo:rustc-check-cfg=cfg(has_embedded_compile_rt)");
143+ println!("cargo:rerun-if-changed=../knot-compile-rt/src");
144+ println!("cargo:rerun-if-changed=../knot-compile-rt/Cargo.toml");
145+ // Watch the whole runtime source tree (cargo tracks directories
146+ // recursively), not just lib.rs — log.rs/tui.rs edits must also
147+ // refresh the embedded runtime.
148+ println!("cargo:rerun-if-changed=../knot-runtime/src");
149+ println!("cargo:rerun-if-changed=../knot-runtime/Cargo.toml");
150+
151+ // A-experiment: whole-archive link the knot-runtime staticlib into the
152+ // knot binary and export its knot_* symbols into the dynamic table so the
153+ // JIT (running in-process for compile-time predicate eval) can resolve
154+ // them via dlsym(RTLD_DEFAULT). The runtime is found the same way as the
155+ // embedded archive above; this only ADDS link directives.
156+ // --allow-multiple-definition: the staticlib bundles compiler_builtins
157+ // objects that the binary already links from rustc's rlib — whole-archive
158+ // would otherwise double-define them. The first (rustc) definition wins.
159+ {
160+ let target_dir = std::env::var("CARGO_TARGET_DIR")
161+ .map(PathBuf::from)
162+ .unwrap_or_else(|_| workspace_root.join("target"));
163+ let rt = out_path
164+ .ancestors()
165+ .take_while(|p| p.starts_with(&target_dir))
166+ .map(|p| p.join("libknot_runtime.a"))
167+ .find(|p| is_valid_lib(p))
168+ .or_else(|| {
169+ ["release", "debug"]
170+ .iter()
171+ .map(|prof| target_dir.join(prof).join("libknot_runtime.a"))
172+ .find(|p| is_valid_lib(p))
173+ });
174+ if let Some(rt) = rt {
175+ if cfg!(target_os = "macos") {
176+ println!("cargo:rustc-link-arg=-Wl,-force_load,{}", rt.display());
177+ println!("cargo:rustc-link-arg=-Wl,-export_dynamic");
178+ } else {
179+ println!("cargo:rustc-link-arg=-Wl,--allow-multiple-definition");
180+ println!(
181+ "cargo:rustc-link-arg=-Wl,--whole-archive,{},--no-whole-archive",
182+ rt.display()
183+ );
184+ println!("cargo:rustc-link-arg=-Wl,--export-dynamic");
185+ }
186+ }
187+ }
188+}
crates/knot-compiler/src/base.rs+461−82
@@ -1,107 +1,486 @@
1−//! Built-in trait declarations and standard implementations.
1+//! Standard prelude: ordinary polymorphic functions injected into every program.
22//!
3−//! Defines the core trait hierarchy (Eq, Ord, Num, Semigroup, Display, Functor,
4−//! Applicative, Monad, Alternative, Foldable) and primitive/[] implementations. Trait
5−//! declarations and simple impls are parsed from Knot source; complex [] impls
6−//! for HKT traits (Functor, Applicative, Monad, Foldable) are registered
7−//! directly in codegen to avoid span collision issues.
3+//! The prelude is a small set of plain functions. Builtin operator semantics
4+//! (`+`, `<`, `++`, unary `-`, `==`) are enforced intrinsically by the type
5+//! checker and code generator, and monadic do-blocks dispatch structurally, so
6+//! no trait declarations are needed here.
7+//!
8+//! A `.knot` file is a single expression, so the prelude is injected by
9+//! wrapping the program: `with { …prelude record… } <program>`. The program's
10+//! own bindings shadow the prelude's (a `with` body's later/literal bindings
11+//! win for the same name).
812
913use knot::ast;
10−use std::collections::HashSet;
1114
12−/// Knot source for built-in trait declarations and simple implementations.
13−/// Complex [] impls for HKT traits are registered directly in codegen.
15+/// Byte offset added to every parsed prelude span so prelude spans can never
16+/// collide with user-file spans (bug B39). Chosen far above any plausible real
17+/// file size and above `desugar::SYNTH_SPAN_BASE` (1 << 31) so it also clears
18+/// the synthesized monad-span range.
19+pub(crate) const PRELUDE_SPAN_OFFSET: usize = 1 << 40;
20+
21+/// Knot source for the standard prelude. The ONLY binding the prelude splices
22+/// into scope is `base` — a record holding every standard-library function.
23+/// User code refers to `base.println`, `base.map`, etc.; nothing else enters
24+/// the environment by default.
25+///
26+/// The prelude's own polymorphic helpers (`min`/`max`/`when`/`unless`) are
27+/// defined as nested lambdas inside the `base` record. The data declaration
28+/// `Ordering` is hoisted out (a `data` decl cannot be a record *field value*),
29+/// then re-exposed as `base.Ordering` by the compiler's ctor routing.
1430const PRELUDE_SOURCE: &str = r#"
15−trait Eq a where
16− eq : a -> a -> Bool
31+{
32+base {
33+_ min (\a b -> ? (a < b)
34+ Bool.True {} a
35+ Bool.False {} b)
1736
18−data Ordering = LT {} | EQ {} | GT {}
37+_ max (\a b -> ? (a > b)
38+ Bool.True {} a
39+ Bool.False {} b)
1940
20−trait Eq a => Ord a where
21− compare : a -> a -> Ordering
41+-- `the (Type) value`: type ascription as a regular type-witness function. The
42+-- leading `\(Type T)` parameter is an erased type argument (no runtime
43+-- representation); `the` returns its value argument unchanged, so
44+-- `base.the (Int 1) 41` is `41` checked to be an `Int 1`. The compiler consumes
45+-- the type argument at the call site (the type-witness diversion) and erases it.
46+_ the (\(Type T) -> \x -> x)
2247
23−trait Functor (f : Type -> Type) where
24− map : (a -> b) -> f a -> f b
48+Bool -> IO {} -> IO {} when (\cond action -> ? (cond)
49+ Bool.True {} action
50+ Bool.False {} yield {})
2551
26−trait Functor f => Applicative (f : Type -> Type) where
27− yield : a -> f a
28− ap : f (a -> b) -> f a -> f b
52+Bool -> IO {} -> IO {} unless (\cond action -> ? (cond)
53+ Bool.True {} yield {}
54+ Bool.False {} action)
2955
30−trait Applicative m => Monad (m : Type -> Type) where
31− bind : (a -> m b) -> m a -> m b
56+-- Structured logging. `log` carries a `(<>logCtx)` fold constraint: at each
57+-- callsite the compiler merges every in-scope `logCtx` record (`base.unify`
58+-- from `{}`, innermost scope wins) and passes the merged record as the hidden
59+-- dictionary, so the caller writes only the level and message. The body reads
60+-- the merged context via `^logCtx` and emits through `knot_emit_log`
61+-- (`emitLog`). The `debug`/`info`/`warn`/`error` wrappers fix the level; each
62+-- is self-contained (a record field can't reference a sibling bare) and
63+-- re-declares the constraint so the caller's context threads through.
64+(<>logCtx) => Level -> Text -> IO {} log (\level msg -> emitLog level msg ^logCtx)
3265
33−trait Applicative f => Alternative (f : Type -> Type) where
34− empty : f a
35− alt : f a -> f a -> f a
66+(<>logCtx) => Text -> IO {} debug (\msg -> emitLog (Level.Debug {}) msg ^logCtx)
3667
37−trait Foldable (t : Type -> Type) where
38− fold : (b -> a -> b) -> b -> t a -> b
68+(<>logCtx) => Text -> IO {} info (\msg -> emitLog (Level.Info {}) msg ^logCtx)
3969
40−trait Eq a => Num a where
41− add : a -> a -> a
42− sub : a -> a -> a
43− mul : a -> a -> a
44− div : a -> a -> a
45− negate : a -> a
70+(<>logCtx) => Text -> IO {} warn (\msg -> emitLog (Level.Warn {}) msg ^logCtx)
4671
47−trait Semigroup a where
48− append : a -> a -> a
72+(<>logCtx) => Text -> IO {} error (\msg -> emitLog (Level.Error {}) msg ^logCtx)
4973
50−trait Display a where
51− display : a -> Text
74+-- List ADT namespace (`base.list.*`). Each field is a codegen builtin
75+-- (`Var(listX)` resolves to the registered knot_list_* function value), so
76+-- the prelude record needs no self-reference for recursion. `nil` is the
77+-- `listNil` builtin (a 1-arg fn that ignores its argument and returns the
78+-- empty list); call it as `base.list.nil {}` so the element type stays
79+-- polymorphic per call site. The rest are the function values themselves.
80+list {
81+nil listNil
82+cons listCons
83+isNil listIsNil
84+head listHead
85+tail listTail
86+length listLength
87+map listMap
88+filter listFilter
89+fold listFold
90+reverse listReverse
91+append listAppend
92+fromRelation listFromRelation
93+toRelation listToRelation
94+}
5295
53−impl Display Int where
54− display x = show x
96+-- Vec namespace (`base.vec.*`): the concrete-sequence overloads of the data
97+-- ops, resolved via `^count` (etc.) against the relation-typed `base.*`
98+-- forms. The `^` resolver searches the full record with name+type matching,
99+-- so a `Vec a` argument reaches `base.vec.count` (nested here) even though
100+-- `base.count : [a] -> Int u` fails to unify with it.
101+vec {
102+count vecCount
103+}
55104
56−impl Display Float where
57− display x = show x
105+-- Num namespace (`base.num.*`): numeric negation, resolved TYPE-DIRECTED via
106+-- `^neg`. Each impl has an EXPLICIT concrete signature so the `^` resolver's
107+-- unify-based match picks by the argument's numeric type (an un-annotated body
108+-- could infer polymorphic and mis-dispatch — see `morph`). Both wrap the
109+-- `valueNegate` runtime primitive; the signature is what steers `^neg`.
110+-- Numeric negation is written `(^neg) x` (unary `-x` was removed).
111+num {
112+int { Int u -> Int u neg valueNegate }
113+float { Float u -> Float u neg valueNegate }
114+}
58115
59−impl Display Text where
60− display x = x
116+-- Morph namespace (`base.morph.*`): type-directed conversions resolved by the
117+-- `^into` projection. Each `<from>To<to>` record holds an `into : S -> T`
118+-- function with an EXPLICIT concrete signature (an un-annotated body could
119+-- infer a polymorphic type and silently mis-dispatch — see the `^into`
120+-- resolver's unify-based candidate match). Fallible conversions return
121+-- `Maybe`. Conversions needing a primitive not exposed as a base builtin are
122+-- written as lambdas over the existing builtins.
123+morph {
124+textToBytes { Text -> Bytes into textToBytes }
125+bytesToText { Bytes -> Maybe Text into bytesToText }
126+bytesToHex { Bytes -> Text into bytesToHex }
127+textToBytesFromHex { Text -> Maybe Bytes into bytesFromHex }
128+intToFloat { Int 1 -> Float 1 into intToFloat }
129+textToInt { Text -> Maybe (Int 1) into textToInt }
130+textToFloat { Text -> Maybe (Float 1) into textToFloat }
131+intToText { Int 1 -> Text into (\n -> show n) }
132+floatToText { Float 1 -> Text into (\f -> show f) }
133+boolToText { Bool -> Text into (\b -> show b) }
134+}
135+}
136+}
137+"#;
61138
62−impl Display Bool where
63− display x = show x
139+/// Stdlib builtins exposed as fields of `base` (`base.map`, `base.filter`, …).
140+/// Each is registered by codegen as a curried function value in `global_fns`;
141+/// the prelude record references them as `name: Var(name)` so the `base`
142+/// record assembles to real function values. Dispatch-only builtins
143+/// (`println`, `show`, `now`, …) and intrinsic constructors
144+/// (`Just`/`Nothing`/`Ok`/`Err`/`True`/`False`) are routed separately by the
145+/// compiler, not via this record.
146+pub(crate) const BASE_STDLIB_FNS: &[&str] = &[
147+ "all",
148+ "any",
149+ "appendFile",
150+ "avg",
151+ "bytesConcat",
152+ "bytesFromHex",
153+ "bytesGet",
154+ "bytesLength",
155+ "bytesSlice",
156+ "bytesToHex",
157+ "bytesToText",
158+ "chars",
159+ "contains",
160+ "countWhere",
161+ "decrypt",
162+ "diff",
163+ "distinct",
164+ "dress",
165+ "drop",
166+ "endsWith",
167+ "elem",
168+ "encrypt",
169+ "fileExists",
170+ "filter",
171+ "fold",
172+ "forEach",
173+ "fork",
174+ "hash",
175+ "hexDecode",
176+ "id",
177+ "inter",
178+ "length",
179+ "listDir",
180+ "map",
181+ "filterCtor",
182+ "maxOn",
183+ "minOn",
184+ "not",
185+ "parseJson",
186+ "race",
187+ "randomInt",
188+ "readFile",
189+ "removeFile",
190+ "reverse",
191+ "run",
192+ "run1",
193+ "sign",
194+ "sleep",
195+ "sortBy",
196+ "sortByDesc",
197+ "startsWith",
198+ "strip",
199+ "stripFloatUnit",
200+ "stripUnit",
201+ "take",
202+ "textToBytes",
203+ "toAsciiLower",
204+ "toAsciiUpper",
205+ "abs",
206+ "intMin",
207+ "intMax",
208+ "clamp",
209+ "unify",
210+ "toJson",
211+ "toLower",
212+ "toUpper",
213+ "traverse",
214+ "trim",
215+ "trimAscii",
216+ "ltrimAscii",
217+ "rtrimAscii",
218+ "byteLength",
219+ "upsertBy",
220+ "verify",
221+ "withFloatUnit",
222+ "withUnit",
223+ "writeFile",
224+ // Numeric/text conversions.
225+ "floor",
226+ "intToFloat",
227+ "textToInt",
228+ "textToFloat",
229+ // Relation query forms as first-class function values (`base.count`,
230+ // `base.union`, `base.sum`, `base.bind`). Each is registered as a curried
231+ // function value in codegen; the call-site SQL-pushdown optimization
232+ // recognizes both the bare (`count rel`) and namespaced (`base.count rel`)
233+ // application head. `bind` here is relation flatMap (`knot_relation_bind`).
234+ "count",
235+ "union",
236+ "sum",
237+ "bind",
238+ // Console IO builtins (registered as stdlib function values in codegen).
239+ "println",
240+ "print",
241+ "putLine",
242+ "show",
243+ // Value → evaluable Knot source (dependency-collecting). Registered as a
244+ // stdlib function value in codegen.
245+ "extract",
246+ // In-process JIT compile+eval: `compile : Text -> Maybe a`. Registered as
247+ // a stdlib function value in codegen (calls knot_builtin_compile, which
248+ // dispatches through the runtime's registered compile implementation).
249+ "compile",
250+ // 0-arg IO builtins. Each is a re-runnable IO thunk (`Value::IO(thunk, _)`)
251+ // produced fresh by the bare-`Var` dispatch, so holding the action as a
252+ // record field is safe — forcing `base.now` twice runs `knot_now` twice.
253+ "now",
254+ "readLine",
255+ "randomFloat",
256+ "randomUuid",
257+ "generateKeyPair",
258+ "generateSigningKeyPair",
259+];
64260
65−impl Alternative [] where
66− empty = []
67− alt a b = union a b
68−"#;
261+/// Names a bare user-span `Var` may NOT reference directly; the hard gate
262+/// (option A) requires `base.<name>` instead. This is every stdlib builtin
263+/// plus the prelude's polymorphic helpers. Constructors
264+/// (`Just`/`Nothing`/`Ok`/`Err`/`True`/`False`) are deliberately NOT here —
265+/// they route through the dedicated `base.Ctor` arms, which gate separately.
266+pub(crate) fn is_gated_stdlib(name: &str) -> bool {
267+ BASE_STDLIB_FNS.contains(&name) || matches!(name, "min" | "max" | "when" | "unless")
268+}
69269
70−/// Parse the prelude source and prepend its declarations to the user's module.
71−/// Skips traits and their impls if the user already defines the same trait.
72−pub fn inject_prelude(module: &mut ast::Module) {
73− // Collect user-defined trait names to avoid conflicts
74− let user_traits: HashSet<String> = module
75− .decls
76− .iter()
77− .filter_map(|d| {
78− if let ast::DeclKind::Trait { name, .. } = &d.node {
79− Some(name.clone())
80− } else {
81− None
82− }
83− })
84− .collect();
270+/// Server special forms (`fetch`/`fetchWith`/`listen`/`listenOn`) and the STM
271+/// primitive `retry`. These are compile-time macros, NOT `base` record fields,
272+/// but the bare form is still gated for consistency — the user must write
273+/// `base.fetch`, `base.listen`, `base.retry`. Codegen routes the namespaced
274+/// head through the same macro dispatch (`server_form_name`); `retry` keeps its
275+/// own "only inside `atomic`" check, which fires first.
276+pub(crate) fn is_gated_special_form(name: &str) -> bool {
277+ matches!(
278+ name,
279+ "fetch" | "fetchWith" | "listen" | "listenOn" | "retry" | "todo" | "trace"
280+ )
281+}
85282
86− // Parse prelude
283+/// Parse the prelude source and return the fully-assembled `base` record
284+/// expression (the prelude helpers `min`/`max`/`when`/`unless` plus every
285+/// stdlib builtin as a `name: Var(name)` field), with all spans shifted by
286+/// `PRELUDE_SPAN_OFFSET`. This is the single source of truth for the `base`
287+/// record's VALUE; infer (`bind_base_record`) and codegen
288+/// (`define_base_record`) each compile it to bind `base` globally — there is
289+/// no `with` wrapper around the program.
290+pub(crate) fn prelude_base_record() -> ast::Expr {
87291 let lexer = knot::lexer::Lexer::new(PRELUDE_SOURCE);
88− let (tokens, _) = lexer.tokenize();
292+ let (tokens, lex_diags) = lexer.tokenize();
293+ assert!(
294+ !lex_diags
295+ .iter()
296+ .any(|d| d.severity == knot::diagnostic::Severity::Error),
297+ "prelude failed to lex: {:?}",
298+ lex_diags
299+ );
89300 let parser = knot::parser::Parser::new(PRELUDE_SOURCE.to_string(), tokens);
90− let (prelude_module, _) = parser.parse_module();
91−
92− // Filter out traits/impls that the user already defines
93− let filtered: Vec<ast::Decl> = prelude_module
94− .decls
95− .into_iter()
96− .filter(|d| match &d.node {
97− ast::DeclKind::Trait { name, .. } => !user_traits.contains(name),
98− ast::DeclKind::Impl { trait_name, .. } => !user_traits.contains(trait_name),
99− _ => true,
100− })
101− .collect();
102−
103− // Prepend prelude declarations before user declarations
104− let mut all_decls = filtered;
105− all_decls.append(&mut module.decls);
106− module.decls = all_decls;
301+ let (mut prelude_record, parse_diags) = parser.parse_file_expr();
302+ assert!(
303+ !parse_diags
304+ .iter()
305+ .any(|d| d.severity == knot::diagnostic::Severity::Error),
306+ "prelude failed to parse: {:?}",
307+ parse_diags
308+ );
309+
310+ // Inject the stdlib builtins as fields of the nested `base` record.
311+ let dummy_span = ast::Span::new(0, 0);
312+ let mut base_record = None;
313+ if let ast::ExprKind::Record(outer) = &mut prelude_record.node
314+ && let Some(base_field) = outer.iter_mut().find(|f| f.name == "base")
315+ {
316+ if let ast::ExprKind::Record(base_fields) = &mut base_field.value.node {
317+ for name in BASE_STDLIB_FNS {
318+ base_fields.push(ast::RecordField {
319+ name: knot::ast::FieldName::Named((*name).to_string()),
320+ // Reference the stdlib fn by its FLATTENED `base.<name>`
321+ // key — a `Var` string containing a dot, unutterable in
322+ // user source, so it always resolves to the stdlib fn and
323+ // is never shadowed by a user decl named `<name>`.
324+ value: ast::Spanned::new(
325+ ast::ExprKind::Var(crate::infer::Binding::User(format!("base.{}", name))),
326+ dummy_span,
327+ ),
328+ sig: None,
329+ doc: None,
330+ });
331+ }
332+ }
333+ base_record = Some(base_field.value.clone());
334+ }
335+ let mut record = base_record.expect("prelude source has no `base` field");
336+ // Elaborate the constrained `base.*` fns (`log`, `info`, …): prepend the
337+ // hidden `__dict_<field>` parameter and rewrite the body's `^field` to it.
338+ // The prelude is parsed raw (no `desugar` pass), so without this their
339+ // `^field` would resolve against the prelude scope, not the dictionary
340+ // the callsite splices in.
341+ crate::desugar::elaborate_all_implicit_dicts(&mut record);
342+ shift_expr_spans(&mut record, PRELUDE_SPAN_OFFSET);
343+ record
344+}
345+
346+// ── Prelude span shifting (bug B39) ──────────────────────────────────
347+//
348+// Add `offset` to every declaration/expression/statement/pattern span in a
349+// prelude decl so prelude spans can never alias user-file spans in
350+// `monad_info` (and the other span-keyed inference maps). Type spans are left
351+// alone — they never key `monad_info`. Mirrors the AST shape walked by
352+// `unused::walk_decl`; keep the two in sync when the AST grows a node.
353+//
354+// "Every span" includes the standalone `name_span`/`api_span` fields, not just
355+// the `Spanned` wrappers: inference keys a punned record field's binder on
356+// `FieldPat::name_span`, so leaving it unshifted leaks a raw PRELUDE_SOURCE
357+// offset into `local_type_info` — a span the LSP cannot tell apart from a
358+// user span, since its provenance filter can only compare byte ranges. It
359+// then anchors an inlay hint at that offset in the user's file.
360+
361+fn shift_expr_spans(e: &mut ast::Expr, offset: usize) {
362+ use ast::ExprKind::*;
363+ e.span.start += offset;
364+ e.span.end += offset;
365+ match &mut e.node {
366+ TypeLiteral(_) => {}
367+ NameOf(inner) => shift_expr_spans(inner, offset),
368+ App { func, arg } => {
369+ shift_expr_spans(func, offset);
370+ shift_expr_spans(arg, offset);
371+ }
372+ With { record, body, .. } => {
373+ shift_expr_spans(record, offset);
374+ shift_expr_spans(body, offset);
375+ }
376+ Lambda { params, body, .. } => {
377+ for p in params {
378+ shift_pat_spans(p, offset);
379+ }
380+ shift_expr_spans(body, offset);
381+ }
382+ BinOp { lhs, rhs, .. } => {
383+ shift_expr_spans(lhs, offset);
384+ shift_expr_spans(rhs, offset);
385+ }
386+ UnaryOp { operand, .. } => shift_expr_spans(operand, offset),
387+ Case { scrutinee, arms } => {
388+ shift_expr_spans(scrutinee, offset);
389+ for arm in arms {
390+ shift_pat_spans(&mut arm.pat, offset);
391+ shift_expr_spans(&mut arm.body, offset);
392+ }
393+ }
394+ Do(stmts) => {
395+ for s in stmts {
396+ shift_stmt_spans(s, offset);
397+ }
398+ }
399+ Set { target, value } => {
400+ shift_expr_spans(target, offset);
401+ shift_expr_spans(value, offset);
402+ }
403+ Atomic(inner) | Refine(inner) => shift_expr_spans(inner, offset),
404+ TimeUnitLit { value, .. } => shift_expr_spans(value, offset),
405+ Record(fields) => {
406+ for fl in fields {
407+ shift_expr_spans(&mut fl.value, offset);
408+ }
409+ }
410+ List(items) => {
411+ for it in items {
412+ shift_expr_spans(it, offset);
413+ }
414+ }
415+ FieldAccess { expr, .. } | Annot { expr, .. } => shift_expr_spans(expr, offset),
416+ Serve {
417+ api_span, handlers, ..
418+ } => {
419+ api_span.start += offset;
420+ api_span.end += offset;
421+ for h in handlers {
422+ h.endpoint_span.start += offset;
423+ h.endpoint_span.end += offset;
424+ shift_expr_spans(&mut h.body, offset);
425+ }
426+ }
427+ Lit(_)
428+ | Var(_)
429+ | Constructor(_)
430+ | SourceRef { .. }
431+ | ImplicitRef(_)
432+ | CollectFold(_)
433+ | TypeHole => {}
434+ TypeCtor { .. } | SourceDecl { .. } | SubsetConstraint { .. } => {}
435+ ast::ExprKind::ArgDecl { .. } => {},
436+ RouteDecl { .. } => {}
437+ }
438+}
439+
440+fn shift_stmt_spans(s: &mut ast::Stmt, offset: usize) {
441+ use ast::StmtKind::*;
442+ s.span.start += offset;
443+ s.span.end += offset;
444+ match &mut s.node {
445+ Bind { pat, expr } => {
446+ shift_pat_spans(pat, offset);
447+ shift_expr_spans(expr, offset);
448+ }
449+ Where { cond } => shift_expr_spans(cond, offset),
450+ GroupBy { key } => shift_expr_spans(key, offset),
451+ Expr(e) => shift_expr_spans(e, offset),
452+ }
453+}
454+
455+fn shift_pat_spans(p: &mut ast::Pat, offset: usize) {
456+ use ast::PatKind::*;
457+ p.span.start += offset;
458+ p.span.end += offset;
459+ match &mut p.node {
460+ Var(_) | Wildcard | Lit(_) => {}
461+ Constructor { payload, .. } => shift_pat_spans(payload, offset),
462+ Record(fields) => {
463+ for fp in fields {
464+ // The field-name token's own span. For a punned field
465+ // (`{value}`) this IS the binder's span, and inference records
466+ // it in `binding_types` — so it must be shifted like any other
467+ // binder span, or it escapes as a raw prelude offset.
468+ fp.name_span.start += offset;
469+ fp.name_span.end += offset;
470+ if let Some(inner) = &mut fp.pattern {
471+ shift_pat_spans(inner, offset);
472+ }
473+ }
474+ }
475+ List(items) => {
476+ for it in items {
477+ shift_pat_spans(it, offset);
478+ }
479+ }
480+ Cons { head, tail } => {
481+ shift_pat_spans(head, offset);
482+ shift_pat_spans(tail, offset);
483+ }
484+ Annot { pat, .. } => shift_pat_spans(pat, offset),
485+ }
107486}
crates/knot-compiler/src/builtins.rsnew file+252
@@ -0,0 +1,252 @@
1+//! Centralized builtin name tables.
2+//!
3+//! These lists are the single source of truth for which builtins perform IO
4+//! and which are forbidden inside `atomic` blocks. Both the effect inferencer
5+//! (`effects.rs`) and the LSP (`knot_lsp::builtins`) reference this module so
6+//! the lists never drift apart.
7+
8+/// Builtins that perform a console effect (`println`, `print`, `logInfo`, ...).
9+pub const CONSOLE_BUILTINS: &[&str] = &[
10+ "println", "putLine", "print", "readLine", "logInfo", "logWarn", "logError", "logDebug",
11+];
12+
13+/// Builtins that read/manipulate the wall clock or sleep.
14+pub const CLOCK_BUILTINS: &[&str] = &["now", "sleep"];
15+
16+/// Builtins that consume randomness.
17+pub const RANDOM_BUILTINS: &[&str] = &[
18+ "randomInt",
19+ "randomFloat",
20+ "randomUuid",
21+ "generateKeyPair",
22+ "generateSigningKeyPair",
23+ "encrypt",
24+];
25+
26+/// Builtins that perform network IO.
27+pub const NETWORK_BUILTINS: &[&str] = &["listen", "listenOn", "fetch", "fetchWith"];
28+
29+/// Builtins that touch the filesystem.
30+pub const FS_BUILTINS: &[&str] = &[
31+ "readFile",
32+ "writeFile",
33+ "appendFile",
34+ "fileExists",
35+ "removeFile",
36+ "listDir",
37+];
38+
39+/// Concurrency builtins (`fork`, `retry`, `race`). These do not contribute IO
40+/// effects the same way as console/fs/etc.: `fork` propagates the effect row of
41+/// its spawned IO through its own result (`IO r {} -> IO r {}`), `retry` is the
42+/// STM primitive used inside atomic to trigger a retry, and `race` propagates
43+/// the effect rows of its arguments through its result.
44+pub const CONCURRENCY_BUILTINS: &[&str] = &["fork", "retry", "race"];
45+
46+/// Pure builtins. These are listed so the LSP can recognize them as user-callable
47+/// and so they sort sensibly in completion lists. They have no effects.
48+pub const PURE_BUILTINS: &[&str] = &[
49+ "show",
50+ "union",
51+ "count",
52+ "filter",
53+ "filterCtor",
54+ "map",
55+ "fold",
56+ "any",
57+ "all",
58+ "diff",
59+ "inter",
60+ "sum",
61+ "avg",
62+ "toUpper",
63+ "toLower",
64+ "take",
65+ "drop",
66+ "length",
67+ "trim",
68+ "contains",
69+ "elem",
70+ "reverse",
71+ "chars",
72+ "id",
73+ "not",
74+ "toJson",
75+ "parseJson",
76+ "decrypt",
77+ "sign",
78+ "verify",
79+ "upsertBy",
80+ "countWhere",
81+ "minOn",
82+ "maxOn",
83+ "sortBy",
84+ "floor",
85+ "intToFloat",
86+ "textToInt",
87+ "textToFloat",
88+ "stripUnit",
89+ "withUnit",
90+ "stripFloatUnit",
91+ "withFloatUnit",
92+ "strip",
93+ "dress",
94+ // The unimplemented hole: polymorphic `∀a. a`, aborts at runtime with a
95+ // full source-context report. Effect-free in the type system (like
96+ // `retry`); dispatch-only at codegen, never a real `base` record field.
97+ "todo",
98+ // The tracer: polymorphic `∀a. a -> a`, prints the traced value (with the
99+ // same source-context + in-scope-bindings report as `todo`) and returns it
100+ // unchanged. Dispatch-only at codegen, never a real `base` record field.
101+ "trace",
102+];
103+
104+/// Set of builtin function names that perform some IO effect. Used by the LSP
105+/// to mark them with the `effectful` semantic-token modifier and to filter
106+/// them out of atomic-context completions.
107+pub const EFFECTFUL_BUILTINS: &[&str] = &[
108+ // console
109+ "println",
110+ "putLine",
111+ "print",
112+ "readLine",
113+ "logInfo",
114+ "logWarn",
115+ "logError",
116+ "logDebug",
117+ // clock
118+ "now",
119+ "sleep",
120+ // random
121+ "randomInt",
122+ "randomFloat",
123+ "randomUuid",
124+ "generateKeyPair",
125+ "generateSigningKeyPair",
126+ "encrypt",
127+ // network
128+ "listen",
129+ "listenOn",
130+ "fetch",
131+ "fetchWith",
132+ // fs
133+ "readFile",
134+ "writeFile",
135+ "appendFile",
136+ "fileExists",
137+ "removeFile",
138+ "listDir",
139+ // concurrency
140+ "fork",
141+ "retry",
142+ "race",
143+];
144+
145+/// Zero-argument IO builtins: referencing the bare name *is* the IO action,
146+/// so a bare `Var` reference legitimately carries the builtin's effects. Every
147+/// other effectful builtin takes arguments and performs no IO until applied —
148+/// a bare reference to it (e.g. `let f = println`, never called) is pure, and
149+/// its effects must only manifest at the call site.
150+pub const NULLARY_IO_BUILTINS: &[&str] = &[
151+ "now",
152+ "readLine",
153+ "randomFloat",
154+ "randomUuid",
155+ "generateKeyPair",
156+ "generateSigningKeyPair",
157+];
158+
159+/// Builtins whose effects cannot be rolled back by the savepoint-based atomic
160+/// machinery. The effect checker rejects any of these inside an `atomic` block;
161+/// the LSP also filters them from atomic-context completion lists.
162+///
163+/// `fork` and `retry` are intentionally absent: `fork`'s spawned IO runs in an
164+/// independent transaction (its effects propagate through the type but the work
165+/// happens on its own connection), and `retry` is the STM primitive used inside
166+/// atomic to trigger a retry.
167+pub const ATOMIC_DISALLOWED_BUILTINS: &[&str] = &[
168+ "println",
169+ "putLine",
170+ "print",
171+ "readLine",
172+ "logInfo",
173+ "logWarn",
174+ "logError",
175+ "logDebug",
176+ "now",
177+ "sleep",
178+ "randomInt",
179+ "randomFloat",
180+ "randomUuid",
181+ "generateKeyPair",
182+ "generateSigningKeyPair",
183+ "encrypt",
184+ "listen",
185+ "listenOn",
186+ "fetch",
187+ "fetchWith",
188+ "readFile",
189+ "writeFile",
190+ "appendFile",
191+ "fileExists",
192+ "removeFile",
193+ "listDir",
194+ "race",
195+];
196+
197+/// Built-in trait method names (Eq.eq, Ord.compare, Num.add, etc.). Resolved
198+/// at codegen time via runtime tag dispatch.
199+pub const TRAIT_METHOD_BUILTINS: &[&str] = &[
200+ "eq", "compare", "ap", "bind", "alt", "empty", "add", "sub", "mul", "div", "negate", "append",
201+ "yield", "display",
202+];
203+
204+/// Bytes builtins. Pure but kept separate so the LSP/codegen can recognize
205+/// them as user-callable and they sort sensibly in completion lists.
206+pub const BYTES_BUILTINS: &[&str] = &[
207+ "bytesLength",
208+ "bytesSlice",
209+ "bytesConcat",
210+ "textToBytes",
211+ "bytesToText",
212+ "bytesToHex",
213+ "bytesFromHex",
214+ "hexDecode",
215+ "bytesGet",
216+ "hash",
217+];
218+
219+/// Internal desugaring helpers emitted by the desugarer. Not user-facing.
220+pub const INTERNAL_BUILTINS: &[&str] = &["__bind", "__yield", "__empty"];
221+
222+/// Single source of truth: every name the compiler treats as a builtin.
223+/// Used by `codegen::is_builtin_name` (free-variable filtering) and the LSP
224+/// completion suggestions. Adding a new builtin should only require editing
225+/// this file and the codegen registration site.
226+pub const ALL_BUILTINS: &[&[&str]] = &[
227+ CONSOLE_BUILTINS,
228+ CLOCK_BUILTINS,
229+ RANDOM_BUILTINS,
230+ NETWORK_BUILTINS,
231+ FS_BUILTINS,
232+ CONCURRENCY_BUILTINS,
233+ PURE_BUILTINS,
234+ TRAIT_METHOD_BUILTINS,
235+ BYTES_BUILTINS,
236+ INTERNAL_BUILTINS,
237+];
238+
239+/// True if `name` is a known builtin (any category).
240+pub fn is_builtin(name: &str) -> bool {
241+ ALL_BUILTINS.iter().any(|list| list.contains(&name))
242+}
243+
244+/// True if `name` is an effectful builtin — i.e. calling it produces an IO
245+/// value. Excludes `retry` (the STM primitive, typed `∀a. a` — no IO).
246+/// `fork` is included: it returns `IO {} {}` and its argument's effects are
247+/// decoupled from the caller, but callers redundantly check `|| name == "fork"`
248+/// to ensure it's recognized. `race` is included because the result IO
249+/// inherits the effect row of its arguments.
250+pub fn is_io_builtin(name: &str) -> bool {
251+ EFFECTFUL_BUILTINS.contains(&name) && !matches!(name, "retry")
252+}
crates/knot-compiler/src/codegen.rs+19946−5237

This diff is too big to show here.

crates/knot-compiler/src/decl_view.rsnew file+176

This diff is too big to show here.

crates/knot-compiler/src/desugar.rs+1233−541

This diff is too big to show here.

crates/knot-compiler/src/effects.rsdeleted−991

This diff is too big to show here.

crates/knot-compiler/src/infer.rs+14195−2000

This diff is too big to show here.

crates/knot-compiler/src/lib.rs+8−3

This diff is too big to show here.

crates/knot-compiler/src/linker.rs+42−6

This diff is too big to show here.

crates/knot-compiler/src/lockfile.rs+601−373

This diff is too big to show here.

crates/knot-compiler/src/main.rs+1076−57

This diff is too big to show here.

crates/knot-compiler/src/modules.rsdeleted−205

This diff is too big to show here.

crates/knot-compiler/src/nonterm.rsnew file+384

This diff is too big to show here.

crates/knot-compiler/src/refine_smt.rsnew file+254

This diff is too big to show here.

crates/knot-compiler/src/sql_lint.rsnew file+1247

This diff is too big to show here.

crates/knot-compiler/src/stack.rsnew file+90

This diff is too big to show here.

crates/knot-compiler/src/stratify.rs+672−233

This diff is too big to show here.

crates/knot-compiler/src/types.rs+2250−262

This diff is too big to show here.

crates/knot-compiler/src/unused.rsnew file+403

This diff is too big to show here.

crates/knot-compiler/src/unwind.rsnew file+333

This diff is too big to show here.

crates/knot-compiler/tests/base_listing.rsnew file+73

This diff is too big to show here.

crates/knot-compiler/tests/compile_boundary.rsnew file+334

This diff is too big to show here.

crates/knot-compiler/tests/compile_panic_containment.rsnew file+51

This diff is too big to show here.

crates/knot-compiler/tests/descriptor_roundtrip.rsnew file+84

This diff is too big to show here.

crates/knot-descriptor/Cargo.tomlnew file+6

This diff is too big to show here.

crates/knot-descriptor/src/lib.rsnew file+376

This diff is too big to show here.

crates/knot-jit/Cargo.tomlnew file+25

This diff is too big to show here.

crates/knot-jit/src/lib.rsnew file+365

This diff is too big to show here.

crates/knot-jit/tests/adts.rsnew file+109

This diff is too big to show here.

crates/knot-jit/tests/cli_overrides.rsnew file+982

This diff is too big to show here.

crates/knot-jit/tests/concurrency.rsnew file+94

This diff is too big to show here.

crates/knot-jit/tests/constraints.rsnew file+115

This diff is too big to show here.

crates/knot-jit/tests/crypto.rsnew file+116

This diff is too big to show here.

crates/knot-jit/tests/e2e.rsnew file+149

This diff is too big to show here.

crates/knot-jit/tests/errors.rsnew file+111

This diff is too big to show here.

crates/knot-jit/tests/extract.rsnew file+438

This diff is too big to show here.

crates/knot-jit/tests/functions.rsnew file+304

This diff is too big to show here.

crates/knot-jit/tests/harness.rsnew file+139

This diff is too big to show here.

crates/knot-jit/tests/http.rsnew file+166

This diff is too big to show here.

crates/knot-jit/tests/jit_e2e.rsnew file+197

This diff is too big to show here.

crates/knot-jit/tests/lists.rsnew file+132

This diff is too big to show here.

crates/knot-jit/tests/literal_pushdown.rsnew file+155

This diff is too big to show here.

crates/knot-jit/tests/migration.rsnew file+485

This diff is too big to show here.

crates/knot-jit/tests/migration_corner.rsnew file+1431

This diff is too big to show here.

crates/knot-jit/tests/nested_and_meta.rsnew file+85

This diff is too big to show here.

crates/knot-jit/tests/numeric_text.rsnew file+154

This diff is too big to show here.

crates/knot-jit/tests/patterns.rsnew file+123

This diff is too big to show here.

crates/knot-jit/tests/persistence.rsnew file+968

This diff is too big to show here.

crates/knot-jit/tests/refinements.rsnew file+273

This diff is too big to show here.

crates/knot-jit/tests/relations.rsnew file+292

This diff is too big to show here.

crates/knot-jit/tests/scalars.rsnew file+238

This diff is too big to show here.

crates/knot-jit/tests/single_value.rsnew file+174

This diff is too big to show here.

crates/knot-jit/tests/smoke.rsnew file+31

This diff is too big to show here.

crates/knot-jit/tests/text_bytes.rsnew file+120

This diff is too big to show here.

crates/knot-jit/tests/type_errors.rsnew file+107

This diff is too big to show here.

crates/knot-jit/tests/units.rsnew file+103

This diff is too big to show here.

crates/knot-jit/tests/views.rsnew file+68

This diff is too big to show here.

crates/knot-lsp/Cargo.toml+1

This diff is too big to show here.

crates/knot-lsp/src/analysis.rsnew file+740

This diff is too big to show here.

crates/knot-lsp/src/builtins.rsnew file+9

This diff is too big to show here.

crates/knot-lsp/src/call_hierarchy.rsnew file+295

This diff is too big to show here.

crates/knot-lsp/src/code_action.rsnew file+1804

This diff is too big to show here.

crates/knot-lsp/src/code_lens.rsnew file+124

This diff is too big to show here.

crates/knot-lsp/src/completion.rsnew file+1727

This diff is too big to show here.

crates/knot-lsp/src/defs.rsnew file+665

This diff is too big to show here.

crates/knot-lsp/src/diagnostics.rsnew file+223

This diff is too big to show here.

crates/knot-lsp/src/document_highlight.rsnew file+87

This diff is too big to show here.

crates/knot-lsp/src/document_symbol.rsnew file+254

This diff is too big to show here.

crates/knot-lsp/src/folding.rsnew file+153

This diff is too big to show here.

crates/knot-lsp/src/formatting.rsnew file+282

This diff is too big to show here.

crates/knot-lsp/src/goto.rsnew file+176

This diff is too big to show here.

crates/knot-lsp/src/hover.rsnew file+547

This diff is too big to show here.

crates/knot-lsp/src/incremental.rsnew file+535

This diff is too big to show here.

crates/knot-lsp/src/inlay_hints.rsnew file+1219

This diff is too big to show here.

crates/knot-lsp/src/legend.rsnew file+53

This diff is too big to show here.

crates/knot-lsp/src/lib.rsnew file+1

This diff is too big to show here.

crates/knot-lsp/src/linked_editing.rsnew file+334

This diff is too big to show here.

crates/knot-lsp/src/main.rs+1274−1494

This diff is too big to show here.

crates/knot-lsp/src/parsed_type.rsnew file+743

This diff is too big to show here.

crates/knot-lsp/src/references.rsnew file+153

This diff is too big to show here.

crates/knot-lsp/src/rename.rsnew file+1090

This diff is too big to show here.

crates/knot-lsp/src/selection_range.rsnew file+182

This diff is too big to show here.

crates/knot-lsp/src/semantic_tokens.rsnew file+824

This diff is too big to show here.

crates/knot-lsp/src/shared.rsnew file+1147

This diff is too big to show here.

crates/knot-lsp/src/signature_help.rsnew file+285

This diff is too big to show here.

crates/knot-lsp/src/state.rsnew file+804

This diff is too big to show here.

crates/knot-lsp/src/type_format.rsnew file+345

This diff is too big to show here.

crates/knot-lsp/src/utils.rsnew file+556

This diff is too big to show here.

crates/knot-lsp/src/workspace_diagnostics.rsnew file+722

This diff is too big to show here.

crates/knot-lsp/src/workspace_symbol.rsnew file+212

This diff is too big to show here.

crates/knot-runtime/Cargo.toml+19−6

This diff is too big to show here.

crates/knot-runtime/src/db_dump.rsnew file+143

This diff is too big to show here.

crates/knot-runtime/src/lib.rs+24073−5598

This diff is too big to show here.

crates/knot-runtime/src/log.rsnew file+305

This diff is too big to show here.

crates/knot-runtime/src/tui.rs+293−236

This diff is too big to show here.

crates/knot/src/ast.rs+525−216

This diff is too big to show here.

crates/knot/src/diagnostic.rs+111−77

This diff is too big to show here.

crates/knot/src/format.rsnew file+1523

This diff is too big to show here.

crates/knot/src/lexer.rs+551−358

This diff is too big to show here.

crates/knot/src/lib.rs+1

This diff is too big to show here.

crates/knot/src/parser.rs+3315−2127

This diff is too big to show here.

crates/knot/tests/parser.rsdeleted−3476

This diff is too big to show here.

editors/vscode/.gitignorenew file+3

This diff is too big to show here.

editors/vscode/.vscodeignorenew file+4

This diff is too big to show here.

editors/vscode/icon.pngnew file

Binary file not shown.

editors/vscode/language-configuration.jsonnew file+27

This diff is too big to show here.

editors/vscode/package-lock.jsonnew file+3027

This diff is too big to show here.

editors/vscode/package.jsonnew file+98

This diff is too big to show here.

editors/vscode/src/extension.tsnew file+119

This diff is too big to show here.

editors/vscode/syntaxes/knot.tmLanguage.jsonnew file+299

This diff is too big to show here.

editors/vscode/tsconfig.jsonnew file+15

This diff is too big to show here.

examples/.gitignorenew file+1

This diff is too big to show here.

examples/apiserver.knotnew file+92

This diff is too big to show here.

examples/arith_aggregate.knotnew file+23

This diff is too big to show here.

examples/arith_aggregate.schema.locknew file+7

This diff is too big to show here.

examples/associated_types.knotdeleted−21

This diff is too big to show here.

examples/builtins.knotnew file+31

This diff is too big to show here.

examples/bytes.knot+54−36

This diff is too big to show here.

examples/cons_pattern.knotnew file+16

This diff is too big to show here.

examples/constraints.knot+15−15

This diff is too big to show here.

examples/constraints.schema.lock+6−9

This diff is too big to show here.

examples/crypto.knot+51−31

This diff is too big to show here.

examples/fold_dictionaries.knotnew file+20

This diff is too big to show here.

examples/fold_stream.knotnew file+33

This diff is too big to show here.

examples/fold_stream.schema.locknew file+7

This diff is too big to show here.

examples/for_each.knotnew file+12

This diff is too big to show here.

examples/forall.knotnew file+27

This diff is too big to show here.

examples/groupby.knot+17−16

This diff is too big to show here.

examples/groupby.schema.lockdeleted−8

This diff is too big to show here.

examples/hash.knotnew file+25

This diff is too big to show here.

examples/hello.knot+7−6

This diff is too big to show here.

examples/hello.schema.lock+5−6

This diff is too big to show here.

examples/hole.knotnew file+35

This diff is too big to show here.

examples/implicit_dictionaries.knotnew file+31

This diff is too big to show here.

examples/inline_pushdown.knotnew file+55

This diff is too big to show here.

examples/inline_pushdown.schema.locknew file+7

This diff is too big to show here.

examples/json.knot+24−20

This diff is too big to show here.

examples/let_pushdown.knotnew file+39

This diff is too big to show here.

examples/log_test.knotnew file+12

This diff is too big to show here.

examples/maybe.knot+34−62

This diff is too big to show here.

examples/migrate.knotnew file+34

This diff is too big to show here.

examples/modules/lib/core.knotdeleted−1

This diff is too big to show here.

examples/modules/lib/helpers.knotdeleted−3

This diff is too big to show here.

examples/modules/main.knotdeleted−10

This diff is too big to show here.

examples/modules/main.schema.lockdeleted−21

This diff is too big to show here.

examples/modules/transitive.knotdeleted−5

This diff is too big to show here.

examples/modules/types.knotdeleted−5

This diff is too big to show here.

examples/modules/utils.knotdeleted−3

This diff is too big to show here.

examples/morph.knotnew file+45

This diff is too big to show here.

examples/nested.knot+27−17

This diff is too big to show here.

examples/nested.schema.lock+5−6

This diff is too big to show here.

examples/ord.knotnew file+61

This diff is too big to show here.

examples/query_opt.knot+119−85

This diff is too big to show here.

examples/query_opt.schema.lock+7−11

This diff is too big to show here.

examples/race.knotnew file+30

This diff is too big to show here.

examples/recursive.knot+18−24

This diff is too big to show here.

examples/recursive.schema.lock+4−4

This diff is too big to show here.

examples/result.knot+30−51

This diff is too big to show here.

examples/routes.knot+33−32

This diff is too big to show here.

examples/routes.schema.lockdeleted−12

This diff is too big to show here.

examples/showcase.knot+407−268

This diff is too big to show here.

examples/showcase.schema.lock+28−88

This diff is too big to show here.

examples/stm_row_filter.knotnew file+35

This diff is too big to show here.

examples/stm_row_filter.schema.locknew file+7

This diff is too big to show here.

examples/temporal.knotdeleted−29

This diff is too big to show here.

examples/temporal.schema.lockdeleted−5

This diff is too big to show here.

examples/todo.knot+20−18

This diff is too big to show here.

examples/todo.schema.lock+5−6

This diff is too big to show here.

examples/trait_replacement.knotnew file+25

This diff is too big to show here.

examples/trait_replacement.onew file

Binary file not shown.

examples/traits.knotdeleted−58

This diff is too big to show here.

examples/uuid.knotnew file+8

This diff is too big to show here.

examples/views.knotdeleted−45

This diff is too big to show here.

examples/views.schema.lockdeleted−15

This diff is too big to show here.

examples/views_history.knotdeleted−49

This diff is too big to show here.

examples/views_history.schema.lockdeleted−11

This diff is too big to show here.

examples/when_unless.knotnew file+7

This diff is too big to show here.

knot.mddeleted−812

This diff is too big to show here.

logo.pngnew file

Binary file not shown.

stdlib.mddeleted−658

This diff is too big to show here.

tree-sitter-knot/grammar.js+196−38

This diff is too big to show here.

tree-sitter-knot/queries/folds.scm+5

This diff is too big to show here.

tree-sitter-knot/queries/highlights.scm+107−15

This diff is too big to show here.

tree-sitter-knot/queries/indents.scm+4

This diff is too big to show here.

tree-sitter-knot/queries/locals.scm+9−3

This diff is too big to show here.

tree-sitter-knot/queries/tags.scm+24−2

This diff is too big to show here.

tree-sitter-knot/src/grammar.json+1050−71

This diff is too big to show here.

tree-sitter-knot/src/node-types.json+1577−117

This diff is too big to show here.

tree-sitter-knot/src/parser.c+20699−14193

This diff is too big to show here.