@amritk/nish-x86_64-linux 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/INSTALL.md +22 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +44 -0
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-os.c +1 -1
  29. package/runtime/runtime-parallel.c +1 -1
  30. package/runtime/runtime-simd.c +231 -7
  31. package/runtime/runtime-term.c +504 -0
  32. package/runtime/runtime-wasm.c +0 -11
  33. package/runtime/runtime.c +190 -6
  34. package/runtime/shim.mjs +6 -985
  35. package/runtime/sqlite/LICENSE.md +85 -0
  36. package/runtime/sqlite/README.md +45 -0
  37. package/runtime/sqlite/nish-sqlite.c +359 -0
  38. package/runtime/sqlite/sqlite3.c +271131 -0
  39. package/runtime/sqlite/sqlite3.h +14447 -0
  40. package/scripts/build.sh +261 -53
  41. package/std/LICENSE-fast-float +23 -0
  42. package/std/LICENSE-fdlibm +8 -0
  43. package/std/README.md +31 -11
  44. package/std/base64.ts +116 -0
  45. package/std/bytes.ts +256 -0
  46. package/std/cli.ts +328 -0
  47. package/std/collections.ts +6 -5
  48. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  49. package/std/crypto/base64url.ts +125 -0
  50. package/std/crypto/bcrypt.ts +760 -0
  51. package/std/crypto/hmac.ts +138 -2
  52. package/std/crypto/x509.ts +11 -122
  53. package/std/json-tokenizer.ts +2998 -57
  54. package/std/json-value.ts +22 -3
  55. package/std/json.ts +83 -1
  56. package/std/math.ts +608 -0
  57. package/std/net/http1-server.ts +85 -8
  58. package/std/net/http3-server.ts +50 -12
  59. package/std/net/postgres.ts +1875 -0
  60. package/std/net/quic-listener.ts +20 -4
  61. package/std/regex.ts +4274 -0
  62. package/std/sqlite.ts +391 -0
  63. package/std/term-io.ts +978 -0
  64. package/std/term-pixels.ts +874 -0
  65. package/std/term-probe.ts +459 -0
  66. package/std/term-rate.ts +993 -0
  67. package/std/term.ts +2453 -0
  68. package/std/text.ts +90 -7
  69. package/std/yaml-lines.ts +141 -0
  70. package/std/yaml.ts +4675 -0
package/docs/AI.md ADDED
@@ -0,0 +1,2759 @@
1
+ # Writing Nish: the rules card
2
+
3
+ This is the **whole language, stated as rules, for a model that has to write
4
+ Nish and get it accepted on the first try**. It is a digest, not the authority:
5
+ [`LANGUAGE.md`](LANGUAGE.md) is normative and settles every disagreement, and
6
+ [`IR_COOKBOOK.md`](IR_COOKBOOK.md) shows the exact IR each construct lowers to.
7
+ This page exists because LANGUAGE.md is 2,300 lines written to be *read*, and
8
+ what an author needs is the rules in one pass with the traps first.
9
+
10
+ > Working **on the compiler** rather than **in the language**? That is a
11
+ > different document: [`AGENTS.md`](../AGENTS.md) and [`.claude/`](../.claude/).
12
+
13
+ Nish is a strictly static subset of TypeScript compiled ahead of time to LLVM
14
+ IR. Every Nish program is legal TypeScript *syntax* — it is parsed by the
15
+ official TypeScript parser — and with
16
+ [`runtime/nish.d.ts`](../runtime/nish.d.ts) on the include path it type-checks
17
+ under `tsc --strict` too. That is the trap this page exists for: **your
18
+ TypeScript instincts produce programs that parse, that `tsc` accepts, and that
19
+ `nish` rejects.** The subset is much smaller than it looks.
20
+
21
+ Contents: [The loop](#the-loop-compile-read-the-code-fix) ·
22
+ [A whole program](#a-whole-program) ·
23
+ [What you must unlearn](#what-you-must-unlearn) · [Types](#types) ·
24
+ [Failure](#failure-result-not-exceptions) · [Absence](#absence-nullable-types) ·
25
+ [Declarations](#declarations) · [Generics](#generic-functions) ·
26
+ [Calling C](#calling-c) · [Statements](#statements) ·
27
+ [Expressions](#expressions) · [The builtins, in full](#the-builtins-in-full) ·
28
+ [Memory](#memory) · [Recipes](#recipes-for-what-is-missing) ·
29
+ [Before you say it compiles](#before-you-say-it-compiles)
30
+
31
+ ## The loop: compile, read the code, fix
32
+
33
+ Do not reason about whether a program is legal. Ask the compiler — it is fast,
34
+ and it is the only authority.
35
+
36
+ ```bash
37
+ nish program.ts --json # one JSON object per diagnostic, on stdout
38
+ nish --fix program.ts # apply every machine-applicable fix, then report what is left
39
+ nish program.ts -o out.ll # emit LLVM IR
40
+ nish program.ts --link prog # build a native binary (needs clang)
41
+ nish run program.ts a b # build into a cache, then run it with `a b`
42
+ # (the C runtime is cached too: ~/.cache/nish/runtime)
43
+ nish --help # the full flag list, stdout, exit 0; plain text
44
+ # under an agent (NISH_AGENT=1, CLAUDECODE, GEMINI_CLI)
45
+ ```
46
+
47
+ `--json` is the surface to automate against. One flat object per line on
48
+ stdout, nothing on stderr, every field 1-based with `endLine`/`endColumn`
49
+ exclusive:
50
+
51
+ ```json
52
+ {"file":"p.ts","line":3,"column":17,"endLine":3,"endColumn":20,"severity":"error","code":"NL2249","message":"Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill)"}
53
+ ```
54
+
55
+ - **Key on `code`, never on `message`.** A code is a promise: `NL2249` means
56
+ the same rule next release. The prose may improve; the code may not.
57
+ - **`severity`** is `"error"`, `"performance"`, `"portability"` or
58
+ `"deprecation"`. A warning of any kind never changes the exit code — it is
59
+ advice, not a rejection. Portability warnings, the sites where the program's
60
+ TypeScript reading answers differently, print only under
61
+ `--warn-portability`; a deprecation warning names a call a later release
62
+ takes away and what to write instead, and always prints.
63
+ - **Bands**: `NL1xxx` the Phase 0 forbidden-syntax sweep, `NL2xxx` the checker,
64
+ `NL3xxx` the driver and modules, `NL4xxx` the interop sidecars, `NL7xxx`
65
+ deprecation, `NL8xxx` portability, `NL9xxx` performance, `NL0001` syntax, `NL0002` toolchain, `NL0003` internal,
66
+ `NL0000` a diagnostic with no rule yet.
67
+ - **Exit codes**: `0` ok, `1` the program was rejected, `2` usage, `3` the C
68
+ toolchain is unusable, `70` an internal compiler error — that last one is a
69
+ bug in `nish`, not in your program, and is worth reporting. `nish run`
70
+ answers the same codes until the program starts, and the program's own
71
+ status after that.
72
+ - Every failure is a `--json` object, toolchain and internal errors included,
73
+ so you never have to parse stderr to find out why a run failed.
74
+
75
+ Some diagnostics carry a **`fix`**: the edits that make the line compile,
76
+ when the rewrite is mechanical and keeps the program's meaning. It comes last,
77
+ and each edit is placed exactly as the diagnostic is — `text` replaces the span,
78
+ and a span whose start equals its end is an insertion:
79
+
80
+ ```json
81
+ {"file":"p.ts","line":2,"column":10,"endLine":2,"endColumn":16,"severity":"error","code":"NL1047","message":"Loose equality is forbidden; use === / !==","fix":[{"line":2,"column":12,"endLine":2,"endColumn":14,"text":"==="}]}
82
+ ```
83
+
84
+ A diagnostic with no safe rewrite has no `fix` key at all. `nish --fix
85
+ program.ts` applies every fix to the files named on its command line (never to
86
+ an import from `std/` or `node_modules`), recompiles, and repeats until nothing
87
+ changes or five rounds pass; then it reports what is left exactly as a plain
88
+ run would, `--json` included, and exits with that run's code. It writes no IR,
89
+ so it refuses `-o` and `--link`. It rewrites your files in place, with no
90
+ backup, so run it on a committed or backed-up tree. Run it first, then read
91
+ what remains. A warning can carry a fix too, and `--fix` applies it only when the program
92
+ compiles and the warning would be printed: `NL9007`'s inserts the guard that
93
+ proves an index in range (not under `--no-warn-performance`), and `NL7002`'s
94
+ migrates a site off `--unchecked-indexing` (below).
95
+
96
+ Two more surfaces worth knowing: `nish --emit-ast f.ts` prints what was parsed
97
+ and `nish --emit-checked f.ts` prints the side tables the emitter reads. Both
98
+ are one node per line, which is to say diffable.
99
+
100
+ ## A whole program
101
+
102
+ ```ts nish:ok
103
+ const gcd = (a: i32, b: i32): i32 => {
104
+ let x = a;
105
+ let y = b;
106
+ while (y !== 0) {
107
+ const t = y;
108
+ y = x % y;
109
+ x = t;
110
+ }
111
+ return x;
112
+ };
113
+
114
+ export const main = (): i32 => {
115
+ console.log(`gcd = ${gcd(1071, 462)}`);
116
+ return 0;
117
+ };
118
+ ```
119
+
120
+ That is the shape: top-level `const` arrows, an exported `main`, explicit types
121
+ everywhere, a template literal instead of `String(n)`.
122
+
123
+ - A module is one `.ts` file. At the top level it may hold **only**
124
+ declarations: `const` arrows (functions), `class`, `interface`, module
125
+ `const`s, `type` aliases, numeric `enum`s, and `import`s. There is no
126
+ top-level code, so there is no initialisation order to think about.
127
+ - `export const main` lives in the entry module only, takes no parameters, and
128
+ returns `void` or an `i32`-lowered `number` — the process exit code. Under
129
+ `--number-mode f64`, declare it `main(): i32`.
130
+ - `number` is `i32` by default and `f64` under `--number-mode f64`. Write the
131
+ explicit width (`i32`, `f64`) when you mean one; it reads the same in both
132
+ modes.
133
+
134
+ ## What you must unlearn
135
+
136
+ Every row is something a TypeScript-trained model writes by reflex and Nish
137
+ rejects. This table is the highest-value part of the page.
138
+
139
+ | Your reflex | What happens | Write instead |
140
+ | --- | --- | --- |
141
+ | `if (xs.length)`, `!xs.length`, `if (s.length && ok)` | `Condition must be boolean … (Nish has no truthiness)` / `` Unsupported unary operator `!` on i32 `` / `` Operator `&&` requires boolean operands, got i32 and boolean `` | `if (xs.length !== 0)`, `xs.length === 0`, `if (s.length !== 0 && ok)`; `nish --fix` writes each where it is the test `tsc` reads (an operand of `&&` or `\|\|` only where the whole is a condition or under `!`) |
142
+ | `x == y` | `Loose equality is forbidden` | `x === y` |
143
+ | `throw new Error(m)` | `` `throw` is forbidden `` | `return Err(m)`, or `panic(m)` to end the process |
144
+ | `try { … } catch { … }` | `` `try`/`catch`/`finally` is forbidden `` | `Result<T, E>` and `isErr()` |
145
+ | `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.sort`, `.shift` | `` Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill) `` | a `for` loop, or a top-level `map(xs, f)` with a [function parameter](#function-parameters) |
146
+ | `s.toUpperCase()`, `s.split()`, `s.trim()`, `s.replace()` | `` Unknown method … on string `` | index bytes with `charCodeAt` / `substring` |
147
+ | `a?.b`, `a ?? b` | `?.` only in a chain that ends in `??`; `??` only on a `T \| null`, a chain or `m.get(k)` | `a?.b ?? d`, `a ?? d`, or `if (a !== null)` first |
148
+ | `x as T`, `<T>x`, `x satisfies T` | `Unsupported expression in Phase 1: AsExpression` (to `any` or `unknown`: `` Type assertion to `any` is forbidden ``); `x!` is a syntax error | there are no casts; `implements` is the only widening |
149
+ | `typeof x`, `x instanceof C`, `"k" in o`, `delete o.k`, `void 0`, `a, b` | each refused by name (`` `typeof` is forbidden ``, …) | there is no runtime type information: a value's type is its declared one |
150
+ | `a ** b`, `[...xs]`, `[1, , 2]` | `` Unsupported binary operator `**` `` / `Spread in array literals is not supported` / `Holes in array literals are not supported` | `Math.pow` on `f64`, a loop, every element written out |
151
+ | `any`, `unknown` | forbidden | name the real type |
152
+ | `undefined` | forbidden, except `x === undefined` on a `Map.get` result | `null`, with a `T \| null` type |
153
+ | `let total = 0` at the top level | `` Top-level `let` is not supported `` | a module `const`, or a local |
154
+ | `for (const k in o)` | `` `for...in` is forbidden `` | `for (const x of xs)` over an array, or `for (const k of m.keys())` over a `Map` |
155
+ | a callback stored or returned: `const cb = (n: i32) => n`, a field `cb: (n: i32) => i32` | `An arrow may only be written as the argument for a function-typed parameter` / `` … is a function type, which may only annotate a parameter of a top-level function `` | there are **no function values**; a callback the call names is a [function parameter](#function-parameters) |
156
+ | `type Pair<T>` (a generic alias) | `Generic type parameters are forbidden on a type alias` | a generic **function**, **class**, **interface** and **method** all work — see below; an alias renames a type that already exists, so it has nothing to specialise |
157
+ | `constructor<T>(x: T)` | a syntax error: `a constructor cannot have type parameters` | put the parameter on the class (`class Box<T>`), or on a method |
158
+ | `h.get<i32>(7)` (type argument at a method call) | `Type arguments are not written at a call site in Nish` | `h.get(7)` — a generic method infers like a generic function |
159
+ | `<T>(p: T) => p.x` (a member of a type parameter) | `` Cannot read `x` of `T`: an unconstrained type parameter has no members `` | `<T extends Point>(p: T) => p.x`, where `Point` is a class or interface that declares `x` |
160
+ | `identity<i32>(7)` (type argument at a call) | `Type arguments are not written at a call site in Nish` | `identity(7)` — `T` is inferred from the arguments |
161
+ | `async` / `await` / `Promise` | forbidden (no event loop) | the I/O builtins are synchronous |
162
+ | `namespace N { }`, `declare global { }`, `@decorator` | forbidden | one module per file; a plain function in place of a decorator |
163
+ | `keyof T`, `<T = i32>` (a default type argument) | `` Unsupported type `keyof T` (Phase 1 supports …) `` / `a default type argument … is not supported` | name the type; a type argument is always inferred |
164
+ | `class B extends A` | `` `extends` is not supported: Nish has no inheritance `` | repeat the fields and `implements` an interface |
165
+ | `static` members, `get x()` / `set x(v)` | `` … `static` members are not supported `` / `` Getters and setters are not supported in class `C` (use a method) `` | module `const`s and plain methods |
166
+ | `const [a, b] = xs`, `({ x }: Point) =>`, `(n = 1)`, `(n?: i32)`, `(...ns: i32[])` | `Destructuring is not supported` / `Destructured parameters are not supported` / `Optional/default parameters are not supported` / `Rest parameters are not supported` | one name per binding, every parameter passed; an array for a variable count |
167
+ | `function f(): void;` (an overload), `export default function ()` | `Functions must have a body` / `Functions must be named` | one named function, with its body |
168
+ | a method in an `interface` | `` Interface `I` cannot declare methods (interfaces describe layout only) `` | fields only; a top-level function over the interface |
169
+ | `abstract class`, `declare class`, `constructor(public x: i32)`, `interface B extends A`, `[k: string]: T` | `Abstract classes are not supported` / `` `declare class` is not supported `` / `Parameter properties … are not supported; declare the field and assign it` / ``Interface inheritance (`extends`) is not supported; list every field`` / `Index signatures are not supported …` | a plain class with its fields declared and assigned in the constructor; every field listed; a `Map` for a keyed table |
170
+ | `{ m() { … } }`, `{ "a": 1 }`, `const C = class { }` | ``Unsupported object literal member: MethodDeclaration (only `key: value`)`` / `Object literal keys must be plain identifiers` / `Classes must be named` | fields with identifier keys, a top-level function over the interface, a named top-level class |
171
+ | `{ ...base, x: 1 }`, `{ [k]: v }`, `[k]: i32` in a class or interface | `Object spread is forbidden in Nish (set each field by name)` / `Computed property names are forbidden in Nish (member names are fixed at compile time)` | every field written by name (`{ x: 1, y: base.y }`); a `Map` for a keyed table |
172
+ | `type Pair = { a: i32 }` (inline object type) | `` Unsupported type `{ a: i32 }` `` | declare an `interface` |
173
+ | `A \| B` unions of anything but tagged classes and interfaces | `` Union types other than `T \| null` are forbidden `` | a [discriminated union](#discriminated-unions): a tag field first in each variant |
174
+ | `String(n)`, `n.toString()` | `` Unknown function `String` `` / `` Unknown method `toString` on i32 `` | `` `${n}` `` |
175
+ | `xs.length = 0` | `` Cannot assign to `length` of i32[] (array length is read-only; use `push`) `` | build a new array |
176
+ | `import { readFileSync } from "fs"` | `` Cannot find package `fs` `` — a bare specifier is a **package name**, so it is looked for in `node_modules`; one that is installed but has no `nish` condition is `` Package `fs` has no Nish entry point `` | `readFileSync` is a global; no import needed (or `import { readFileSync } from "nish:fs"`) |
177
+ | `export default f` | `` `export default` / `export =` are not supported; use a named `export function` `` | `export const f = …` |
178
+ | `export { f }`, `export * from "./m"`, `export default function f` | `` `export { ... }` / `export * from` are not supported; put `export` on the function declaration itself `` / `` `export default` is not supported; use a named `export function` `` | `export` on the declaration itself |
179
+ | `import d from "./m"`, `import * as m from "./m"`, `import "./m"`, `import type { T } from "./m"` | `` Default imports are not supported; use `import { d } from "./m"` `` / `` Namespace imports (`import * as m`) are not supported; import functions by name `` / `` Side-effect imports (`import "./m"`) are not supported; modules have no top-level code `` / `Type-only imports are not supported` | `import { d, T } from "./m"`, naming what you use |
180
+ | `total += xs[i]` in a loop, with `i` from somewhere the compiler cannot bound | it compiles, with an `NL9007` performance warning: `` `i` is not proven to be in range for `xs` here, so this access keeps its bounds check `` | a guard before the statement, `if (!(i >= 0 && i < toI32(xs.length))) { panic("index out of range") }`, which proves both ends in either number mode; `nish --fix` inserts it where it changes nothing the program does |
181
+ | `new Date()` | `` `new Date()` is refused: Nish has no `Date` object; the one `Date` member is `Date.now()`, the wall clock in milliseconds `` | `Date.now()` for the wall clock, `monotonicNanos()` for elapsed time; there is no calendar |
182
+ | `JSON.parse`, `RegExp`, `/re/` literals, `Promise` | unknown / forbidden | none of these are built in: `regexCompile(pattern, flags)` from `nish/regex` for a pattern, `jsonDecode` from `nish/json-typed` for JSON; restructure around `Promise` |
183
+ | `for (const [k, v] of m)`, `m.forEach(...)`, `new Map(entries)` | each refused by name | `Map` and `Set` exist, with `size`, `get`, `set` / `add`, `has`, `delete`, `clear`, and `keys()` / `values()` in a `for...of` — see [Map and Set](#map-and-set) |
184
+ | `let v = m.get(k)`, `f(m.get(k))`, `m.get(k) + 1` | `` `m.get(k)` is `i32 \| undefined` and cannot be held in a `let` `` (and one message per place) | `m.get(k) ?? 0`, or `const v = m.get(k); if (v !== undefined) { … }` |
185
+ | `let x = 5; x = "s"` | `Cannot initialize …` | types never change and never convert implicitly |
186
+
187
+ A worked pair. This is the single most common rejection:
188
+
189
+ ```ts nish:err-body NL2188
190
+ const xs = [1, 2, 3];
191
+ if (xs.length) {
192
+ console.log("non-empty");
193
+ }
194
+ ```
195
+
196
+ ```ts nish:ok-body
197
+ const xs = [1, 2, 3];
198
+ if (xs.length !== 0) {
199
+ console.log("non-empty");
200
+ }
201
+ ```
202
+
203
+ ## Types
204
+
205
+ Every type is exactly one LLVM first-class type. No boxing, no runtime tag, no
206
+ structural subtyping, **no implicit conversion of any kind**: two values are
207
+ compatible only when their types are identical.
208
+
209
+ | Type | Is | Notes |
210
+ | --- | --- | --- |
211
+ | `number` | `i32`, or `f64` under `--number-mode f64` | the mode's default width |
212
+ | `i32`, `i64` | signed integers | overflow **panics** (exit 1) unless proven to fit; `wrappingAdd`/`wrappingSub`/`wrappingMul` from `nish:unsafe` wrap |
213
+ | `u8`, `u16`, `u32`, `u64` | unsigned integers | arithmetic **always** wraps; `>>` is logical |
214
+ | `f32`, `f64` | IEEE-754 | `f32` is never the lowering of `number` |
215
+ | `boolean` | `i1` | no truthiness anywhere |
216
+ | `string` | immutable UTF-8 bytes | `length` is the **byte** length |
217
+ | `T[]`, `Array<T>` | one element type, bounds-checked | `readonly T[]` refuses every write |
218
+ | `Int32Array`, `Float32Array`, `Float64Array`, `BigInt64Array` | aliases of `i32[]`, `f32[]`, `f64[]`, `i64[]` | not distinct types; fixed length, so no `push` or `pop` (NL2415): declare `f64[]` to grow one |
219
+ | `class C`, `interface I` | a struct, fields in declaration order | no header, no vtable |
220
+ | `enum K` | a **distinct** type represented as `i32` | never interchangeable with `i32` |
221
+ | `K.M` | an enum member type: the one member `K.M`, as `i32` | holds only that member; a read is a `K` |
222
+ | `T \| null` | `T` a class, interface, array, or string | a scalar can never be nullable |
223
+ | `A \| B` of tagged variants | a pointer to the variant; its first field is the tag | [Discriminated unions](#discriminated-unions); only the tag is read until a test of it narrows |
224
+ | `Result<T, E>` | the only way to report failure | `E` may not be `void` |
225
+ | `void` | return type only | |
226
+
227
+ **An unchecked index and a defined wrap are `nish:unsafe` imports**, written at
228
+ the site: `uncheckedGet(xs, i)` and `uncheckedSet(xs, i, v)` on an array of
229
+ numbers with an `i32` index (out of range is undefined behaviour), and
230
+ `wrappingAdd` / `wrappingSub` / `wrappingMul` on two `i32`s or two `i64`s.
231
+ The sixth export, `readCString`, copies a C string out of a `CPtr`
232
+ ([Calling C](#calling-c)). Calling one without the import is NL2456. `--unchecked-indexing` and
233
+ `--wrapping` are deprecated (NL9014, NL9015) and reach only your own package,
234
+ never a dependency or `nish/`. To migrate off `--unchecked-indexing`, run
235
+ `nish --unchecked-indexing --fix` on your files: each index the flag leaves
236
+ unchecked is a `deprecation` warning of its own (NL7002), and where the site
237
+ is a number array with an `i32` index its `fix` rewrites it to `uncheckedGet`
238
+ or `uncheckedSet` and adds the import. A site with no rewrite keeps its
239
+ warning; dropping the flag gives it back its bounds check.
240
+
241
+ ```ts nish:ok
242
+ import { uncheckedGet, wrappingAdd, wrappingMul } from "nish:unsafe";
243
+
244
+ export const hash = (bytes: u8[]): i32 => {
245
+ let h: i32 = 17;
246
+ for (let i: i32 = 0; i < toI32(bytes.length); i++) {
247
+ h = wrappingAdd(wrappingMul(h, 31), toI32(uncheckedGet(bytes, i)));
248
+ }
249
+ return h;
250
+ };
251
+ ```
252
+
253
+ **Mixing widths is an error, always.** `f32 + f64`, `i64 + number`, even
254
+ `u32 + i32` (same LLVM type, different signedness) are rejected. Convert
255
+ explicitly: `toI32` `toI64` `toU8` `toU16` `toU32` `toU64` `toF32` `toF64`.
256
+
257
+ **Signed overflow panics.** `+ - *`, unary `-`, `++`, `--` and `op=` on an
258
+ `i32` or `i64` stop the program with `attempt to add with overflow` (or
259
+ `subtract`, `multiply`, `negate`) and exit 1 when the result leaves the type;
260
+ it is never a silent wrap and never undefined. The compiler drops the check
261
+ where it can prove the result fits — a loop counter under `i < n`, a byte
262
+ widened with `toI32`, an `integer<Lo, Hi>`, a value masked with `& c`. Arithmetic
263
+ that is *meant* to wrap — a hash, a PRNG, a checksum — goes in `u32` or `u64`,
264
+ which always wrap and are never checked; convert back with `toI32` (free), or
265
+ call `wrappingAdd` / `wrappingSub` / `wrappingMul` at the site. The deprecated
266
+ `--wrapping` makes every signed operation of your own package wrap instead.
267
+
268
+ ```ts nish:ok
269
+ // FNV-1a: the multiply overflows on purpose, so the state is a `u32`.
270
+ export const fnv1a = (bytes: u8[]): u32 => {
271
+ let h: u32 = 2166136261;
272
+ for (const b of bytes) {
273
+ h = (h ^ toU32(b)) * 16777619;
274
+ }
275
+ return h;
276
+ };
277
+ ```
278
+
279
+ ```ts nish:err NL2231
280
+ const bad = (a: i32, b: i64): i64 => {
281
+ return a + b;
282
+ };
283
+ ```
284
+
285
+ ```ts nish:ok
286
+ const good = (a: i32, b: i64): i64 => {
287
+ return toI64(a) + b;
288
+ };
289
+ ```
290
+
291
+ **`integer<Lo, Hi>` is an `i32` that stays in `[Lo, Hi]`.** The bounds are two
292
+ integer literals (a sign allowed) inside `i32`, and it is an `i32` everywhere
293
+ the machine can see. Putting a value *into* one is checked: an `i32` or
294
+ another range is compared once and the program panics (exit 1) outside the
295
+ range, a literal outside it is a compile error, and a literal inside it or a
296
+ narrower range costs nothing. So does a value a loop condition or a guard
297
+ already bounds (`b` below), and `toI32` of a `u8`; a check left inside a loop
298
+ is a performance warning (NL9013) naming the guard that removes it. A range
299
+ bounds an index too: `integer<0, 255>`, like `u8`, needs only a length guard of
300
+ 256. Taking one *out* is free: every operator reads
301
+ it as `i32`, a `const` keeps the range and a `let` widens to `i32`. A `u8` is
302
+ not an `integer<0, 255>` (convert with `toI32`), and a `declare function` may
303
+ not mention a range. Put the range on the value you use, not on a loop
304
+ counter, which has to leave the range to end the loop.
305
+
306
+ ```ts nish:ok
307
+ const getByte = (buf: u8[], i: integer<0, 255>): u8 => buf[i];
308
+
309
+ const sumAll = (buf: u8[]): i32 => {
310
+ let sum = 0;
311
+ for (let i = 0; i < 256 && i < buf.length; i++) {
312
+ const b: integer<0, 255> = i;
313
+ sum = sum + toI32(getByte(buf, b));
314
+ }
315
+ return sum;
316
+ };
317
+ ```
318
+
319
+ ```ts nish:err-body NL2384
320
+ const digit: integer<0, 9> = 12;
321
+ ```
322
+
323
+ Declaring a type alias, enum, class, interface or function named `integer` is
324
+ refused, because the name is the type. A value named `integer` (a local or a
325
+ module constant) is fine.
326
+
327
+ ```ts nish:err NL2332
328
+ class integer {
329
+ value: i32 = 0;
330
+ }
331
+ ```
332
+
333
+ ### Numeric literals take their type from the immediate context
334
+
335
+ A literal is the mode's default (`i32`, or `f64` in f64 mode) **unless its
336
+ immediate context demands another numeric type**, and the contexts that do are
337
+ a closed list: an annotated initializer, a `return`, an argument to a user
338
+ *function* or to a constructor, the other operand of a binary operator whose
339
+ type is already known, `Math.min` / `Math.max`, the f64-only `Math.*` and
340
+ `toF64` / `toF32`, `process.exit`, `Number`, an element of an array literal
341
+ that itself has a `T[]` context, a class field's literal initializer, a field
342
+ or element assignment target, a ternary arm taking the conditional's own
343
+ context, and `push` / `indexOf` through a receiver that is a **plain name**.
344
+
345
+ Everything else leaves the literal at the default, however obvious the intent.
346
+ The three that catch you out: an **object-literal property**, a `Result`
347
+ payload, and a **method's** argument.
348
+
349
+ ```ts nish:err NL2269
350
+ interface Pixel { r: u8; g: u8; b: u8; }
351
+
352
+ export const main = (): i32 => {
353
+ const p: Pixel = { r: 255, g: 0, b: 0 };
354
+ console.log(`${p.r}`);
355
+ return 0;
356
+ };
357
+ ```
358
+
359
+ ```ts nish:ok
360
+ interface Pixel { r: u8; g: u8; b: u8; }
361
+
362
+ export const main = (): i32 => {
363
+ const p: Pixel = { r: toU8(255), g: toU8(0), b: toU8(0) };
364
+ console.log(`${p.r}`);
365
+ return 0;
366
+ };
367
+ ```
368
+
369
+ The same rule refuses `const b: u8 = 1 + 2` — that is a sum of two `i32`
370
+ literals, not a literal in a `u8` context. Name the value first, or convert.
371
+
372
+ ## Failure: `Result`, not exceptions
373
+
374
+ There is no `throw`, no `try`, and no unwinding anywhere in the language. A
375
+ function that can fail says so in its return type.
376
+
377
+ ```ts nish:ok
378
+ const half = (n: i32): Result<i32, string> => {
379
+ if (n % 2 !== 0) {
380
+ return Err("odd");
381
+ }
382
+ return Ok(n / 2);
383
+ };
384
+
385
+ const quarter = (n: i32): Result<i32, string> => {
386
+ const h = half(n).orReturn(); // Rust's `?`: propagates the error
387
+ return half(h);
388
+ };
389
+
390
+ export const main = (): i32 => {
391
+ const outcome = quarter(8);
392
+ if (outcome.isErr()) {
393
+ console.error(outcome.error);
394
+ return 1;
395
+ }
396
+ console.log(`${outcome.value}`);
397
+ return 0;
398
+ };
399
+ ```
400
+
401
+ The surface: `Ok(v)` / `Ok()`, `Err(e)`, `r.isOk()`, `r.isErr()`, `r.ok`,
402
+ `r.value`, `r.error`, `r.orReturn()`, `r.unwrapOr(d)`, `r.expect(msg)`. There
403
+ is deliberately no `unwrap()` — `expect` insists on a reason — and no
404
+ `map`/`andThen`, which would need function values.
405
+
406
+ **Three rules, and they are the ones that catch you out:**
407
+
408
+ 1. **A `Result` cannot be dropped.** A call answering one may not stand as an
409
+ expression statement, and a local holding one must be read at least once.
410
+ Passing it on — as an argument, as a `return` — counts as reading it.
411
+ 2. **The error arm comes first.** `r.value` is legal only where the checker
412
+ proved `isOk()`; `r.error` only where it proved `isErr()`. There is no
413
+ spelling that reaches the success payload without deciding what happens to
414
+ the failure.
415
+ 3. **Propagation is contagious.** `r.orReturn()` is legal only inside a
416
+ function that itself returns a `Result` whose error arm accepts `E`. There
417
+ is no implicit error conversion — convert by hand with
418
+ `if (r.isErr()) { return Err(…); }`.
419
+
420
+ ```ts nish:err NL2044
421
+ const half = (n: i32): Result<i32, string> => {
422
+ return Ok(n / 2);
423
+ };
424
+
425
+ export const main = (): i32 => {
426
+ const r = half(8);
427
+ console.log(`${r.value}`);
428
+ return 0;
429
+ };
430
+ ```
431
+
432
+ Narrowing follows the same engine as `T | null` below: it applies to a
433
+ **variable**, never a property path; it ends at any assignment to that
434
+ variable; and it is dropped before a loop that assigns it. `if (r.isOk()) A
435
+ else B` narrows in `A`, and after the `if` when `B` cannot fall through.
436
+ The proof stays with `r`: `const y = r`, `c ? r : q` and `[r]` are plain
437
+ `Result`s, so test `y` itself before `y.value`.
438
+
439
+ `panic(message)` is the other ending: message to stderr, exit 1. It is for an
440
+ invariant that cannot hold, not for a failure a caller should handle. It
441
+ terminates control flow, so a non-`void` function may end with it.
442
+
443
+ ## Absence: nullable types
444
+
445
+ `T | null` is available for `T` a class, interface, array, or string — never a
446
+ scalar, which has no null value. It is the same pointer with `null` as one more
447
+ value, so nothing is boxed.
448
+
449
+ ```ts nish:ok
450
+ class Node {
451
+ value: i32;
452
+ next: Node | null = null;
453
+ constructor(value: i32) {
454
+ this.value = value;
455
+ }
456
+ }
457
+
458
+ const total = (head: Node | null): i32 => {
459
+ let sum = 0;
460
+ let cur = head;
461
+ while (cur !== null) {
462
+ sum = sum + cur.value;
463
+ cur = cur.next;
464
+ }
465
+ return sum;
466
+ };
467
+
468
+ export const main = (): i32 => {
469
+ const a = new Node(1);
470
+ a.next = new Node(2);
471
+ console.log(`${total(a)}`);
472
+ return 0;
473
+ };
474
+ ```
475
+
476
+ - Reading anything off an un-narrowed nullable is an error: narrow with
477
+ `!== null` first, or read it through a `?.` chain that ends in `??`.
478
+ - `a ?? d` defaults a nullable: it is `a` where that is not `null`, `d` where
479
+ it is, and its type is `T` when `d` is a `T`, so it needs no narrowing. It
480
+ reads `a` once, so a property path works: `cur = cur.next ?? cur`. `d` that
481
+ may be `null` keeps the `T | null`. An element read (`xs[i] ?? d`) is
482
+ refused, because Node answers `d` past the end where Nish panics. `??` also
483
+ defaults a `Map.get` result (see [Map and Set](#map-and-set)).
484
+
485
+ ```ts nish:ok
486
+ class Node {
487
+ value: i32;
488
+ next: Node | null = null;
489
+ constructor(value: i32) {
490
+ this.value = value;
491
+ }
492
+ }
493
+
494
+ const label = (name: string | null): string => name ?? "anonymous";
495
+
496
+ export const main = (): i32 => {
497
+ const a = new Node(1);
498
+ const after = a.next ?? a; // `Node`, no narrowing needed
499
+ console.log(`${label(null)} ${after.value}`); // anonymous 1
500
+ return 0;
501
+ };
502
+ ```
503
+
504
+ ```ts nish:err-body NL2489
505
+ const names = new Array<string | null>(2);
506
+ console.log(names[2] ?? "none");
507
+ ```
508
+
509
+ - `a?.b?.c ?? d` reads each link only where the receiver before its `?.` is
510
+ not `null`, and is `d` where one is. A `?.` is allowed **only in a chain
511
+ that is the left operand of `??`** (NL1062): `const x = a?.b`, `f(a?.b)` and
512
+ `` `${a?.b}` `` are refused, because on its own a chain answers
513
+ `undefined`. The last link may be any type, a number included
514
+ (`o?.next?.id ?? -1` is an `i32`), and a method call is a link
515
+ (`c?.greeting() ?? ""`). Only a `?.` skips: `a?.next.v` with a nullable
516
+ `next` needs `a?.next?.v`. `?.` on something that cannot be `null` is
517
+ NL2490; `?.[i]` and `f?.()` are refused.
518
+
519
+ ```ts nish:ok
520
+ class Order {
521
+ id: i32;
522
+ next: Order | null = null;
523
+ constructor(id: i32) {
524
+ this.id = id;
525
+ }
526
+ }
527
+
528
+ const nextId = (o: Order | null): i32 => o?.next?.id ?? -1;
529
+
530
+ export const main = (): i32 => {
531
+ const a = new Order(1);
532
+ a.next = new Order(2);
533
+ console.log(`${nextId(a)} ${nextId(null)}`); // 2 -1
534
+ return 0;
535
+ };
536
+ ```
537
+
538
+ ```ts nish:err-body NL1062
539
+ const s: string | null = "x";
540
+ const n = s?.length;
541
+ ```
542
+ - Narrowing applies to a **local or parameter**, never a property path. `if
543
+ (n.next !== null) n.next.v` is rejected — copy into a local first.
544
+ - A narrowing ends at any assignment to the variable, including one in an
545
+ earlier operand of the same `&&` / `||` chain, and is dropped before a loop
546
+ whose body, condition or update assigns it.
547
+ - Two nullables cannot be compared with each other; compare each with `null`.
548
+ - `new Array<T | null>(n)` is allowed: the zero fill *is* `null`.
549
+
550
+ ## Declarations
551
+
552
+ ### Functions
553
+
554
+ ```ts nish:ok
555
+ const add = (a: i32, b: i32): i32 => {
556
+ return a + b;
557
+ };
558
+
559
+ const double = (n: i32): i32 => n * 2; // a concise body is that one `return`
560
+ ```
561
+
562
+ - A function is a **module-level `const` bound to an arrow**. `let` is
563
+ rejected; annotating the `const` is rejected (the annotation would be a
564
+ function type, which only a parameter may have). The `function` keyword is
565
+ accepted as the legacy spelling and compiles to identical IR.
566
+ - **Every parameter and the return type must be annotated.**
567
+ - **A function is not a value.** `const alias = double` is
568
+ `` Unknown identifier `double` ``. A function may be *passed* only for a
569
+ function-typed parameter, where it is resolved at compile time — see
570
+ [Function parameters](#function-parameters) — and never stored, returned or
571
+ held in a field, a local or an array.
572
+ - **Parameters are immutable**: `p = 1`, `p++`, `p += 1` are all rejected. Copy
573
+ into a `let` first.
574
+ - A non-`void` function must return on every path.
575
+ - Rejected: generators, `async`, destructured / rest / optional / default
576
+ parameters, overloads, nested function declarations. Type parameters *are*
577
+ allowed — see [Generic functions](#generic-functions).
578
+
579
+ ### Generic functions
580
+
581
+ A function may declare type parameters, and **each instantiation is compiled to
582
+ its own specialised function** — no boxing, no dictionary, no runtime type
583
+ information. The IR is what somebody would have written by hand at that type.
584
+
585
+ ```ts nish:ok
586
+ const identity = <T>(x: T): T => x;
587
+
588
+ export const main = (): i32 => {
589
+ console.log(identity(7));
590
+ console.log(identity("hi"));
591
+ return 0;
592
+ };
593
+ ```
594
+
595
+ - **The type argument is inferred from the arguments and is never written at a
596
+ call site.** `identity<i32>(7)` is
597
+ `Type arguments are not written at a call site in Nish`. Inference matches the
598
+ shape of each declared parameter against its argument — `T` against `i32`,
599
+ `T[]` against `i32[]`, `Result<T, string>` against `Result<i32, string>` —
600
+ left to right, first binding wins.
601
+ - **A type parameter that appears in no parameter is an error at the
602
+ declaration**, because it could never be inferred.
603
+ - **Every other rule still applies inside.** A type parameter is not a hole to
604
+ smuggle something through: each instantiation is checked with `T` bound to a
605
+ concrete type.
606
+ - **A template may be exported and instantiated from another module.** Each
607
+ instantiation is defined once, in the module that declares the template, and
608
+ `declare`d everywhere else — so one imported name becomes one symbol per
609
+ distinct type-argument tuple, and two modules asking for the same tuple share
610
+ the one definition. A type argument may be a class the declaring module has
611
+ never heard of. A template that is not exported cannot be imported, and an
612
+ imported one may be renamed (`import { identity as id }`) without moving the
613
+ symbol. A type-argument list on an imported name that is *not* a template is
614
+ `` `Point` in `./lib` takes no type arguments ``.
615
+ - **A type parameter has no members unless it is constrained.** Passing a `T`
616
+ on, returning it, storing it and comparing it are fine; `p.x`, `p.x = 1` and
617
+ `p.m()` through a `T` are `` Cannot read `x` of `T`: an unconstrained type
618
+ parameter has no members ``, even when every call passes a type with an `x`.
619
+ It is where the value came from that counts — a local copied from a `T`, an
620
+ element of a `T[]` and a narrowed `T | null` are all a `T` — never its type.
621
+
622
+ ```ts nish:err NL2329
623
+ interface Point { x: i32; y: i32; }
624
+
625
+ const getX = <T>(p: T): i32 => p.x;
626
+
627
+ export const main = (): i32 => {
628
+ const p: Point = { x: 1, y: 2 };
629
+ return getX(p);
630
+ };
631
+ ```
632
+
633
+ - **`<T extends Shape>` gives `T` exactly `Shape`'s members**: its fields, and
634
+ its methods when `Shape` is a class. A constraint is a declared class or
635
+ interface (or `Container<i32>`, with its arguments written out) — never a
636
+ scalar, `string`, an array or another type parameter. Each instantiation still
637
+ reads its own struct directly, with no vtable.
638
+ - **A type argument must satisfy the constraint**: be `Shape` itself, or a
639
+ class that declares `implements Shape`. Matching field names are not enough.
640
+ A class constraint is satisfied by that class alone. The refusal is at the
641
+ call: `` `T` of `areaOf` requires `T extends Shape`, and `i32` does not
642
+ implement it ``.
643
+ - **A constraint is a declaration, not a name.** A `Shape` your module declares
644
+ is not the `Shape` another module's `<T extends Shape>` names, even with the
645
+ same fields, so implementing your own is refused the same way; import the
646
+ template's `Shape` instead.
647
+
648
+ ```ts nish:ok
649
+ interface Shape { area: i32; }
650
+
651
+ class Circle implements Shape {
652
+ area: i32;
653
+ radius: i32;
654
+ constructor(r: i32) { this.area = 3 * r * r; this.radius = r; }
655
+ }
656
+
657
+ const areaOf = <T extends Shape>(s: T): i32 => s.area; // s.radius would be refused
658
+
659
+ export const main = (): i32 => areaOf(new Circle(2)) - 12;
660
+ ```
661
+
662
+ - **Each type parameter is named once**: `<T, T>` is
663
+ `` Duplicate type parameter `T` ``, on a function, method, class or
664
+ interface, and in a template nothing calls too.
665
+ - **Not supported yet**: a default type argument (`<T = string>`) and type
666
+ parameters on a **constructor**, each with its own message, and a generic
667
+ **type alias**, which is only the syntax error `` expected `=`, found `<` ``.
668
+ A generic `main` is refused. There are no multiple bounds (`T extends A & B`)
669
+ and no bound that mentions another parameter.
670
+ - **`$` may not appear in a function, class or interface name** — it is what
671
+ separates a generic's name from its type arguments in the emitted symbol.
672
+ - **Read `identity<i32>` in a message, `identity$i32` in the IR.** Diagnostics,
673
+ `--emit-checked` and the `-g` debug names spell an instantiation as written
674
+ (`identity<Box<i32>>`, `Box<i32>.get`); symbols and `%struct` names are
675
+ mangled (`@identity$$Box$i32`, `%struct.Box$i32`).
676
+ - **A host calls an exported instantiation as `nish_gen_…`**: `identity<i32>`
677
+ is `nish_gen_identity_i32` in the C header, the `.d.ts`, the wasm loader and
678
+ the N-API addon, and `identity<i32[]>` is `nish_gen_identity_arr_i32`. There
679
+ is no `identity` to call — only the instantiations the program makes.
680
+ - **With a sidecar flag, an exported generic must be instantiated.** Nothing
681
+ instantiated means no symbol, so `--emit-header`, `--emit-dts` and
682
+ `--emit-napi` refuse it (NL4007) rather than leave it out; call it somewhere,
683
+ or do not export it. The same flags refuse two names that are one C
684
+ identifier, such as a method `Point.shifted` and a function `Point_shifted`
685
+ (NL4008).
686
+
687
+ ```ts nish:err NL4007 --emit-dts build/docs-ai/nl4007.d.ts
688
+ export const identity = <T>(x: T): T => x; // exported, never called
689
+
690
+ export const main = (): i32 => 0;
691
+ ```
692
+
693
+ ### Generic classes and interfaces
694
+
695
+ A `class` or an `interface` may take type parameters too, and an instantiation
696
+ is **an ordinary struct**: `Box<i32>` is `%struct.Box$i32` with `Box`'s fields
697
+ at `T = i32`, and everything the language does to a struct works on it.
698
+
699
+ ```ts nish:ok
700
+ class Box<T> {
701
+ value: T;
702
+ constructor(v: T) { this.value = v; }
703
+ get(): T { return this.value; }
704
+ }
705
+
706
+ interface Pair<A, B> { first: A; second: B; }
707
+
708
+ export const main = (): i32 => {
709
+ const b = new Box<i32>(7);
710
+ const p: Pair<i32, string> = { first: 1, second: "hi" };
711
+ console.log(p.second);
712
+ return b.get();
713
+ };
714
+ ```
715
+
716
+ - **Write the type arguments out.** `Box` on its own is not a type — there is
717
+ no layout until `T` is bound — so it is `` `Box` is generic: it must be
718
+ written with its type arguments ``. The two positions that take them are an
719
+ annotation (`const b: Box<i32>`) and `new` (`new Box<i32>(7)`); a *call*
720
+ infers instead. A class that is not generic takes none.
721
+ - **A type argument may be anything a type may be**, including another
722
+ instantiation: `Box<Point>`, `Box<Box<i32>>`, `Box<i32[]>`,
723
+ `Box<Result<i32, string>>`.
724
+ - **`implements` may name an instantiation**: `class Box<T> implements
725
+ Container<T>` is checked per instantiation by the usual field-prefix rule.
726
+ - **A field may not grow its own type argument.** `class Nest<T> { inner:
727
+ Nest<T[]> | null }` is `` Monomorphising `Nest` would not terminate ``;
728
+ `Nest<T>` and a type that mentions no parameter are both fine.
729
+ - **A type parameter takes no type arguments of its own.** `T` is whatever the
730
+ instantiation bound it to, so `const y: T<i32>` is
731
+ `` Unsupported type reference `T<i32>` ``.
732
+ - **A template obeys every rule a declared class obeys**: `declare class
733
+ Box<T>`, `export default class Box<T>`, `abstract class Box<T>` and
734
+ `interface Box<T> extends Base` are refused in the words their non-generic
735
+ spellings are refused in.
736
+ - **A template claims its name.** `class Box<T>` is a declaration of `Box`, so
737
+ a function, constant, alias, enum or second class of that name is refused
738
+ whichever was written first: `` `Box` is already declared in this module ``,
739
+ or `` Duplicate declaration of `Box` `` for a second class.
740
+ - **Two modules may not both declare a generic class of one name** once both
741
+ instantiate it: `%struct.Box$i32` is program-wide, so
742
+ `` Generic class `Holder` is also declared in helper.ts ``.
743
+ - **A generic class or interface may be imported**, and the rule is a generic
744
+ function's: `%struct.Box$i32` and every `@Box$i32.*` symbol are defined once,
745
+ by the module that declares `Box<T>`, and `declare`d by every module that
746
+ holds one. Unlike a declared class it may be renamed on import, because the
747
+ name that crosses the ABI is the template's. Named without its type arguments
748
+ it is `` `Crate` is generic: it must be written with its type arguments ``.
749
+ - **A class or interface may constrain its parameters** — `class Holder<T
750
+ extends Shape>` — by the rules a function's follow: its methods may read
751
+ `this.item.area` through a field of type `T`, and `new Holder<Point>` or an
752
+ annotation `Holder<Point>` is refused unless `Point` satisfies `Shape`.
753
+
754
+ ### Generic methods
755
+
756
+ A method — of a generic class or not — may declare its own type parameters.
757
+ They are inferred from the arguments like a generic function's, and each
758
+ (receiver, method type arguments) pair is its own `define`:
759
+ `@Chooser.pick$i32`, `@Box$i32.keep$str`.
760
+
761
+ ```ts nish:ok
762
+ class Chooser {
763
+ flip: boolean = false;
764
+ pick<T>(a: T, b: T): T { return this.flip ? b : a; }
765
+ }
766
+
767
+ class Box<T> {
768
+ value: T;
769
+ constructor(v: T) { this.value = v; }
770
+ keep<U>(other: U): T { return this.value; }
771
+ }
772
+
773
+ export const main = (): i32 => {
774
+ const c = new Chooser();
775
+ const word: string = c.pick("left", "right");
776
+ console.log(word);
777
+ return c.pick(0, 1) + new Box<i32>(7).keep("seven") - 7;
778
+ };
779
+ ```
780
+
781
+ - **Never write the type arguments at the call**: `c.pick<i32>(1, 2)` is
782
+ `` Type arguments are not written at a call site ``. A method type parameter
783
+ that no parameter mentions is `` Cannot infer `U` for `Holder.make` ``.
784
+ - **Do not reuse a class parameter's name**: `class Box<T> { map<T>(…) }` is
785
+ `` Type parameter `T` of `Box.map` shadows `Box`'s own `T` `` (NL2331) —
786
+ call the method's `U`.
787
+ - **Constrain it like a function's** — `apply<U extends Shape>(u: U)` reads
788
+ `u.area` — but the constraint may not mention a type parameter, the class's
789
+ included.
790
+ - **A constructor takes none**; its class's are written after `new`.
791
+
792
+ ```ts nish:err NL2331
793
+ class Box<T> {
794
+ value: T;
795
+ constructor(v: T) { this.value = v; }
796
+ map<T>(other: T): T { return other; } // shadows Box's T: call it U
797
+ }
798
+
799
+ export const main = (): i32 => new Box<i32>(1).value;
800
+ ```
801
+
802
+ ### Function parameters
803
+
804
+ A parameter of a top-level function may have a **function type**. Its argument
805
+ is a top-level function named at the call, or an arrow written right there, and
806
+ each one is compiled into its **own copy** of the function, with a direct call
807
+ where the parameter is called — there is no function pointer at run time.
808
+
809
+ ```ts nish:ok
810
+ const map = <T, U>(xs: T[], f: (x: T) => U): U[] => {
811
+ const out: U[] = [];
812
+ for (const x of xs) {
813
+ out.push(f(x));
814
+ }
815
+ return out;
816
+ };
817
+
818
+ const fold = <T>(xs: T[], f: (acc: T, x: T) => T, identity: T): T => {
819
+ let acc = identity;
820
+ for (const x of xs) {
821
+ acc = f(acc, x);
822
+ }
823
+ return acc;
824
+ };
825
+
826
+ const add = (a: i32, b: i32): i32 => a + b;
827
+
828
+ export const main = (): i32 => {
829
+ const doubled = map([1, 2, 3], (n) => n * 2); // U is bound from the body
830
+ return fold(doubled, add, 0) - 12; // 0
831
+ };
832
+ ```
833
+
834
+ - **Call it, or pass it on to another function parameter. Nothing else**:
835
+ `const g = f`, `return f` and `[f]` are
836
+ `` `f` is a function parameter and can only be called or passed on as a function argument ``.
837
+ - **An arrow captures nothing.** It sees its own parameters and top-level
838
+ names; reading a local of the function around it is
839
+ `` The arrow reads `k`, which belongs to the function it is written in ``.
840
+ Pass the value in as another argument of the template instead.
841
+ - **The argument's type must match exactly**: `(x: i32) => i64` for a
842
+ `(x: i32) => i32` parameter is refused. A generic function cannot be passed
843
+ by name — write `(x) => identity(x)`.
844
+ - **Annotate what cannot be inferred**: an arrow parameter whose type no value
845
+ argument binds, and the return type of a block-bodied arrow whose result is a
846
+ type parameter.
847
+ - **Only a top-level function** may have one: not a method, a constructor, a
848
+ field, a local, a return type or an alias.
849
+
850
+ ```ts nish:err NL2335
851
+ const apply = (f: (x: i32) => i32, x: i32): i32 => f(x);
852
+
853
+ export const main = (): i32 => {
854
+ const k = 3;
855
+ return apply((x) => x + k, 1); // captures `k`: pass it to `apply` instead
856
+ };
857
+ ```
858
+
859
+ ### Data parallelism: `nish/threads`
860
+
861
+ `parallelMapInto(src, dst, f)` writes `f(src[i])` into `dst[i]`, and
862
+ `parallelReduce(src, f, identity)` folds `src` with `f` — each on as many
863
+ threads as the array is long enough for. `f` is a [function parameter](#function-parameters).
864
+ Importing the module compiles the program with `--threads`; there is nothing
865
+ else to switch on and no thread count to choose. `parallelMap(src, f)` and
866
+ `parallelFrom(n, f)` answer a new array instead (`f(src[i])`, `f(i)`), under
867
+ the same rules for `f`.
868
+
869
+ ```ts nish:ok
870
+ import { parallelMapInto, parallelReduce } from "nish/threads";
871
+
872
+ const square = (x: f64): f64 => x * x;
873
+
874
+ export const main = (): i32 => {
875
+ const src: f64[] = [1.0, 2.0, 3.0];
876
+ const dst: f64[] = [0.0, 0.0, 0.0]; // at least src.length, or it panics
877
+ parallelMapInto(src, dst, square);
878
+ const total = parallelReduce(dst, (a, b) => a + b, 0.0); // 14, the same bits on any core count
879
+ return toI32(total);
880
+ };
881
+ ```
882
+
883
+ ```ts nish:ok
884
+ import { parallelFrom, parallelMap } from "nish/threads";
885
+
886
+ export const main = (): i32 => {
887
+ const names = parallelFrom(3, (i) => `w${i}`); // each string copied out at the join
888
+ const lens = parallelMap(names, (n) => n.length); // 2 2 2
889
+ console.log(`${names[2]} ${lens[0]}`); // w2 2
890
+ return 0;
891
+ };
892
+ ```
893
+
894
+ - **`f` writes nothing its caller can see.** No field or element store
895
+ through its argument and no `console.log` — each is refused at the call,
896
+ naming the write (NL2345): answer the value and write after the call. An
897
+ arrow may read the `const` locals and parameters around it, never write
898
+ them (NL2503: answer the value from the body instead, and fold the answers with `parallelReduce`,
899
+ `const sumTotal = parallelReduce(xs, (a, b) => a + b, 0)`, or read them from the array the call
900
+ gives back, `const kValues = parallelMap(xs, (x): i32 => x)`)
901
+ and never read a `let` (NL2500: snapshot it first, `const kNow = k`). Reading is fine; so are a bounds check, an integer
902
+ division and `panic`.
903
+ - **What `f` allocates is freed after every element.** A string, array or
904
+ object built on the way to the result compiles, with performance warning
905
+ NL9012: each element pays for building it and for the release. Storing an
906
+ allocation into another object (NL2352) and touching `Arena` (NL2351) are
907
+ refused, because either would make that release unsound.
908
+ - **A map's result is a number, a `boolean`, an enum, a `string` or an array
909
+ of numbers, booleans or enums**: a string or a scalar array is copied out of
910
+ the worker's arena at the join. An array of strings, a nested array, a class,
911
+ a `Map` or a `Set` is refused (NL2348): answer a string or a number per
912
+ element and build the structure after the call. **A reduce's `T` stays a number, a
913
+ `boolean` or an enum**: map strings to numbers first.
914
+ - **`dst` may not be reachable from an element of `src`**: mapping `Row[]` into
915
+ `f64[]` is refused when `Row` holds an `f64[]`, because it could be `dst`.
916
+ - **A reduce's `f` is associative and `identity` is its identity** — `0` for
917
+ `+`, `1` for `*`. The array is folded in fixed blocks and the blocks combined
918
+ in order, so `-` or a wrong identity is refused when `f` is an arrow of one
919
+ operator; a named function is trusted.
920
+ - A short array runs on the calling thread at the cost of the loop, so there
921
+ is no reason to guard a call by size. How short is sized from what `f`
922
+ costs: a body estimated at 3, such as `(x) => x + 1`, is divided only past
923
+ about 700,000 elements, a cheaper one later, one with a loop far sooner.
924
+
925
+ ```ts nish:err NL2350
926
+ import { parallelReduce } from "nish/threads";
927
+
928
+ export const main = (): i32 => parallelReduce([1, 2, 3], (a, b) => a + b, 1); // `+` folds from 0
929
+ ```
930
+
931
+ ### Scoped tasks: `using s = scope()`
932
+
933
+ Different functions at once, one thread each: open a scope with `using`, give
934
+ it tasks with `spawn(entry, arg, dst, at)`, and every task has run and stored
935
+ `entry(arg)` into `dst[at]` when the block ends.
936
+
937
+ ```ts nish:ok
938
+ import { scope } from "nish/threads";
939
+
940
+ const sumOf = (xs: f64[]): f64 => xs[0] + xs[1];
941
+ const square = (n: i32): i32 => n * n;
942
+
943
+ export const main = (): i32 => {
944
+ const xs: f64[] = [1.5, 2.5];
945
+ const sums: f64[] = [0.0];
946
+ const squares: i32[] = [0];
947
+ {
948
+ using s = scope();
949
+ s.spawn(sumOf, xs, sums, 0);
950
+ s.spawn(square, 12, squares, 0);
951
+ } // joined here, on every exit
952
+ console.log(`${sums[0]} ${squares[0]}`); // 4 144
953
+ return 0;
954
+ };
955
+ ```
956
+
957
+ - **The tasks run when the block ends**, together, and each answer is stored
958
+ after the last one finishes. So read a destination after the block. A
959
+ destination is a `const` bound to a fresh array (`[0, 0]`, `new Array`) that
960
+ you only index; never pass it on. Between the first `spawn` and the block's
961
+ end, don't read a destination, and don't write memory a task could read (a
962
+ store into an argument, a call that writes through its argument).
963
+ - **`using` takes only `scope()`**, a `Mutex`'s `lock()` (below), or `arena()`
964
+ (under [Memory](#memory)), and `scope()` only comes from `using`
965
+ (NL2388): `using s = scope()`.
966
+ The scope is only ever the receiver of a `spawn` statement: never pass it,
967
+ store it or return it.
968
+ - **The task is a top-level function named in the `spawn`, or an arrow written
969
+ there.** `s.spawn(entry, arg)` with no destination runs an entry that
970
+ answers `void`; `s.spawn(() => work(a, b))` runs an arrow that reads the
971
+ `const` locals and parameters around it (never a `let` or `this` (NL2506):
972
+ bind a `const` first, `const kNow = k`) and answers `void`. An entry that
973
+ answers a value has a destination (NL2511): `const answer = new Array<i32>(1)`
974
+ above the scope and `s.spawn(entry, arg, answer, 0)`. Either follows the
975
+ data-parallel rules: it writes nothing anybody else can see but what it
976
+ writes through a `Mutex`'s guard or sends on a `Channel` (below), answers a number,
977
+ a `boolean`, an enum, a `string` or an array of numbers, booleans or enums
978
+ (copied out of its arena at the join), and leaves `Arena` alone. A task cannot open a scope
979
+ of its own or call `parallelMapInto`.
980
+ - **Any argument will do** — an array, an object — because nothing writes
981
+ memory while the tasks run. There is no thread count: one task, one thread.
982
+
983
+ ```ts nish:err NL2388
984
+ import { scope } from "nish/threads";
985
+
986
+ const one = (n: i32): i32 => n + 1;
987
+
988
+ export const main = (): i32 => {
989
+ const out: i32[] = [0];
990
+ const s = scope(); // a scope must be introduced by `using`
991
+ s.spawn(one, 1, out, 0);
992
+ return out[0];
993
+ };
994
+ ```
995
+
996
+ ### Sharing a value between tasks: `Mutex<T>`
997
+
998
+ When tasks must update one value, put it in a `Mutex` and reach it only through
999
+ `using g = m.lock()`: the lock is held until that block ends, on every exit.
1000
+
1001
+ ```ts nish:ok
1002
+ import { Mutex, scope } from "nish/threads";
1003
+
1004
+ class Tally {
1005
+ bins: i32[];
1006
+ constructor() {
1007
+ this.bins = [0, 0, 0, 0];
1008
+ }
1009
+ }
1010
+
1011
+ class Shard {
1012
+ xs: i32[];
1013
+ tally: Mutex<Tally>;
1014
+ constructor(xs: i32[], tally: Mutex<Tally>) {
1015
+ this.xs = xs;
1016
+ this.tally = tally;
1017
+ }
1018
+ }
1019
+
1020
+ const binAll = (s: Shard): i32 => {
1021
+ for (const x of s.xs) {
1022
+ const bin: i32 = x & 3;
1023
+ using g = s.tally.lock(); // held to the end of this pass
1024
+ g.value.bins[bin] = g.value.bins[bin] + 1;
1025
+ }
1026
+ return 0;
1027
+ };
1028
+
1029
+ export const main = (): i32 => {
1030
+ const tally = new Mutex<Tally>(new Tally()); // a fresh value: `new` or a literal
1031
+ const done: i32[] = [0, 0];
1032
+ {
1033
+ using s = scope();
1034
+ s.spawn(binAll, new Shard([1, 2, 3], tally), done, 0);
1035
+ s.spawn(binAll, new Shard([4, 5, 6], tally), done, 1);
1036
+ }
1037
+ using g = tally.lock(); // after the join: lock freely
1038
+ console.log(`${g.value.bins[1]}`); // 2
1039
+ return 0;
1040
+ };
1041
+ ```
1042
+
1043
+ - **Only `g.value`, and only as the base of a field or element access.** Read
1044
+ a number, a `boolean`, an enum or a `string` through it, or store one into a
1045
+ field or an element that already exists (a string is copied each way) (for a `Mutex<i32>`, `g.value = g.value + 1`).
1046
+ Don't bind, store, return or walk `g` itself (NL2466), nor bind, store, return
1047
+ or walk `g.value` or an array it holds (NL2467); store only a scalar or a
1048
+ `string` into it (NL2468), never `null` (NL2537), and call no method
1049
+ through it (no `push`).
1050
+ - **A guard goes only to a helper**: a top-level function named at the call,
1051
+ whose parameter is `MutexGuard<T>`, once per call — `note(g, x)` with
1052
+ `const note = (g: MutexGuard<Stats>, x: i32): void => { … }`. The helper is
1053
+ held to the block's rules. A method, a constructor or a generic is refused
1054
+ (NL2536): `this` or `T` would be a way to keep the guard.
1055
+ - **Make the data fresh all the way down**: `new Mutex<Hist>(new Hist())`, not
1056
+ `new Hist(bins)` with a `bins` you still hold.
1057
+ - **`lock()` only as `using g = m.lock()`** (NL2465), one lock per `using`. The lock is
1058
+ released at every exit of the block, and at a `return` after the returned
1059
+ value is computed, so `return g.value.n` reads under the lock.
1060
+ - **One lock at a time, and no waiting while it is held**: inside the guard's
1061
+ block, directly or through any call or `new`, no second `lock()`, no
1062
+ `scope()` or `spawn`, no `parallelMapInto` or `parallelReduce`. So a program
1063
+ cannot deadlock.
1064
+ - **Only a scope's tasks and the thread that opened it lock**: the function
1065
+ given to `parallelMapInto` or `parallelReduce` takes no lock, even through a
1066
+ call (NL2474).
1067
+ - **A `Mutex` reaches a task as its argument or a field of its argument's
1068
+ class** — not in an array. From the statement holding a scope's first
1069
+ `spawn` to its block's end the parent doesn't lock what the tasks share,
1070
+ even through a call; before and after, it may.
1071
+ - **Make the updates commute exactly** (an integer sum or count, a histogram;
1072
+ not an `f64` sum): the critical sections run in the scheduler's order
1073
+ natively and in spawn order under Node.
1074
+
1075
+ ```ts nish:err NL2467
1076
+ import { Mutex } from "nish/threads";
1077
+
1078
+ class Counter {
1079
+ n: i32 = 0;
1080
+ }
1081
+
1082
+ export const main = (): i32 => {
1083
+ const m = new Mutex<Counter>(new Counter());
1084
+ using g = m.lock();
1085
+ const c = g.value; // what the guard reaches would outlive the lock
1086
+ return c.n;
1087
+ };
1088
+ ```
1089
+
1090
+ ```ts nish:ok
1091
+ import { Mutex, MutexGuard } from "nish/threads";
1092
+
1093
+ class Stats {
1094
+ count: i32 = 0;
1095
+ }
1096
+
1097
+ const bump = (g: MutexGuard<Stats>): void => {
1098
+ g.value.count = g.value.count + 1; // a helper: the block's rules, the block's lock
1099
+ };
1100
+
1101
+ export const main = (): i32 => {
1102
+ const m = new Mutex<Stats>(new Stats());
1103
+ using g = m.lock();
1104
+ bump(g);
1105
+ return g.value.count - 1; // 0
1106
+ };
1107
+ ```
1108
+
1109
+ ### Counters without a lock: `AtomicI32` and `AtomicI64`
1110
+
1111
+ A shared counter, minimum, maximum or flag is an atomic: `new AtomicI32(0)`,
1112
+ `new AtomicI64(toI64(0))`, updated from any task or parallel `f` by one
1113
+ indivisible method call.
1114
+
1115
+ ```ts nish:ok
1116
+ import { AtomicI32, parallelMapInto } from "nish/threads";
1117
+
1118
+ export const main = (): i32 => {
1119
+ const hits = new AtomicI32(0);
1120
+ const top = new AtomicI32(0);
1121
+ const xs: i32[] = [3, 9, 4];
1122
+ const ys: i32[] = [0, 0, 0];
1123
+ parallelMapInto(xs, ys, (x) => hits.add(1) + top.max(x)); // each answers the old value
1124
+ console.log(`${hits.load()} ${top.load()}`); // 3 9
1125
+ return 0;
1126
+ };
1127
+ ```
1128
+
1129
+ - **Only its methods**: `load`, `store`, `add`, `sub`, `min`, `max`,
1130
+ `exchange`, `compareExchange(expected, desired)`; each update answers the
1131
+ value held just before it. No field to read.
1132
+ - **Not inside a scope's block after the first `spawn`** (NL2513): read it
1133
+ before, or after the block. Two scopes of one `using` share no atomic
1134
+ (NL2516): give each scope a block of its own, `{ using s = scope(); … }`
1135
+ then `{ using t = scope(); … }`.
1136
+ - **Make the updates commute** — `add`, `sub`, `min`, `max` — or native and
1137
+ Node differ.
1138
+ - **Add once per batch**: count into a local array, then one `add` per bin.
1139
+ One `add` per value makes the cores wait on each other.
1140
+ - **`AtomicI64` takes `i64` values**, exact under Node within ±2^53.
1141
+
1142
+ ```ts nish:err NL2513
1143
+ import { AtomicI32, scope } from "nish/threads";
1144
+
1145
+ const bump = (a: AtomicI32): i32 => a.add(1);
1146
+
1147
+ export const main = (): i32 => {
1148
+ const a = new AtomicI32(0);
1149
+ const done: i32[] = [0];
1150
+ let seen: i32 = 0;
1151
+ {
1152
+ using s = scope();
1153
+ s.spawn(bump, a, done, 0);
1154
+ seen = a.load(); // the task may not have run yet
1155
+ }
1156
+ return seen;
1157
+ };
1158
+ ```
1159
+
1160
+ ### Passing values between tasks: `Channel<T>`
1161
+
1162
+ When one task produces values and another consumes them, give both a
1163
+ `Channel`: the producers `send`, and the consumer walks the channel with
1164
+ `for...of`, which waits for values and ends once every producer has returned.
1165
+ Nobody closes a channel.
1166
+
1167
+ ```ts nish:ok
1168
+ import { Channel, scope } from "nish/threads";
1169
+
1170
+ class Pipe {
1171
+ ch: Channel<i32>;
1172
+ n: i32;
1173
+ constructor(ch: Channel<i32>, n: i32) {
1174
+ this.ch = ch;
1175
+ this.n = n;
1176
+ }
1177
+ }
1178
+
1179
+ const produce = (p: Pipe): i32 => {
1180
+ for (let i: i32 = 1; i <= p.n; i++) {
1181
+ p.ch.send(i); // never waits
1182
+ }
1183
+ return p.n;
1184
+ };
1185
+
1186
+ const consume = (p: Pipe): i32 => {
1187
+ let sum: i32 = 0;
1188
+ for (const x of p.ch) { // ends once every sender has returned
1189
+ sum = sum + x;
1190
+ }
1191
+ return sum;
1192
+ };
1193
+
1194
+ export const main = (): i32 => {
1195
+ const sent: i32[] = [0, 0];
1196
+ const sums: i32[] = [0];
1197
+ const ch = new Channel<i32>(); // a `const`, right before its scope
1198
+ {
1199
+ using s = scope();
1200
+ s.spawn(produce, new Pipe(ch, 10), sent, 0); // every sender first
1201
+ s.spawn(produce, new Pipe(ch, 20), sent, 1);
1202
+ s.spawn(consume, new Pipe(ch, 0), sums, 0); // then the one receiver
1203
+ }
1204
+ console.log(`${sums[0]}`); // 265
1205
+ return 0;
1206
+ };
1207
+ ```
1208
+
1209
+ - **It carries a number, a `boolean`, an enum or a `string`** (copied in by
1210
+ `send`, out by the receive), never an array or an object.
1211
+ - **Declare it as a `const` right before its scope** — in the block that holds
1212
+ `using s = scope()`, or in the block around the scope's block — and hand it
1213
+ to tasks only as the task's argument or a field of the object you build for
1214
+ it in the `spawn` (`new Pipe(ch, 10)`). Don't bind it to another name, store
1215
+ it, return it, or name any member but `send`. Passing it to a function
1216
+ whose parameter is declared `Channel<T>` is fine; pushing it onto an array,
1217
+ putting it in a `Map`, or passing it as a generic's `T` is not. A `spawn`
1218
+ hands a task only a channel the function itself declared, never one the
1219
+ function was handed, and no task gets one channel in two fields. Hold it
1220
+ only as `Channel<T>` (no `| null`, no `Channel<T>[]`, no `Map` of them), and
1221
+ use it only in a top-level function's body, never inside an arrow
1222
+ (NL2484): write the task as a module function,
1223
+ `const sendAll = (ch: Channel<i32>): void => { … }`, and spawn that.
1224
+ `new Channel<i32>()` takes no capacity: it is unbounded.
1225
+ - **One scope, one receiver.** Only one scope's tasks use a channel. Its one
1226
+ receiver is either a single task spawned outside any loop, or the parent
1227
+ after the scope's block; never both, never two. The parent sends only before
1228
+ the scope's block, and receives nothing inside it.
1229
+ - **Senders before the receiver.** Spawn every task that may send before the
1230
+ task that receives (NL2481: `s.spawn(produce, …)` above
1231
+ `s.spawn(consume, …)`), and never let one task both send and receive on the
1232
+ same channel. So channels cannot deadlock.
1233
+ - **No receive while a lock is held**, even through a call. A `send` under a
1234
+ guard is fine.
1235
+ - **Print what the interleaving cannot change**: two senders' values arrive in
1236
+ the scheduler's order natively and in spawn order under Node, so a count or an
1237
+ integer sum prints the same both ways.
1238
+
1239
+ ```ts nish:err NL2481
1240
+ import { Channel, scope } from "nish/threads";
1241
+
1242
+ const one = (ch: Channel<i32>): i32 => {
1243
+ ch.send(1);
1244
+ return 1;
1245
+ };
1246
+
1247
+ const drain = (ch: Channel<i32>): i32 => {
1248
+ let sum: i32 = 0;
1249
+ for (const x of ch) {
1250
+ sum = sum + x;
1251
+ }
1252
+ return sum;
1253
+ };
1254
+
1255
+ export const main = (): i32 => {
1256
+ const out: i32[] = [0, 0];
1257
+ const ch = new Channel<i32>();
1258
+ {
1259
+ using s = scope();
1260
+ s.spawn(drain, ch, out, 0); // the receiver first: under Node it finds nothing
1261
+ s.spawn(one, ch, out, 1);
1262
+ }
1263
+ return out[0];
1264
+ };
1265
+ ```
1266
+
1267
+ ### What crosses between threads, and `nish --fix`
1268
+
1269
+ - **Crosses, by copy:** a number, a `boolean`, an enum, a `string`, an array
1270
+ of numbers, booleans or enums — a task's answer, a `parallelMap` element, a
1271
+ `Channel<string>` value, a string stored through a guard. Each thread moves
1272
+ the `parallelMap` elements it answers into blocks of its own, and the caller
1273
+ takes the blocks whole at the join; a task's answer under 64 KB is copied in
1274
+ by the joining thread. On one thread every element still pays the move, so
1275
+ cheap work that answers strings can be slower there than sequential.
1276
+ - **Doesn't cross:** a class value, a nested array, a `string[]`, a `Map`, a
1277
+ `Set`. Answer what it is built from, and build it after the join.
1278
+ - **`nish --fix` writes the usual repair**: `const` to `using` for a scope or a
1279
+ guard (NL2388, NL2465), a destination for a number or `boolean` answer
1280
+ (NL2511), a `const kNow = k` snapshot of a `let` an arrow reads (NL2506,
1281
+ NL2500), and a channel arrow hoisted to a top-level `const` (NL2484). It
1282
+ leaves to you a `spawn` in a loop, a `let` another argument names or a `for`
1283
+ header declares, and a channel arrow that captures or leaves a parameter's or
1284
+ its result's type out.
1285
+ - **Every thread refusal ends with the code to write**, in your names where
1286
+ the compiler has them; the guide's
1287
+ [refusal table](guide/using-nish/threads.md#refused-and-what-to-write-instead)
1288
+ pairs each code with an example that compiles.
1289
+
1290
+ ```ts nish:err NL2506
1291
+ import { scope } from "nish/threads";
1292
+
1293
+ export const main = (): i32 => {
1294
+ let k: i32 = 3;
1295
+ const out: i32[] = [0];
1296
+ {
1297
+ using s = scope();
1298
+ s.spawn((n: i32): i32 => n + k, 1, out, 0); // --fix: `const kNow = k;` above, the arrow reads `kNow`
1299
+ }
1300
+ return out[0];
1301
+ };
1302
+ ```
1303
+
1304
+ ### Calling C
1305
+
1306
+ `declare function name(params): T;` declares a C function this program calls but
1307
+ does not define. The symbol is the identifier, unmangled, in any package
1308
+ (a package's own functions are prefixed, its declarations are not), and the
1309
+ call is an ordinary call. Two declarations of one name must have the same
1310
+ types.
1311
+
1312
+ ```ts nish:ok
1313
+ declare function abs(n: i32): i32;
1314
+
1315
+ export const main = (): i32 => {
1316
+ console.log(`${abs(-5)}`);
1317
+ return 0;
1318
+ };
1319
+ ```
1320
+
1321
+ - **A parameter is a scalar, a `CPtr`, a borrowed `string` or a `u8[]` buffer
1322
+ with its count; a return is a scalar, a `CPtr` or a copied `string`.** The
1323
+ scalars are the integer and float widths, `boolean` and `void`. Any other
1324
+ array, a class or an interface is rejected, in either position.
1325
+ - **A run of `u8[]` / `readonly u8[]` parameters is followed by exactly one
1326
+ `u64`**, the byte count C may touch in each (`memcpy(dst: u8[], src:
1327
+ readonly u8[], n: u64)`). `readonly` is C's `const`. A run without its count
1328
+ is NL2549, a count that is not `u64` NL2550, `i32[]` NL2551, `u8[] | null`
1329
+ NL2552. Declare `fwrite` as `(buf: readonly u8[], n: u64, one: u64, f: CPtr)`
1330
+ and pass `1` for `one`. A call passes the data pointer and first checks the
1331
+ count against every buffer of the run: a larger count is an `ffi-length`
1332
+ panic, and only `toU64(buf.length)` of the buffer itself is proved. C
1333
+ borrows the bytes for the call and may write a `u8[]`; a spawned task may
1334
+ not make the call.
1335
+
1336
+ ```ts nish:err NL2549
1337
+ declare function SHA256(d: readonly u8[], n: u64, md: u8[]): CPtr;
1338
+
1339
+ export const main = (): i32 => {
1340
+ return 0;
1341
+ };
1342
+ ```
1343
+
1344
+ - **A `string` parameter is lent for the call.** C gets a NUL-terminated
1345
+ `const char *` that it may read until it returns, and may neither keep nor
1346
+ write; a C function that does either is declared to take a `CPtr` instead.
1347
+ `string | null` passes `NULL` for `null`. A string with a NUL byte in it
1348
+ panics (`ffi-nul`) before the call, because C would read only up to it; a
1349
+ literal with no NUL is not checked.
1350
+
1351
+ ```ts nish:ok
1352
+ declare function puts(s: string): i32;
1353
+
1354
+ export const main = (): i32 => {
1355
+ puts("hello");
1356
+ return 0;
1357
+ };
1358
+ ```
1359
+
1360
+ - **A `string` result is copied out of C's `const char *`** into the arena at
1361
+ the call, so it lives like any other string and C may reuse its buffer.
1362
+ `string | null` maps `NULL` to `null`; a `string` result that comes back
1363
+ `NULL` panics (`ffi-null`). The copy never frees: a result the caller must
1364
+ `free` (`strdup`) is declared `CPtr | null` instead.
1365
+
1366
+ ```ts nish:ok
1367
+ declare function strerror(errnum: i32): string;
1368
+ declare function getenv(name: string): string | null;
1369
+
1370
+ export const main = (): i32 => {
1371
+ console.log(strerror(2));
1372
+ const home = getenv("HOME");
1373
+ console.log(home === null ? "unset" : "set");
1374
+ return 0;
1375
+ };
1376
+ ```
1377
+
1378
+ - **A buffer C allocated is read with `readCString` from `nish:unsafe`, then
1379
+ freed.** `readCString(p: CPtr): string` copies the NUL-terminated bytes at
1380
+ `p` into a new string; a dangling or unterminated `p` is undefined
1381
+ behaviour, which is why it needs the import. It takes a `CPtr`, so narrow a
1382
+ `CPtr | null` with `!== null` first.
1383
+ - **A `CPtr | null` parameter takes the literal `null` or a narrowed pointer.**
1384
+ `realpath(path, null)` passes `NULL`; a nullable pointer held in a variable
1385
+ is NL2539, so C is never handed a `NULL` by accident.
1386
+
1387
+ ```ts nish:ok
1388
+ import { readCString } from "nish:unsafe";
1389
+
1390
+ declare function realpath(path: string, resolved: CPtr | null): CPtr | null;
1391
+ declare function free(p: CPtr): void;
1392
+
1393
+ export const main = (): i32 => {
1394
+ const p = realpath(".", null);
1395
+ if (p === null) {
1396
+ return 1;
1397
+ }
1398
+ const dir = readCString(p);
1399
+ free(p);
1400
+ console.log(dir.startsWith("/") ? "absolute" : "relative");
1401
+ return 0;
1402
+ };
1403
+ ```
1404
+
1405
+ - **No body, and it cannot be `export`ed.**
1406
+ - **A class with a `CPtr` field is a handle class: open one with `using`.**
1407
+ `using f = new File(p)` closes `f` once at every exit of its block (falling
1408
+ off the end, `return`, `break`, `continue`, `orReturn()`), so never call the
1409
+ close yourself. The class is checked: one `readonly` `CPtr` or
1410
+ `CPtr | null` field written once by the constructor's foreign call, read only
1411
+ as `this.f` in its own methods, and a `[Symbol.dispose]` that is exactly the
1412
+ close. A handle is never copied, stored, returned or captured: it is called
1413
+ or lent to a parameter of its type, and `new` of one appears only as a
1414
+ `using` initialiser. A panic or `process.exit` does not close it, so
1415
+ `fflush` a written `FILE *` before code that may panic. The rules:
1416
+ `LANGUAGE.md` → "A handle class".
1417
+ - **It costs the caller its attributes.** Nothing about a body the compiler
1418
+ cannot see is provable, so a function that calls C loses `readnone` and
1419
+ `willreturn`, and so does everything above it in the call graph. A program
1420
+ that calls C is no longer one the compiler can reason about end to end — that
1421
+ trade *is* the feature.
1422
+
1423
+ ### Module constants
1424
+
1425
+ ```ts nish:ok
1426
+ const TOKEN_NAME: i32 = 1;
1427
+ const TOKEN_END: i32 = TOKEN_NAME + 1;
1428
+ export const PROMPT: string = "> ";
1429
+ ```
1430
+
1431
+ A top-level `const` names a value the compiler already knows. **No symbol is
1432
+ emitted and no initialiser runs** — every use lowers to the value itself.
1433
+
1434
+ - The **type annotation is required**, and so is an initialiser.
1435
+ - The type must be `i32`, `i64`, `f64`, `number`, `boolean`, or `string`. An
1436
+ array, class or interface constant would need an allocation, and there is no
1437
+ code to run it.
1438
+ - The initialiser is literals, other constants (declared anywhere, or
1439
+ imported), and arithmetic over them. A call, a `new`, a local — anything else
1440
+ — is rejected.
1441
+ - Folding uses the language's own arithmetic, so an overflowing fold is an
1442
+ error rather than a wrap, and `1 / 0` in a constant is refused at compile
1443
+ time.
1444
+
1445
+ ### Classes and interfaces
1446
+
1447
+ ```ts nish:ok
1448
+ interface Shape { width: i32; height: i32; }
1449
+
1450
+ class Rect implements Shape {
1451
+ width: i32;
1452
+ height: i32;
1453
+ label: string = "r";
1454
+
1455
+ constructor(width: i32, height: i32) {
1456
+ this.width = width;
1457
+ this.height = height;
1458
+ }
1459
+
1460
+ area(): i32 {
1461
+ return this.width * this.height;
1462
+ }
1463
+ }
1464
+
1465
+ const describe = (s: Shape): i32 => s.width + s.height;
1466
+
1467
+ export const main = (): i32 => {
1468
+ const r = new Rect(3, 4);
1469
+ console.log(`${r.area()} ${describe(r)}`);
1470
+ return 0;
1471
+ };
1472
+ ```
1473
+
1474
+ - Fields need annotations; an initializer must be a **literal** of the field's
1475
+ type. Layout is declaration order, exactly as clang lays out the same C
1476
+ struct.
1477
+ - At most one constructor, no parameter properties, no overloads. **Definite
1478
+ assignment** is checked syntactically: after the constructor returns every
1479
+ field holds a value, and reading `this.f` before every field is assigned is
1480
+ an error.
1481
+ - Methods need a body and an explicit return type. `readonly` fields may be
1482
+ assigned only as `this.f = v` in their own class's constructor.
1483
+ - `===` / `!==` on two values of one class is pointer identity; `<` and friends
1484
+ are rejected.
1485
+ - **No inheritance.** `extends` and `super` are rejected in every spelling.
1486
+ - An **interface** is a struct with fields only — no methods, no `extends`, no
1487
+ `new`.
1488
+ - **`class C implements I` requires `I`'s fields to be `C`'s first fields**, in
1489
+ order, with identical types; `C` may declare more after them. A `C` then
1490
+ converts to an `I` wherever one is expected. **This is the only widening in
1491
+ the language**, and nothing converts back — no downcast, no `instanceof`.
1492
+
1493
+ ### An array of records is one contiguous block
1494
+
1495
+ `Point[]` where `Point` is a class or interface stores the records themselves,
1496
+ back to back — not an array of pointers. So `ps[i]` is an *interior* pointer,
1497
+ and `push` may move the whole block to grow it. **Holding an element across a
1498
+ `push` on the same array is therefore refused**, because the write would land
1499
+ in the old storage:
1500
+
1501
+ ```ts nish:err NL2290
1502
+ interface Point { x: i32; }
1503
+
1504
+ export const main = (): i32 => {
1505
+ const ps: Point[] = [{ x: 1 }];
1506
+ const p = ps[0];
1507
+ ps.push({ x: 2 });
1508
+ p.x = 3;
1509
+ return 0;
1510
+ };
1511
+ ```
1512
+
1513
+ Index again after the `push` instead of holding the element across it:
1514
+
1515
+ ```ts nish:ok
1516
+ interface Point { x: i32; }
1517
+
1518
+ export const main = (): i32 => {
1519
+ const ps: Point[] = [{ x: 1 }];
1520
+ ps.push({ x: 2 });
1521
+ ps[0].x = 3;
1522
+ console.log(`${ps[0].x}`);
1523
+ return 0;
1524
+ };
1525
+ ```
1526
+
1527
+ ### Object literals
1528
+
1529
+ An object literal takes its type from context — a variable annotation, the
1530
+ enclosing function's return type, the parameter it is passed to, the field it
1531
+ is assigned to, or a field of an enclosing literal. It must set every field
1532
+ exactly once, with plain identifier keys.
1533
+
1534
+ ```ts nish:ok
1535
+ interface Pair { first: i32; second: i32; }
1536
+
1537
+ const swap = (p: Pair): Pair => ({ first: p.second, second: p.first });
1538
+
1539
+ export const main = (): i32 => {
1540
+ const p: Pair = { first: 1, second: 2 };
1541
+ console.log(`${swap(p).first}`);
1542
+ return 0;
1543
+ };
1544
+ ```
1545
+
1546
+ The context reaches a nested literal and a `null`, and stops there: a numeric
1547
+ literal in a property keeps the mode's default type (above), and `[]` has no
1548
+ element type at all in that position.
1549
+
1550
+ A class with methods is made only by `new`: a literal typed as one would be a
1551
+ plain object under Node, without the methods (NL2556). A class with fields
1552
+ alone, and an interface, still take a literal.
1553
+
1554
+ ```ts nish:err NL2556
1555
+ class Counter {
1556
+ n: i32;
1557
+ constructor(n: i32) {
1558
+ this.n = n;
1559
+ }
1560
+ get(): i32 {
1561
+ return this.n;
1562
+ }
1563
+ }
1564
+
1565
+ export const main = (): i32 => {
1566
+ const c: Counter = { n: 5 }; // write `new Counter(5)`
1567
+ return c.get();
1568
+ };
1569
+ ```
1570
+
1571
+ ### Type aliases and enums
1572
+
1573
+ ```ts nish:ok
1574
+ type Byte = u8;
1575
+ type Bytes = Byte[];
1576
+
1577
+ enum Kind {
1578
+ If = 1,
1579
+ While = 2,
1580
+ }
1581
+
1582
+ const name = (k: Kind): string => {
1583
+ switch (k) {
1584
+ case Kind.If:
1585
+ return "if";
1586
+ default:
1587
+ return "while";
1588
+ }
1589
+ };
1590
+
1591
+ export const main = (): i32 => {
1592
+ console.log(name(Kind.If));
1593
+ return 0;
1594
+ };
1595
+ ```
1596
+
1597
+ - A `type` alias is a second name for an existing type, **not a type of its
1598
+ own** — the same program with and without its aliases emits byte-identical
1599
+ IR. A generic *alias* is still forbidden even though a generic function,
1600
+ class and interface are not; built-in names may not be aliased.
1601
+ - A numeric `enum` is a **distinct type** represented as `i32`, and that is the
1602
+ point: `Kind` and `i32` never convert in either direction. Members must be
1603
+ numeric literals. `===` and `!==` are the **only** operators — no arithmetic,
1604
+ no bitwise, so **bit flags stay `i32` module constants**.
1605
+ - **A member is also a type.** `Kind.While` as an annotation is a slot that
1606
+ holds exactly that member — the shape of a tag field that says which case
1607
+ its object is. Only the member's own constant (or a value already of that
1608
+ member type) enters it; any other member, or a `Kind` value, is NL2519. A
1609
+ member type converts to its enum, and a read of one *is* the enum, so it
1610
+ compares, switches and passes as a `Kind`. The IR is the enum's `i32`. A
1611
+ generic call takes the member's constant where the member type is wanted
1612
+ (`fold(xs, Kind.Array)` with `xs: Kind.Array[]`), parenthesised or not.
1613
+ It binds a type parameter only from the arguments, so `id(Kind.Array)`
1614
+ into a `Kind.Array` slot, and `apply(take, Kind.Array)` with
1615
+ `take: (x: Kind.Array) => Kind.Array`, bind `T` to `Kind` and are refused:
1616
+ pass a value already of the member type instead. An unannotated `let`
1617
+ keeps the member type of a value already of it, and widens the member's
1618
+ constant to `Kind`, as `tsc` does; so do a ternary, an array literal, `??`
1619
+ and an arrow's result (`[a]` is a `Kind.Array[]`, and so is
1620
+ `map([a], (k) => k)`; `(k) => Kind.Array` answers `Kind`). Arms or elements
1621
+ that differ where one is a member type or an array of one (`c ? a : b`,
1622
+ `c ? a : k`, `[a, Kind.Array]`, `[[a], [Kind.Array]]`) are refused:
1623
+ annotate the variable.
1624
+ `new Array<Kind.Array>(n)` zero-fills, so it is NL2520 unless the member is
1625
+ 0; build such an array with `[]` and `push`. A `parallelMap`,
1626
+ `parallelFrom` or `parallelReduce` body answering one is NL2521: answer
1627
+ `Kind` instead.
1628
+
1629
+ ```ts nish:ok
1630
+ enum Kind {
1631
+ Scalar,
1632
+ Array,
1633
+ }
1634
+
1635
+ class ArrayNode {
1636
+ readonly kind: Kind.Array = Kind.Array;
1637
+ size: i32 = 0;
1638
+ }
1639
+
1640
+ const isArray = (k: Kind): boolean => k === Kind.Array;
1641
+
1642
+ export const main = (): i32 => {
1643
+ const n = new ArrayNode();
1644
+ return isArray(n.kind) ? 0 : 1;
1645
+ };
1646
+ ```
1647
+
1648
+ ```ts nish:err NL2519
1649
+ enum Kind {
1650
+ Scalar,
1651
+ Array,
1652
+ }
1653
+
1654
+ const pick = (k: Kind): Kind.Array => k; // a `Kind` could be any member
1655
+
1656
+ export const main = (): i32 => (pick(Kind.Array) === Kind.Array ? 0 : 1);
1657
+ ```
1658
+
1659
+ - **Both can be exported.** `export enum Kind` is imported like a function,
1660
+ `import { Kind } from "./kinds"` (renamed with `as` if you like), and in the
1661
+ importer it *is* the exporter's `Kind`: the members fold to the exporter's
1662
+ integers and a `Kind` value crosses a call as itself. A `Kind` a second
1663
+ module declares for itself is a different type, however alike the two are.
1664
+ `export type Conn = Socket | null` is imported the same way and is, in the
1665
+ importer, the type it names; its right-hand side is resolved in the module
1666
+ that wrote it, so the importer need not import `Socket`. A module does not
1667
+ re-export an enum or alias it imported.
1668
+ - Both are top-level only.
1669
+
1670
+ ### Discriminated unions
1671
+
1672
+ ```ts nish:ok
1673
+ enum Kind {
1674
+ Circle,
1675
+ Square,
1676
+ }
1677
+
1678
+ class Circle {
1679
+ readonly kind: Kind.Circle = Kind.Circle;
1680
+ r: i32 = 3;
1681
+ }
1682
+
1683
+ class Square {
1684
+ readonly kind: Kind.Square = Kind.Square;
1685
+ side: i32 = 4;
1686
+ }
1687
+
1688
+ type Shape = Circle | Square;
1689
+
1690
+ const pick = (round: boolean): Shape => (round ? new Circle() : new Square());
1691
+
1692
+ export const main = (): i32 => {
1693
+ const shapes: Shape[] = [pick(true), new Square()];
1694
+ let circles = 0;
1695
+ let area = 0;
1696
+ for (const s of shapes) {
1697
+ if (s.kind === Kind.Circle) {
1698
+ circles = circles + 1; // `s` is a `Circle` here
1699
+ area = area + 3 * s.r * s.r;
1700
+ } else {
1701
+ area = area + s.side * s.side; // and a `Square` here
1702
+ }
1703
+ }
1704
+ return circles === 1 && area === 43 ? 0 : 1;
1705
+ };
1706
+ ```
1707
+
1708
+ - A **variant** is a class or interface, not generic, whose **first field is
1709
+ its tag**, of one enum member type: `readonly kind: Kind.Circle =
1710
+ Kind.Circle`. All variants of a union take their tags from one enum, under
1711
+ one field name, with different members; a union names at least two. Each
1712
+ broken rule has its code (NL2522–NL2527); a tag that is not `readonly` is
1713
+ NL2535, one typed as the whole enum (`readonly kind: Kind`) NL2559. Any
1714
+ other union is still NL1053.
1715
+ - A union is a local, parameter, field, return, array element, or nullable
1716
+ (`Shape | null`). An alias may name another union, which is flattened. A
1717
+ variant, or a smaller union, converts into it wherever the union is
1718
+ expected — argument, `return`, `push`, an annotated array literal, a
1719
+ ternary arm or `??` default whose other side is the union. A class that
1720
+ implements an interface variant converts to that interface first. A union
1721
+ crosses modules but not a sidecar (NL4009).
1722
+ - **Test the tag to read a variant.** Before a test, the tag is the only
1723
+ member (NL2528 for anything else, even a field every variant has). A test of
1724
+ a local's or parameter's tag narrows it as `tsc` does: `if`/`else`, a
1725
+ ternary, `&&`, `||`, `!==`, an early `return`/`break`/`continue`, each
1726
+ `case` of a `switch (s.kind)`, and composed with `s !== null &&`. Two or
1727
+ more variants left still give only the tag; an assignment drops the
1728
+ narrowing. Where `a || b` held (or `a && b` failed), the variable is what
1729
+ either side proved, `Circle | Square` after two tag tests. A test reads
1730
+ through an assignment: `(t = next()).kind === Kind.Square` narrows `t`. Past an `if`, the
1731
+ variable is what the arms that fall through left it (an `else if` chain
1732
+ that returns leaves the variant no test named), including what a test nested
1733
+ in each arm proved: two arms that each return when `s.kind === Kind.A` leave
1734
+ `s` not a `VA`; a `switch`'s exits join the same way. A path does not narrow the
1735
+ union (`h.s.kind` then `h.s.r` is NL2558): copy it into a local and test
1736
+ that. The tag read through a path (`h.k`, `this.k`, `h.s.kind`,
1737
+ `xs[0].kind`, `xs[i].kind`) does stay narrowed for later comparisons, until a store to
1738
+ the path, a prefix of it or its root. An index `i` is part of the path when
1739
+ it holds one value: a parameter, a `const` (`const i = 0` makes `xs[i]` the
1740
+ path `xs[0]`, and so does `const j = i` after it), a module constant
1741
+ (`const IDX: i32 = 0` makes `xs[IDX]` the path `xs[0]`), or a `let` nothing
1742
+ assigns; through a `let` that is assigned
1743
+ or stepped, `xs[i]` is not narrowed, and `xs[j] = t` drops nothing. A read where every variant was excluded is
1744
+ NL2557.
1745
+ - The tag reads as the enum: `const k: Kind = s.kind` — annotate it,
1746
+ `const k = s.kind` is NL2530. `k` holds only the variants' members. A
1747
+ union is read-only, even for a field every variant has (NL2529). Comparing
1748
+ the tag (or such a `k`) with a member it cannot hold — one no variant has,
1749
+ or one a test already excluded — is NL2531, a `case` for one NL2532.
1750
+ - A `switch` whose subject reads a variant's tag field (`s.kind`,
1751
+ `h.shape.kind`, `xs[i].kind`, the receiver typed as the union) and whose
1752
+ labels name every variant, with no `default`, is complete: if every
1753
+ clause returns, it ends a function that returns a value, and its unmatched
1754
+ case is `unreachable` in the IR. A statement after it is unreachable code
1755
+ when every clause returns; a clause's `break` reaches it.
1756
+ With a variant unnamed it is the missing-return error, as TS2366. A
1757
+ `switch (k)` on an enum variable (a parameter, or `const k: Kind = s.kind`,
1758
+ `let` or `const`), or on an enum field assigned from a tag
1759
+ (`b.k = s.kind`), is never complete, whatever its labels
1760
+ (C may pass an enum any `i32`): give it a `default` or return after it.
1761
+ A path's tag a test narrowed (`h.s.kind` after
1762
+ `if (h.s.kind === Kind.A) return`) needs every tag of the whole union; a
1763
+ narrowed union local is complete with the variants left to it.
1764
+ - An enum variable, a parameter `k: Kind` as much as such a tag local, narrows
1765
+ on `k === Kind.A` / `!==` in the same forms as a union, `switch (k)`
1766
+ included: after `if (k === Kind.A) return`, `k === Kind.A` is NL2531.
1767
+ Narrowed to one member, `let j = k` is a `Kind.A`; to several,
1768
+ `const j = k` is NL2530 (annotate it `Kind`). `s.kind === k` narrows `s`
1769
+ to the variants of `k`'s members where they are equal. An enum local an
1770
+ `if`, ternary, loop or `switch` stores into holds, after it, what it held
1771
+ or any member stored; after a `switch`, what its exits left (each `break`,
1772
+ the last clause's end, and the unmatched case when there is no `default`);
1773
+ after a loop, what its condition leaves false at the head, joined until it
1774
+ no longer changes, and each `break`, as `tsc` reads it — a variable the
1775
+ loop only tests included, and an `if` or `switch` inside the loop narrowing
1776
+ as it does outside; a `for` update is read after the body. A loop holding
1777
+ a path (`h.k`), a store inside an expression (an argument, an initializer,
1778
+ a chain's inner `=`), a store of a field or method call read through an
1779
+ element, a call or a receiver the loop declares (`gs[0].b`,
1780
+ `makeG().b`), of a `let` the loop declares or of a `const` it declares
1781
+ from a tracked variable (a swap's `const t: Kind = a`), a ternary arm
1782
+ reading the stored variable where it holds no member
1783
+ (`k = k === Kind.A ? Kind.B : k`), or a type of more than 29 members
1784
+ falls back to every member the body stored
1785
+ anywhere or the whole type, which accepts a few comparisons `tsc` refuses
1786
+ (TS2367; LANGUAGE.md, "Accepted here, refused by `tsc`").
1787
+ - **Refused by design:** union type arguments (`Box<Shape>`,
1788
+ `Map<K, Shape>`, `Result<Shape, E>`, a generic call binding `T` to a
1789
+ union: NL2533), unions as `Map`/`Set` keys, covariant arrays (`Circle[]`
1790
+ is not `Shape[]`), and inferred unions (an unannotated ternary or array of
1791
+ two variants).
1792
+ - **Declare what you store.** A store of a value that is only some of the
1793
+ variants into a union slot is NL2534, because `tsc` would narrow the slot
1794
+ from there on. Declare the slot as the variant, or store a value of the
1795
+ whole union (a call returning `Shape`):
1796
+
1797
+ ```ts nish:err NL2534
1798
+ enum Kind {
1799
+ Circle,
1800
+ Square,
1801
+ }
1802
+
1803
+ class Circle {
1804
+ readonly kind: Kind.Circle = Kind.Circle;
1805
+ }
1806
+
1807
+ class Square {
1808
+ readonly kind: Kind.Square = Kind.Square;
1809
+ }
1810
+
1811
+ type Shape = Circle | Square;
1812
+
1813
+ export const main = (): i32 => {
1814
+ const s: Shape = new Circle(); // tsc treats `s` as a Circle from here
1815
+ return s.kind === Kind.Circle ? 0 : 1;
1816
+ };
1817
+ ```
1818
+
1819
+ ### Modules
1820
+
1821
+ - `export` goes on `const` (function or constant), `class`, `interface`,
1822
+ `enum` and `type` declarations. No `export default`, no `export { … }`, no
1823
+ `export *`, no `export =`: each is refused by its own rule, once.
1824
+ - The only import form is a **named import**:
1825
+ `import { square, cube as pow3 } from "./math"`. `.ts` is optional. Default
1826
+ imports, namespace imports, side-effect imports, type-only imports,
1827
+ `import x = require(…)`, import attributes (`with { … }`) and
1828
+ `import defer` are all rejected, each by its own rule and before anything
1829
+ is loaded. `type`, `as` and `from` are still names: `import { type } from`
1830
+ imports a function called `type`.
1831
+ - A specifier is a relative path (`./x`, `../x`), a builtin module (`nish:fs`),
1832
+ a standard-library module (`nish/text`, `nish/pair`), or a **package name** (`hash`,
1833
+ `@scope/hash`, with a subpath after it if you want one).
1834
+ - **A package is resolved through `node_modules` and compiled from source.**
1835
+ The package's `package.json` must offer the file under the `nish` export
1836
+ condition — `{"exports": {".": {"nish": "./src/index.ts"}}}` — and the mode
1837
+ gets a spelling of its own, `nish-i32` / `nish-f64`, for source that is only
1838
+ correct under one `--number-mode`. The mode-qualified condition wins wherever
1839
+ the package declares it, so a manifest carrying both never compiles the wrong
1840
+ one of the two. A package without that condition is
1841
+ `` Package `lodash` has no Nish entry point ``, which is what an ordinary npm
1842
+ package gets: there is nothing to compile in a `.js` file. One that offers
1843
+ only the other mode is `` supports Nish in f64 mode only ``, one whose
1844
+ `"engines": {"nish": ">=X.Y.Z"}` is above this compiler `` needs a newer
1845
+ compiler ``, and a `package.json` that is not JSON names its line and
1846
+ column — each with its own `--json` code (`NL3017`–`NL3021`).
1847
+ - Functions and enums may be renamed on import; classes and interfaces may
1848
+ not — the type name is part of the ABI.
1849
+ - Import cycles are allowed; a shared dependency is compiled once.
1850
+ - **A function name is unique across the whole program**, exported or not.
1851
+ - An `export`ed function is an external C-ABI symbol; every other function gets
1852
+ `internal` linkage so LLVM may inline or drop it.
1853
+ - **So an exported function is not named after a C library function**
1854
+ (`write`, `read`, `free`, `exit`, `strlen`, `sin`, …, the list in
1855
+ `src/c-symbols.ts`) and does not begin with `_`, `napi_` or `node_api_`: its symbol would
1856
+ replace the C one in the linked program, in a native, a wasm or an N-API
1857
+ build alike. `` `write` is the C library's `write`: an exported function of the program's own package keeps its name as its symbol, so the references the C library and the runtime make to that name would reach this function instead; rename it ``
1858
+ (NL3037). Name it `writeLine`, or leave it private: a private function with
1859
+ such a name gets a symbol ending in `$`.
1860
+ - **Nor, in a wasm build, `memory`**: a wasm `--target`, `--profile wasi` or
1861
+ `--emit-dts` refuses it, because the `--profile wasm` module exports its
1862
+ linear memory under that name; wasi reserves it too, so a program that
1863
+ builds for wasi also builds as a freestanding wasm module. `` `memory` is the name a wasm module exports its linear memory under: an exported function of the program's own package keeps its name as its symbol, so the module would export two things named `memory` and no host could load it; rename it ``
1864
+ (NL3038). A native build keeps it.
1865
+
1866
+ ## Statements
1867
+
1868
+ - **Conditions must be `boolean`.** There is no truthiness, in `if`, `while`,
1869
+ `for`, the ternary, `&&` or `||`.
1870
+ - `let` / `const` need an initializer and take their type from it; an
1871
+ annotation must match **exactly**. `const` freezes the binding, not the
1872
+ contents: `xs[0] = 1` and `xs.push(1)` on a `const xs` are fine.
1873
+ - `var` is forbidden. Destructuring is not supported.
1874
+ - **Semicolons are optional**, where TypeScript would insert one: at a line
1875
+ break, before `}` and at the end of the file. It is TypeScript's rule, so a
1876
+ line starting with `(` or `[` continues the one before it, and `return`
1877
+ followed by a line break returns nothing. Here, the value left on the next
1878
+ line is an unreachable-code error rather than a silent bug.
1879
+ - `for (init; cond; update)` with every clause optional, and
1880
+ `for (const x of xs)` over an **array** only — `x` gets the element type and
1881
+ must not be annotated. The array's `length` is re-read each iteration, so a
1882
+ `push` inside the body extends the loop. `for...in`, `for await` and
1883
+ `for (x of xs)` over an existing `x` are each refused by name.
1884
+ - `switch` takes an **integer or enum** discriminant; every `case` label is an
1885
+ integer constant expression of that type. **There is no implicit
1886
+ fallthrough** — a clause with statements ends in `break`, `return`,
1887
+ `continue` or `process.exit`, unless it is the last. An *empty* clause does
1888
+ fall through, which is how `case 1: case 2:` shares a body. A clause cannot
1889
+ declare a variable directly; wrap its body in a block.
1890
+ - `break` / `continue` are unlabelled only; labeled statements are forbidden.
1891
+ - `process.exit(code)` and `panic(msg)` **terminate control flow**, so a
1892
+ non-`void` function may end with either.
1893
+ - **Unreachable code is an error**, not a warning: anything after a `return`,
1894
+ `break`, `continue`, `process.exit`, an `if` whose branches all return, or an
1895
+ infinite loop.
1896
+
1897
+ ```ts nish:err NL2166
1898
+ const classify = (n: i32): string => {
1899
+ switch (n) {
1900
+ case 0:
1901
+ console.log("zero"); // no `break`: a clause with statements must end in one
1902
+ case 1:
1903
+ return "one";
1904
+ default:
1905
+ return "many";
1906
+ }
1907
+ };
1908
+
1909
+ export const main = (): i32 => {
1910
+ console.log(classify(0));
1911
+ return 0;
1912
+ };
1913
+ ```
1914
+
1915
+ ## Expressions
1916
+
1917
+ - Arithmetic `+ - * / %` takes **two numbers of one type**. `+` on two strings
1918
+ concatenates; `string + number` is rejected (use a template literal).
1919
+ - **Integer division is checked**, Rust-style: a zero divisor, or `MIN / -1`,
1920
+ prints to stderr and exits 1. Float division is never checked.
1921
+ - Bitwise `& | ^ ~` and shifts `<< >> >>>` take **integers of one type**.
1922
+ `>>` is arithmetic on a signed type and logical on an unsigned one; the shift
1923
+ count is masked to the operand width.
1924
+ - `=== !==` on any two values of the same type: integers and booleans by value,
1925
+ `f64` by `fcmp` (so `x !== x` is true for `NaN`), **strings by content**,
1926
+ arrays and objects by **identity**.
1927
+ - `< <= > >=` on two numbers of one type and nothing else — not booleans, not
1928
+ strings, not structs.
1929
+ - `++` / `--` work on **numeric mutable locals only**, not fields or elements.
1930
+ - Compound assignment `+= -= *= /= %=` requires a numeric target (so `+=` never
1931
+ concatenates strings); the bitwise forms need an integer target.
1932
+ - **Forbidden**: `,` `in` `instanceof` `typeof` `delete` `void expr` `==`
1933
+ `!=` `**` unary `+`, `?.[i]` and `f?.()`, `?.` outside a chain that ends in
1934
+ `??`, and `??` on anything but a `T | null`, a `?.` chain or a `Map.get`
1935
+ result.
1936
+ - Element access `a[i]` needs an array and a numeric index, and is
1937
+ bounds-checked (negative indices fail too). String-keyed access is forbidden.
1938
+ The check comes off where the compiler proves the index in range — a loop
1939
+ condition `i < xs.length`, or `i < h.xs.length` on a field path — as long as
1940
+ nothing between the test and the access calls a function, stores to a field
1941
+ the path names, or reassigns its root.
1942
+ - Array literals need one element type; `[]` needs a contextual `T[]`. Holes
1943
+ and spread are not supported.
1944
+ - Template literals accept holes of type `string`, `number`, `i64`, `f64` or
1945
+ `boolean` — this is how you convert a number to a string.
1946
+
1947
+ ## The builtins, in full
1948
+
1949
+ No import is needed; these are resolved by name, and a user function of the
1950
+ same bare name shadows the builtin. **This list is exhaustive** — anything not
1951
+ on it does not exist, and inventing a method is the most common way to write a
1952
+ program that does not compile.
1953
+
1954
+ **Output.** `console.log(x)` and `console.error(x)` take exactly one argument
1955
+ of `string | number | i64 | f64 | boolean` and are **statement position only**.
1956
+ `write(s)` / `writeError(s)` take a `string` and add no newline.
1957
+ `panic(message)` writes to stderr and exits 1.
1958
+
1959
+ **`Math`.** `sqrt` `floor` `ceil` `trunc` `sin` `cos` `exp` `log` (all `f64`
1960
+ argument, `f64` result), `pow(x, y)`, `round(x)`, `abs(x)` (any numeric type),
1961
+ `min(a, b)` / `max(a, b)` (exactly two, one type), `clz32(x)` (an `i32` or `u32` in,
1962
+ the leading zero bits out, 0 to 32), `random()`, and the
1963
+ constants `Math.PI` / `Math.E`. The f64-only ones reject an `i32`: write
1964
+ `Math.sqrt(toF64(n))`.
1965
+
1966
+ **Conversions.** `toI32` `toI64` `toU8` `toU16` `toU32` `toU64` `toF32`
1967
+ `toF64`; `f64ToBits(x)` / `bitsToF64(b)` reinterpret rather than convert;
1968
+ `Number(x)`, `parseInt(s)` (base 10, `i32`, no `NaN` — no digits give `0`),
1969
+ `parseFloat(s)`.
1970
+
1971
+ **Constant time.** `ctSelect(mask, a, b)` is `(a & mask) | (b & ~mask)` and
1972
+ `ctEq(a, b)` is all-ones when equal and zero otherwise, both over `u32` or
1973
+ `u64` only, every operand one type. The mask sits behind an optimisation
1974
+ barrier, so neither ever becomes a branch; select and compare on a secret with
1975
+ these, never with `if`, `?:`, `===` or `table[secret]`, which they cannot fix.
1976
+ `secureZero(bytes)` sets every byte of a `u8[]` to zero in stores the optimiser
1977
+ may not drop, as a statement: how a key in a plain array is cleared once used.
1978
+
1979
+ **Secrets.** Hold a key as a `Secret<T>` from `nish:secret` (`T` an integer
1980
+ array or a record of integer fields). It is opaque: no printing, interpolating,
1981
+ comparing with a value, branching, indexing, `.value`, array element or
1982
+ `Result` payload, a field other than a holder's `readonly` one (below), and no
1983
+ builtin takes one. Read it only with `expose(s, f)` or
1984
+ `exposeWith(s, arg, f)`, where `f` is a top-level function or an arrow that
1985
+ declares its return type, reaches no I/O and no C, keeps nothing of the value
1986
+ and returns no `Secret` — that return is what leaves. A `Secret` your function
1987
+ makes must be `wipe`d or returned **on every path** (each `return`, `break`,
1988
+ `orReturn()`), is moved by `const j = k`, and is never read after `wipe`;
1989
+ `secret(local)` moves the local too. `secret(v)` takes only a value nothing
1990
+ else holds (NL2512): a literal, a `new`, a call that makes one, or a local that
1991
+ is the only name for its value. A second name made before the move — `const
1992
+ alias = raw`, a field or element store, `push`, `Ok(raw)`, a ternary arm, a
1993
+ function that keeps its parameter, a data-parallel arrow that captures it — is
1994
+ refused with where it was made; pass a fresh value, or a copy
1995
+ (`const copy: u8[] = new Array<u8>(raw.length); copy.set(raw)`, then
1996
+ `secret(copy)`). `std/crypto`'s `p256`, `x25519` and
1997
+ `x509ParseP256PrivateKey` take and give keys this way.
1998
+
1999
+ ```ts nish:ok
2000
+ import { Secret, expose, secret, wipe } from "nish:secret";
2001
+
2002
+ const key = (): u8[] => [7, 1, 9];
2003
+ const width = (k: u8[]): i32 => toI32(k.length);
2004
+
2005
+ export const main = (): i32 => {
2006
+ const k: Secret<u8[]> = secret(key());
2007
+ const n: i32 = expose(k, width);
2008
+ wipe(k);
2009
+ return n === 3 ? 0 : 1;
2010
+ };
2011
+ ```
2012
+
2013
+ ```ts nish:err NL2440
2014
+ import { Secret, expose, secret } from "nish:secret";
2015
+
2016
+ const key = (): u8[] => [7, 1, 9];
2017
+ const width = (k: u8[]): i32 => toI32(k.length);
2018
+
2019
+ export const main = (): i32 => {
2020
+ const k: Secret<u8[]> = secret(key());
2021
+ return expose(k, width); // NL2440: `k` leaves unwiped
2022
+ };
2023
+ ```
2024
+
2025
+ ```ts nish:err NL2512
2026
+ import { Secret, secret, wipe } from "nish:secret";
2027
+
2028
+ export const main = (): i32 => {
2029
+ const raw: u8[] = [7, 1, 9];
2030
+ const alias = raw; // a second name for the bytes
2031
+ const k: Secret<u8[]> = secret(raw); // NL2512: `raw` has a second reference
2032
+ wipe(k);
2033
+ return toI32(alias[0]);
2034
+ };
2035
+ ```
2036
+
2037
+ **A key kept in an object.** A class keeps a key as long as it lives in a
2038
+ `readonly` field of exactly `Secret<T>` (the class not generic). Set it once,
2039
+ in the constructor, with `this.k = secret(fresh)`; rewrite it only in place
2040
+ through `exposeWith`; read `this.k` only as a call or `new` argument (`expose`,
2041
+ `exposeWith`, a function taking a `Secret`; `wipe` only in the holder's own `wipe()`) or a `===`/`!==` operand —
2042
+ never bind, return or store it. The class declares `wipe(): void`, no
2043
+ parameters, calling `wipe(this.k)` itself for every such field on every path
2044
+ (NL2540, NL2541); a field is wiped nowhere else (NL2547). A function taking a
2045
+ `Secret` borrows it: it neither returns it (NL2546) nor wipes it (NL2548). Nothing checks that `wipe()` is called: call it when the
2046
+ holder is done.
2047
+
2048
+ ```ts nish:ok
2049
+ import { Secret, expose, secret, wipe } from "nish:secret";
2050
+
2051
+ const width = (k: u8[]): i32 => toI32(k.length);
2052
+
2053
+ class Session {
2054
+ readonly key: Secret<u8[]>;
2055
+ constructor() {
2056
+ this.key = secret(new Array<u8>(32));
2057
+ }
2058
+ size(): i32 {
2059
+ return expose(this.key, width);
2060
+ }
2061
+ wipe(): void {
2062
+ wipe(this.key);
2063
+ }
2064
+ }
2065
+
2066
+ export const main = (): i32 => {
2067
+ const s = new Session();
2068
+ const n: i32 = s.size();
2069
+ s.wipe();
2070
+ return n === 32 ? 0 : 1;
2071
+ };
2072
+ ```
2073
+
2074
+ ```ts nish:err NL2544
2075
+ import { Secret, secret, wipe } from "nish:secret";
2076
+
2077
+ class Session {
2078
+ readonly key: Secret<u8[]>;
2079
+ constructor() {
2080
+ this.key = secret(new Array<u8>(32));
2081
+ }
2082
+ wipe(): void {
2083
+ wipe(this.key);
2084
+ }
2085
+ }
2086
+
2087
+ export const main = (): i32 => {
2088
+ const s = new Session();
2089
+ const k = s.key; // NL2544: a second owner of the key
2090
+ s.wipe();
2091
+ return 0;
2092
+ };
2093
+ ```
2094
+
2095
+ **Arrays.** `a.length` (read-only), `a.push(v)`, `a.pop()` (panics when empty —
2096
+ there is no `undefined` to return), `a.indexOf(v)`, `a.join(sep)` — **`join` is
2097
+ `string[]` only** — and, on an array of numbers only, `dst.set(src[, offset])`
2098
+ (one `memmove`; a range past the end panics) and `a.fill(v[, start[, end]])`
2099
+ (ends clamped and counted back from the end when negative, as in JavaScript;
2100
+ never panics). Both are statements. That is every array method there is. A
2101
+ window into a buffer is `(buf, offset, length)`; there is no view type.
2102
+
2103
+ **Strings.** `s.length` (**bytes**), `s.charCodeAt(i)` (the byte, bounds-checked),
2104
+ `s.substring(start[, end])`, `s.slice(start[, end])`, `s.indexOf(sub)`,
2105
+ `s.startsWith(sub)`, `s.endsWith(sub)`, `String.fromCharCode(c)`, and
2106
+ `String.fromBytes(bytes, start, end)` (a `u8[]` range as one string, one
2107
+ allocation; the range panics outside `0 <= start <= end <= bytes.length`; build
2108
+ text from bytes with it, never a `fromCharCode` per byte). That is every string
2109
+ method there is. `substring` **clamps** an out-of-range offset the
2110
+ way JavaScript does; `slice` instead **panics** unless
2111
+ `0 <= start <= end <= s.length`, and is the faster of the two when you have
2112
+ already established the range. Every offset is a **byte** offset, so cutting a multi-byte character
2113
+ in half is possible. To count characters rather than bytes, `codePointLength(s)`
2114
+ from `nish/text` counts code points natively and, for text written in the
2115
+ source, under Node alike; under Node a string built from bytes (`String.fromBytes`,
2116
+ a `\u` escape `nish/json` decodes) is one unit per byte, and counts its bytes.
2117
+
2118
+ **Process.** `process.exit(code)`, `process.argv` (a read-only `string[]`;
2119
+ `argv[0]` is the program path, one index earlier than Node),
2120
+ `process.platform`, `process.arch`.
2121
+
2122
+ **Files and the system.** `readFileSync(path)` (exits 1 on failure),
2123
+ `readFileSyncOrNull(path)` (`string | null`), `readFileBytesSync(path)`
2124
+ (`u8[] | null`, the bytes as they are on disk), `writeFileSync(path, data)`,
2125
+ `appendFileSync(path, data)`, `mkdirSync(path)` (one level, `boolean`),
2126
+ `isDirectorySync(path)`, `readdirSync(path)` (`string[] | null`, sorted by
2127
+ bytes, no `.`/`..`), `spawnSync(argv)`, `spawnSyncTo(argv, outPath, errPath)`,
2128
+ `getenv(name)` (`string | null` — unset and empty are different answers),
2129
+ `realpathSync(path)` (`string | null`; symbolic links resolved, absolute, and
2130
+ `null` when it does not resolve),
2131
+ `monotonicNanos()` (`i64`; elapsed time only), and, for checking a path before
2132
+ trusting it, `lstatOwnerModeSync(path)` (an `i64`: the owner's uid in the high
2133
+ 32 bits, `st_mode` in the low 32, `-1` when it does not resolve; a symbolic
2134
+ link answers for itself), `geteuid()` (`i64`) and `isExecutableSync(path)`
2135
+ (`boolean`).
2136
+
2137
+ **The host.** `Date.now()` (an `f64` of whole milliseconds, the wall clock —
2138
+ **the only `Date` there is**: `new Date()`, `Date.parse` and the rest are
2139
+ refused), `crypto.getRandomValues(bytes)` (a statement over a `u8[]` only, at
2140
+ most 65,536 bytes a call, from the kernel's CSPRNG; more panics),
2141
+ `statMtimeSync(path)` (an `f64` of milliseconds, **NaN** — not `null` — when the
2142
+ path cannot be stat'd, so test `m !== m`), and `signalFd()` / `readSignal(fd)`:
2143
+ call `signalFd()` once at the top of `main`, then `readSignal(fd)` blocks until
2144
+ SIGTERM or SIGINT and answers 15 or 2. None of them exists on a wasm target.
2145
+
2146
+ **Children and the machine.** `spawn(argv)` starts a child like `spawnSync` but
2147
+ answers its pid (`i32`) at once, without waiting; `kill(pid, sig)` sends 0, 2,
2148
+ 9 or 15 and answers 0 (any other signal is `-22`); `waitPid(pid, flags)` answers
2149
+ `[pid, status]` as an `i32[]` — pid `-1` is any child, flag `1` does not block,
2150
+ `[0, 0]` means none has exited yet, and the status is the exit code or 128 plus
2151
+ the signal. Each answers a **negative errno** on failure (`[-10, 0]`: no child
2152
+ left), so test `pid > 0` before passing a pid on: `-1` (EPERM) handed to
2153
+ `waitPid` means any child. After `signalFd()` a blocking `waitPid` restarts
2154
+ through SIGTERM, so a supervisor reaps with flag `1` on a `pollWait` tick that
2155
+ also watches the signal fd. None of `spawn`, `kill`, `waitPid` may be called in
2156
+ a task or a parallel body; the two host facts may. `availableParallelism()` (`i32`, at least 1, no `--threads` needed) and
2157
+ `totalMemory()` (`i64` bytes, or `-1`) size a program to the machine. None of
2158
+ the five exists on a wasm target, and `spawn` and `waitPid` throw under Node.
2159
+
2160
+ **Sockets** (`nish:net`, or the same names as globals). `tcpListen(host, port,
2161
+ backlog)`, `tcpAccept(fd, peer)`, `netRead(fd, buf, off, len)`,
2162
+ `netWrite(fd, buf, off, len)`, `netShutdown(fd, how)`, `netClose(fd)`,
2163
+ `netLocalPort(fd)` and `netAddress(out, host, port)`. Every one answers an
2164
+ `i32`: **a negative number is a failure, not an exception** — `-11` means "would
2165
+ block, call again", `-32` a peer that has gone (never SIGPIPE), `-98` the port
2166
+ is in use, `-111` refused, `-22` a bad argument. Every socket is non-blocking,
2167
+ and there is no `async` and no callback: your `main` is the loop. A client calls
2168
+ `tcpConnect(addr)`, which answers the descriptor while it is still connecting;
2169
+ wait for it to be writable, then `connectResult(fd)` is 0 or the failure. Buffers
2170
+ are `u8[]` only; an address is 18 bytes of one (IPv4 as `::ffff:a.b.c.d`, then
2171
+ the port big-endian); hosts are numeric literals, because there is no DNS.
2172
+ `netRead` / `netWrite` panic on a range outside the buffer.
2173
+ `tcpListenFlags(host, port, backlog, flags)` is `tcpListen` with flag 1
2174
+ `SO_REUSEPORT`, so several listeners, in one process or many, share a port (Linux
2175
+ spreads the connections), and flag 2 `TCP_NODELAY` on every socket `tcpAccept`
2176
+ takes from it; flags add, and any other bit is `-22`. UDP is
2177
+ `udpBind(host, port, flags)` (flag 1 `SO_REUSEPORT`, flag 2 GRO),
2178
+ `udpSendTo(fd, buf, off, len, to, segment, ecn)` and
2179
+ `udpRecvFrom(fd, buf, off, len, from, meta)`: a `segment` above 0 sends one
2180
+ buffer as many datagrams of that size, `meta` is an `i32[]` of at least 2 that
2181
+ receives the GRO segment size (0 when nothing was coalesced) and the ECN bits,
2182
+ and an empty datagram answers 0. GSO, GRO and ECN are Linux's: on Darwin they
2183
+ answer `-95`. To wait for whichever descriptor is ready, `pollCreate()` makes a
2184
+ loop, `pollAdd(loop, fd, events, token)` / `pollModify(...)` watch `fd` for
2185
+ `events` (1 readable, 2 writable) under a `token` you choose, and
2186
+ `pollRemove(loop, fd)` stops. `pollWait(loop, ready, timeoutMs)` waits (forever
2187
+ for `-1`), fills `ready: i32[]` with pairs — `ready[2k]` a token,
2188
+ `ready[2k + 1]` its events, 4 added for a hang-up or error — and answers how
2189
+ many, **0 on a timeout or a signal**. It is level-triggered, so what you leave
2190
+ unread is reported again. Add `signalFd()` to the loop to wake on SIGTERM. None
2191
+ exists on a wasm target, and under Node each throws.
2192
+
2193
+ **Arena.** `Arena.mark()`, `Arena.used()` and `using a = arena()` — see below.
2194
+ `Arena.release(m)` and `Arena.reset()` are deprecated (NL7001).
2195
+
2196
+ ### Map and Set
2197
+
2198
+ `Map<K, V>` and `Set<T>` are global, as in JavaScript: no import, insertion
2199
+ order, SameValueZero keys (`-0` is `+0`, `NaN` finds `NaN`). What they have is
2200
+ exactly `size` (a read-only `number`), `get(k)`, `set(k, v)` / `add(x)` (both
2201
+ answer the receiver, so they chain), `has(k)`, `delete(k)` (answers whether it
2202
+ was there) and `clear()`, and `keys()` / `values()` as the iterable of a
2203
+ `for...of` and nowhere else. `entries` and `forEach` are refused by name.
2204
+
2205
+ ```ts nish:ok-body
2206
+ const seen = new Set<string>();
2207
+ const counts: Map<string, i32> = new Map();
2208
+ seen.add("a").add("b").add("a");
2209
+ counts.set("a", 1).set("b", 2);
2210
+ counts.delete("b");
2211
+ console.log(`${seen.size} ${counts.size} ${seen.has("b")}`);
2212
+ ```
2213
+
2214
+ - Write the type arguments on `new`, or annotate the declaration and write
2215
+ `new Map()`. `new Map(entries)` and `new Set(array)` are refused: start empty
2216
+ and `set` / `add` in a loop.
2217
+ - A key is a string, any number type, a `boolean`, an enum or a class instance
2218
+ (by identity). An interface, an array, a nullable type or a `Result` is not a
2219
+ key; a value is anything but `void` or an interface.
2220
+ - A module that declares its own `Map` or `Set` keeps it, but then no other
2221
+ module of the program may name the global one.
2222
+
2223
+ `m.get(k)` is `V | undefined`, as under `tsc`, and it is read in exactly one of
2224
+ three ways: a default with `??`, a `const` tested with `!== undefined` (or
2225
+ `=== undefined` and an early `return`), or a bare test. Each is one probe.
2226
+
2227
+ ```ts nish:ok-body
2228
+ const counts = new Map<string, i32>();
2229
+ for (const w of ["a", "b", "a"]) {
2230
+ counts.set(w, (counts.get(w) ?? 0) + 1);
2231
+ }
2232
+ const a = counts.get("a");
2233
+ if (a !== undefined) {
2234
+ console.log(`${a} ${counts.get("z") === undefined}`); // 2 true
2235
+ }
2236
+ ```
2237
+
2238
+ - Anywhere else — a `let`, an argument, a `return`, a field or element, a
2239
+ template hole, an arithmetic operand, the default of another `??`, a `const`
2240
+ annotated `i32` — the maybe is refused with a message naming the place.
2241
+ Default it with `??` first: `m.get(a) ?? (m.get(b) ?? 0)`.
2242
+ - The only spelling of the type is `const a: i32 | undefined = m.get(k)`.
2243
+ - `??` does not mix with `||` or `&&` without parentheses, and its default must
2244
+ be the value type. For a `Map<K, Node | null>` it replaces a stored `null`
2245
+ too, but the result is still `Node | null`.
2246
+
2247
+ ```ts nish:err-body NL2361
2248
+ const m = new Map<string, i32>();
2249
+ let v = m.get("a");
2250
+ ```
2251
+
2252
+ Walk a map with `for (const k of m.keys())` or `for (const v of m.values())`,
2253
+ and a set with `for (const x of s)`: insertion order, deleted keys skipped. You
2254
+ may `set`, `delete` or `clear` the table inside the walk and get exactly what
2255
+ JavaScript gives (a new key is visited later in the same walk; a key deleted
2256
+ before it is reached is not).
2257
+
2258
+ ```ts nish:ok-body
2259
+ const ages = new Map<string, i32>();
2260
+ ages.set("ada", 36).set("alan", 41);
2261
+ ages.delete("alan");
2262
+ ages.set("grace", 85);
2263
+ let total = 0;
2264
+ for (const name of ages.keys()) {
2265
+ console.log(name); // ada, then grace
2266
+ }
2267
+ for (const age of ages.values()) {
2268
+ total += age;
2269
+ }
2270
+ console.log(`${total}`); // 121
2271
+ ```
2272
+
2273
+ - An iterator is not a value: `keys()` / `values()` stored, passed, returned or
2274
+ spread are refused. Pass the map itself and walk it where it is used.
2275
+ - `for (const e of m)` over a `Map` itself is refused, because it would need
2276
+ `[key, value]` destructuring: walk `m.keys()` and call `m.get(k)`.
2277
+
2278
+ ```ts nish:err-body NL2375
2279
+ const m = new Map<string, i32>();
2280
+ for (const e of m) {
2281
+ }
2282
+ ```
2283
+
2284
+ Asking about one key twice costs one probe when the second call writes
2285
+ through the first: `m.set(k, E)` whose `E` reads `m.get(k)` or `m.has(k)`
2286
+ once, and `if (m.has(k)) { m.set(k, E); … }`, `if (!m.has(k)) { m.set(k, E); … }`
2287
+ or `if (!s.has(x)) { s.add(x); … }` with the write first in the branch. The
2288
+ receiver and key must be a local, a parameter or a `this.<field>` path (the key
2289
+ may be a literal), spelled the same in both calls, and nothing in between may
2290
+ call anything, allocate (`new`, a literal array or object, a template, a
2291
+ string `+`) or assign. Anything else still compiles, as one probe per call.
2292
+ `nish/map` adds `getOrInsert(m, k, v)` — the value of `k`, or `v` after
2293
+ inserting it — and `reserve(m, n)`, which sizes the table for `n` entries; both
2294
+ are one module with the program and run unchanged under Node.
2295
+
2296
+ ```ts nish:ok
2297
+ import { getOrInsert, reserve } from "nish/map";
2298
+
2299
+ export const main = (): i32 => {
2300
+ const words = ["to", "be", "or", "not", "to", "be"];
2301
+ const counts = new Map<string, i32>();
2302
+ reserve(counts, words.length);
2303
+ for (const w of words) {
2304
+ counts.set(w, (counts.get(w) ?? 0) + 1); // one probe per word
2305
+ }
2306
+ const first = new Map<string, i32>();
2307
+ for (let i = 0; i < words.length; i++) {
2308
+ getOrInsert(first, words[i], i); // one probe; inserts only the first time
2309
+ }
2310
+ console.log(`${counts.get("to") ?? 0} ${first.get("be") ?? -1}`); // 2 1
2311
+ return 0;
2312
+ };
2313
+ ```
2314
+
2315
+ ### Typed JSON: `nish/json-typed`
2316
+
2317
+ `jsonDecode(d, out)` reads one JSON object into a class and `jsonEncode(w,
2318
+ value)` writes one; the compiler writes the code at each call from the class's
2319
+ fields. A field is `i32`, `u32`, `i64`, `u64`, `f64`, `boolean`, `string`, a
2320
+ numeric enum, a class, an array of one of those, a `Map<string, V>` of any of
2321
+ those, or a `string`, class, array or map `| null`; anything else is refused at
2322
+ the call (NL2485), a `Map` with non-`string` keys and a `Set` included, and so
2323
+ is a value that is not a class (NL2486). Decoding is strict: every field
2324
+ required, each key once, an unknown key an error unless `d.ignoreUnknown`, and
2325
+ every outcome a sticky negative code in `d.failed`, never a panic. An enum is
2326
+ its number, and a number that is no member's value is `JSON_DECODE_TYPE`; a
2327
+ `u64` is exact to 2^64 - 1, and negative is `JSON_DECODE_RANGE`; a map is
2328
+ emptied and filled in the input's key order, a repeated key an error, and
2329
+ more keys over all the maps of one decode than `d.maxMapKeys` (4,096 unless
2330
+ set) `JSON_DECODE_MAP_LIMIT`. A class
2331
+ the decoder makes (an array's element, a `| null` field's object, a map's
2332
+ value) has a constructor with no parameters (NL2488). A class's options are
2333
+ one `static readonly json = { rename: { field: "key" }, optional: ["field"] }`
2334
+ literal, the only `static` a class may have: `rename` gives a field its key on
2335
+ the wire, and a missing `optional` key sets its field to the initializer, so
2336
+ an optional field has one (NL2498). Each misuse is its own code where the class
2337
+ is declared: not `static readonly` or on a generic class (NL2492), not a
2338
+ literal (NL2493), an unknown option (NL2494), a name that is no field (NL2495),
2339
+ a rename to a non-string (NL2496) or to a key that is not valid UTF-8
2340
+ (NL2499), two fields with one key (NL2497). A
2341
+ `JsonNumber` field keeps a number's text byte for byte (`text`, `i64`, `u64`,
2342
+ `f64`, `setText`).
2343
+
2344
+ ```ts nish:ok
2345
+ import { JsonWriter } from "nish/json";
2346
+ import { JsonLimits } from "nish/json-tokenizer";
2347
+ import { JsonDecoder, jsonDecode, jsonEncode } from "nish/json-typed";
2348
+
2349
+ class Point {
2350
+ x: i32 = 0;
2351
+ y: i32 = 0;
2352
+ }
2353
+
2354
+ export const main = (): i32 => {
2355
+ const text = `{"x":3,"y":4}`;
2356
+ const body = new Array<u8>(toI32(text.length));
2357
+ for (let i: i32 = 0; i < toI32(body.length) && i < toI32(text.length); i += 1) {
2358
+ body[i] = toU8(text.charCodeAt(i));
2359
+ }
2360
+ const p = new Point();
2361
+ const code = jsonDecode(new JsonDecoder(body, 0, toI32(body.length), new JsonLimits()), p);
2362
+ const w = new JsonWriter(new Array<u8>(64), 0);
2363
+ jsonEncode(w, p); // {"x":3,"y":4}
2364
+ return code; // 0, or a JSON_DECODE_* / JSON_TOK_ERR_* code
2365
+ };
2366
+ ```
2367
+
2368
+ ```ts nish:err NL2485
2369
+ import { JsonWriter } from "nish/json";
2370
+ import { jsonEncode } from "nish/json-typed";
2371
+
2372
+ class Reading {
2373
+ value: f32 = 0; // no JSON reading: use f64
2374
+ }
2375
+
2376
+ export const main = (): i32 => {
2377
+ const w = new JsonWriter(new Array<u8>(16), 0);
2378
+ jsonEncode(w, new Reading());
2379
+ return w.failed;
2380
+ };
2381
+ ```
2382
+
2383
+ ```ts nish:ok
2384
+ import { JsonWriter } from "nish/json";
2385
+ import { JsonDecoder, JsonNumber, jsonDecode, jsonEncode } from "nish/json-typed";
2386
+
2387
+ class Order {
2388
+ static readonly json = { rename: { orderId: "order_id" }, optional: ["note"] };
2389
+ orderId: i32 = 0;
2390
+ note: string = ""; // missing key: back to ""
2391
+ total: JsonNumber; // any number, kept as written
2392
+ constructor() {
2393
+ this.total = new JsonNumber();
2394
+ }
2395
+ }
2396
+
2397
+ export const readOrder = (d: JsonDecoder, o: Order): i32 => jsonDecode(d, o);
2398
+
2399
+ export const writeOrder = (w: JsonWriter, o: Order): void => {
2400
+ jsonEncode(w, o); // {"order_id":..,"note":..,"total":..}
2401
+ };
2402
+ ```
2403
+
2404
+ ```ts nish:err NL2495
2405
+ import { JsonWriter } from "nish/json";
2406
+ import { jsonEncode } from "nish/json-typed";
2407
+
2408
+ class Order {
2409
+ static readonly json = { optional: ["notes"] }; // no field `notes`
2410
+ note: string = "";
2411
+ }
2412
+
2413
+ export const main = (): i32 => {
2414
+ const w = new JsonWriter(new Array<u8>(16), 0);
2415
+ jsonEncode(w, new Order());
2416
+ return w.failed;
2417
+ };
2418
+ ```
2419
+
2420
+ ```ts nish:err NL2485
2421
+ import { JsonWriter } from "nish/json";
2422
+ import { jsonEncode } from "nish/json-typed";
2423
+
2424
+ class Index {
2425
+ byId: Map<i32, string>; // a JSON object's keys are strings
2426
+ constructor() {
2427
+ this.byId = new Map<i32, string>();
2428
+ }
2429
+ }
2430
+
2431
+ export const main = (): i32 => {
2432
+ const w = new JsonWriter(new Array<u8>(16), 0);
2433
+ jsonEncode(w, new Index());
2434
+ return w.failed;
2435
+ };
2436
+ ```
2437
+
2438
+ ## Memory
2439
+
2440
+ There is **no garbage collector and no `free`**, and nothing about this changes
2441
+ what a program computes — only where memory lives. You do not have to manage
2442
+ it, and mostly you should not try:
2443
+
2444
+ 1. **Stack allocation** for a `new`, object literal, array literal or
2445
+ `new Array<T>(literal)` whose value provably does not outlive its function.
2446
+ 2. **Automatic arena scopes** for a function whose temporaries all die with it:
2447
+ a mark on entry, a release before every `ret`.
2448
+ 3. **A reclaim at the call site** for a function that returns a string.
2449
+ 4. **Explicit control** with `using a = arena()`, for code that manages
2450
+ batches itself.
2451
+
2452
+ **To free a batch, wrap it in a block with `using a = arena()`**: everything
2453
+ the block allocates after that line is released when the block ends, on every
2454
+ exit, and the compiler refuses the block if anything allocated in it could
2455
+ outlive it — so allocate what must outlive the block before it, and let only
2456
+ numbers, booleans and enums out. `arena()` is only ever a `using` initialiser,
2457
+ and `a` is never read.
2458
+
2459
+ ```ts nish:ok-body
2460
+ const rows: string[] = ["a", "bb"];
2461
+ let total = 0;
2462
+ for (const row of rows) {
2463
+ using a = arena();
2464
+ const line = `${row}: ${row.length}`;
2465
+ total = total + line.length;
2466
+ }
2467
+ ```
2468
+
2469
+ **Do not hand an outer local a new allocation on every pass.**
2470
+ `let last = ""` above a loop whose body runs ``last = `item ${i}` `` keeps
2471
+ every pass's string, because nothing frees a value a later pass replaced:
2472
+ 10,000,000 passes peaked at 392 MB. The compiler warns (NL9016). Declare the
2473
+ value with `const` inside the loop when only the pass reads it, or keep
2474
+ numbers across passes and build the value once after the loop. A server, or
2475
+ anything that runs for a long time, should compile with `--deny-retention`,
2476
+ which makes this warning and the three like it errors.
2477
+
2478
+ Everything else is bumped from the arena, which is released when `main`
2479
+ returns. **Do not write `Arena.release` or `Arena.reset`**: releasing while
2480
+ anything allocated after the mark is still referenced is undefined behaviour
2481
+ the compiler does not check, so both are deprecated, and each call compiles
2482
+ with a `deprecation` warning (NL7001) pointing to `using a = arena()`.
2483
+
2484
+ ## Recipes for what is missing
2485
+
2486
+ **Iterate and transform** — there is no `map`, and no function to pass it:
2487
+
2488
+ ```ts nish:ok-body
2489
+ const xs: i32[] = [1, 2, 3];
2490
+ const doubled: i32[] = [];
2491
+ for (const x of xs) {
2492
+ doubled.push(x * 2);
2493
+ }
2494
+ console.log(`${doubled.length}`);
2495
+ ```
2496
+
2497
+ **Convert a number to a string** — there is no `toString` and no `String()`:
2498
+
2499
+ ```ts nish:ok-body
2500
+ const n = 42;
2501
+ const s = `${n}`;
2502
+ console.log(s);
2503
+ ```
2504
+
2505
+ **Return two values** — there are no tuples: declare an interface.
2506
+
2507
+ ```ts nish:ok
2508
+ interface Split { head: string; rest: string; }
2509
+
2510
+ const cut = (s: string, at: i32): Split => ({
2511
+ head: s.substring(0, at),
2512
+ rest: s.substring(at),
2513
+ });
2514
+
2515
+ export const main = (): i32 => {
2516
+ console.log(cut("hello", 2).rest);
2517
+ return 0;
2518
+ };
2519
+ ```
2520
+
2521
+ When the two values need no names of their own, `import { Pair } from "nish/pair"`
2522
+ and return `Pair<A, B>`: an object literal `{ first, second }` at the return,
2523
+ exactly as above. It is for returning two values, not storing them — a struct
2524
+ kept in an array or a field wants named fields.
2525
+
2526
+ **A map or a set** — the global `Map` and `Set` ([Map and Set](#map-and-set)).
2527
+ Read a value with `m.get(k) ?? d`, and keep the keys in an array beside it when
2528
+ you need to walk them, since there is no iteration yet.
2529
+
2530
+ **Find the next byte of a few** — `indexOfAny(text, bytes, from)` from
2531
+ `nish/text` answers the first index at or after `from` whose byte is one of
2532
+ `bytes`, or -1: the scan for the next `"` or `\\` in a JSON string, the next
2533
+ delimiter in a line. `bytes` holds 1 to 16 ASCII bytes and anything else
2534
+ panics; `from` is clamped to `[0, text.length]`. Natively it reads 16 or 32
2535
+ bytes at a time, so prefer it to a `charCodeAt` loop over a long text — and
2536
+ never call it on secret data, because its time says where the match is.
2537
+ (No block: a program importing `nish/text` writes two `.ll` files;
2538
+ `tests/link/std_text_index_of_any` is its program.)
2539
+
2540
+ **Match a pattern** — there is no `RegExp` and no `/re/` literal:
2541
+ `regexCompile(pattern, flags)` from `nish/regex` answers a
2542
+ `Result<Regex, string>`, the error text naming the byte. The syntax is
2543
+ JavaScript's with the `u` flag's strictness (a stray `{` or `]` is an
2544
+ error), the flags are `i`, `m` and `s`, and backreferences and lookaround are
2545
+ refused, because matching never backtracks: time is linear in the input for
2546
+ every pattern. `test`, `exec(text, from, m)` into a reused `RegexMatch`,
2547
+ `find`, `findAll`, `count`, `replace`, `replaceAll` (`$&`, `$1`,
2548
+ `$<name>`) and `split`; offsets are UTF-8 bytes and `\w`, `\d`, `\b` and `i`
2549
+ are ASCII. A `Regex` keeps its scratch memory, so compile once outside the
2550
+ loop and use it from one thread. (No block, as for `nish/text`;
2551
+ `tests/link/std_regex` is its program, and the user guide's [standard-library
2552
+ page](guide/using-nish/standard-library.md#nishregex) its walk-through.)
2553
+
2554
+ **Decode a URL or read a query** — there is no `decodeURIComponent` and no
2555
+ `URLSearchParams`: `nish/net/url` has both readings. `urlDecode(s, flags)` and
2556
+ `urlQueryGet(target, name)` answer a `string | null`; the byte API
2557
+ (`percentDecode`, `UrlPathSegments`, `UrlQuery`, `urlQueryFind`) reads a
2558
+ window of a `u8[]` and allocates nothing. Decoding is strict by default — a
2559
+ malformed escape or `%00` is refused, `+` is kept — and `URL_DECODE_FORM`,
2560
+ `URL_DECODE_LENIENT` and `URL_DECODE_ALLOW_NUL` add `URLSearchParams`'s
2561
+ repairs, short of its U+FFFD for invalid UTF-8. `percentDecode` answers the
2562
+ length or a negative `URL_DECODE_ERR_*` code — test it before reading `out`,
2563
+ since a peer's `%zz` makes it negative and `urlQueryStart` answers -1 for no
2564
+ `?` — and `percentDecodeErrorAt` the offset an error is at; a query yields at
2565
+ most 100 pairs. (No block, as for `nish/text`; `tests/link/std_url` is its
2566
+ program, and the user guide's [standard-library
2567
+ page](guide/using-nish/standard-library.md#nishneturl) its walk-through.)
2568
+
2569
+ **Draw to the whole terminal** (a game, a dashboard) — there is no curses and
2570
+ no `process.stdout.on("resize")`: `runTerminal(options, state, update, render)`
2571
+ from `nish/term-io` opens the terminal, probes what it can draw, steps
2572
+ `update(state, input)` at `options.stepsPerSecond` and calls
2573
+ `render(state, frame)` only when the link to the terminal has room, then
2574
+ leaves the terminal as it was, even on a panic. Draw cells on `frame.screen`
2575
+ or pixels on `frame.framebuffer` (sized by `options.framebufferWidth` and
2576
+ `framebufferHeight`) and never pick a backend: kitty graphics, Sixel, half
2577
+ blocks or ASCII is chosen from the probe and the measured link. It answers
2578
+ `Err` with one sentence on a pipe, so test the `Result`; it reaches `tty`.
2579
+ `NISH_TERM_GRAPHICS` and `NISH_TERM_FPS` override what it picked. (No block,
2580
+ as for `nish/text`; `examples/term-probe.ts` is its program, and the user
2581
+ guide's [terminal page](guide/using-nish/terminal.md) its walk-through.)
2582
+
2583
+ **Shared behaviour over several shapes** — there is no inheritance and no
2584
+ dispatch. Give each class the interface's fields **first**, `implements` it,
2585
+ and write a free function over the interface:
2586
+
2587
+ ```ts nish:ok
2588
+ interface Sized { width: i32; height: i32; }
2589
+
2590
+ class Photo implements Sized {
2591
+ width: i32;
2592
+ height: i32;
2593
+ path: string = "";
2594
+ constructor(width: i32, height: i32) {
2595
+ this.width = width;
2596
+ this.height = height;
2597
+ }
2598
+ }
2599
+
2600
+ const area = (s: Sized): i32 => s.width * s.height;
2601
+
2602
+ export const main = (): i32 => {
2603
+ console.log(`${area(new Photo(3, 4))}`);
2604
+ return 0;
2605
+ };
2606
+ ```
2607
+
2608
+ **Optional parameters** — there are none. Write two functions with different
2609
+ names, or take the value and document the sentinel.
2610
+
2611
+ **A calendar, regex, JSON, threads of your own** — not in the language (the
2612
+ wall clock is `Date.now()`, and that is all of `Date`). Say so rather than
2613
+ emitting code that cannot compile.
2614
+
2615
+ ### Proving it cannot panic
2616
+
2617
+ Under `--deny-panics`, or for a module the root `package.json` lists in
2618
+ `"nish": { "noPanic": [...] }`, every place the program could stop with a panic
2619
+ is an error (NL2457) until a proof removes it; out of memory is the one kind
2620
+ allowed. Each error names its fix, and there are five:
2621
+
2622
+ - **Guard** what the checker cannot prove: `if (i >= 0 && i < xs.length)` before
2623
+ `xs[i]` (`if (toU32(i) < toU32(xs.length))` for a `u8`, `u16` or `u32` index,
2624
+ whose type is the lower end; `toI32(xs.length)` for the bound under
2625
+ `--number-mode f64`; an `i64`, `u64` or float index — `number` under
2626
+ `--number-mode f64` — has no guard, so index with an `i32` or a `u32`),
2627
+ `if (d !== 0 && d !== -1)` before `a / d` (a
2628
+ constant divisor needs nothing), `if (xs.length > 0)` before `xs.pop()`,
2629
+ `if (n >= 0 && n <= 9)` before `n` enters `integer<0, 9>`.
2630
+ - **Bound a signed `+ - *`, or say it wraps**: an `i32`/`i64` operation the
2631
+ checker cannot prove fits is an `overflow` site; bound its operands (a loop
2632
+ below a length, a guard on the value), compute in `u32`/`u64`, or write
2633
+ `wrappingAdd`, `wrappingSub` or `wrappingMul` from `nish:unsafe`.
2634
+ - **Swap `expect` for `orReturn` or `unwrapOr`**, which hand the error on or
2635
+ supply a value instead of stopping.
2636
+ - **Swap `readFileSync` for `readFileSyncOrNull`**, which answers `null` where
2637
+ the other exits.
2638
+ - **Read the list**: `nish file.ts --emit-panics panics.json` writes every site
2639
+ per function, proven or not, and the `"panics"` of `--emit-capabilities` the
2640
+ same.
2641
+
2642
+ ```ts nish:ok --deny-panics
2643
+ const at = (xs: i32[], i: i32): i32 => {
2644
+ if (i >= 0 && i < xs.length) {
2645
+ return xs[i];
2646
+ }
2647
+ return 0;
2648
+ };
2649
+
2650
+ const byte = (xs: i32[], i: u8): i32 => {
2651
+ if (toU32(i) < toU32(xs.length)) {
2652
+ return xs[i];
2653
+ }
2654
+ return 0;
2655
+ };
2656
+
2657
+ const ratio = (a: i32, d: i32): i32 => {
2658
+ if (d !== 0 && d !== -1) {
2659
+ return a / d;
2660
+ }
2661
+ return a / 2;
2662
+ };
2663
+
2664
+ const size = (path: string): i32 => {
2665
+ const text = readFileSyncOrNull(path);
2666
+ return text === null ? -1 : text.length;
2667
+ };
2668
+
2669
+ export const main = (): i32 => {
2670
+ console.log(`${at([4, 5], 1)} ${byte([4, 5], 0)} ${ratio(9, 3)} ${size("missing.txt")}`);
2671
+ return 0;
2672
+ };
2673
+ ```
2674
+
2675
+ The same function without its guard is refused, and the error names the guard:
2676
+
2677
+ ```ts nish:err NL2457 --deny-panics
2678
+ export const at = (xs: i32[], i: i32): i32 => xs[i];
2679
+ ```
2680
+
2681
+ A call into the standard library or a dependency that may panic is refused at
2682
+ the call (NL2458), because only its own author can prove it: call a function
2683
+ that cannot panic, or make the call from a module outside the scope.
2684
+ `--unchecked-indexing` is not a proof, and an index it leaves unchecked is
2685
+ still refused. `uncheckedGet` and `uncheckedSet` are not proofs either, but
2686
+ they cannot panic, and their `nish:unsafe` import is the module's visible
2687
+ opt-in to an index out of range being undefined behaviour, so the scope allows
2688
+ them (as `"allowed": true` sites) and a call into them is never refused:
2689
+
2690
+ ```ts nish:ok --deny-panics
2691
+ import { uncheckedGet } from "nish:unsafe";
2692
+
2693
+ export const first = (xs: i32[]): i32 => uncheckedGet(xs, 0);
2694
+ ```
2695
+
2696
+ Without the import the call is refused (NL2456), scope or not:
2697
+
2698
+ ```ts nish:err NL2456 --deny-panics
2699
+ export const first = (xs: i32[]): i32 => uncheckedGet(xs, 0);
2700
+ ```
2701
+
2702
+ ### Holding it to its capabilities
2703
+
2704
+ A package that must not touch the network says so in `package.json`,
2705
+ `"nish": { "capabilities": { "deny": ["net"] } }`, and `--deny net` or an
2706
+ allowlist such as `--allow fs.read,exit` says the same on the command line.
2707
+ The `"nish"` field holds `noPanic` and `capabilities` and nothing else, each
2708
+ written once; anything more is refused (NL3036) rather than skipped. A
2709
+ program whose `main` — or, in a library or a header build, any export —
2710
+ reaches more is refused (NL2459), spanned at the first call and naming the
2711
+ whole chain to the builtin; nothing changes in a program
2712
+ that keeps to it ([LANGUAGE.md](LANGUAGE.md#the-capability-policy)). Under an
2713
+ allowlist `exit` is a capability like the others, and `unsafe` can be denied
2714
+ but never allowed: importing `nish:unsafe` is the opt-in.
2715
+
2716
+ ```ts nish:err NL2459 --deny fs.read
2717
+ const settings = (): string => readFileSync("settings.txt");
2718
+
2719
+ export const main = (): i32 => settings().length;
2720
+ ```
2721
+
2722
+ ## Before you say it compiles
2723
+
2724
+ Run it. `nish file.ts --json` is one command and it is the only proof.
2725
+
2726
+ 1. Did you **run the compiler**? Nothing else counts.
2727
+ 2. Are all conditions `boolean` — no `if (x)` on a number, string or array?
2728
+ 3. Did you invent a method? Check it against
2729
+ [the builtins list](#the-builtins-in-full).
2730
+ 4. Is every parameter, return type, field and module constant annotated?
2731
+ 5. Does every numeric type match exactly — no `i32` meeting `i64`, no literal
2732
+ in an object-literal property or a method argument expecting a width?
2733
+ 6. Is every `Result` read, and is every `.value` behind an `isOk()`?
2734
+ 7. Is every nullable narrowed with `!== null` before it is touched?
2735
+ 8. Did you use `throw`, `try`, `any`, `undefined` or `??` (outside a `T | null`, a chain or `Map.get`), `?.` without a `??`, a cast, a
2736
+ stored or returned callback, a generic *alias*, or `extends`?
2737
+ 9. Does it reach only what you meant it to? `nish file.ts --emit-capabilities
2738
+ caps.json` lists every capability each function can reach — files, processes,
2739
+ the network, the environment, the clock, entropy, signals, `exit`, C calls —
2740
+ with the call chain that reaches it, and `nish run --capabilities file.ts`
2741
+ prints the one-line summary ([LANGUAGE.md](LANGUAGE.md#capabilities)).
2742
+ A package that must not touch the network says so in `package.json`
2743
+ ([Holding it to its capabilities](#holding-it-to-its-capabilities)).
2744
+ 10. Must it never panic? Compile with `--deny-panics` (or list the module in
2745
+ `package.json`'s `"nish": { "noPanic": [...] }`): every index, division,
2746
+ signed `+ - *`, `pop`, range entry, `expect` and exiting call it cannot
2747
+ prove is an error
2748
+ naming the guard ([Proving it cannot panic](#proving-it-cannot-panic)); an
2749
+ `uncheckedGet` or `uncheckedSet` behind its `nish:unsafe` import is allowed.
2750
+ 11. If you are adding to this repository: `npm run check` and the touched
2751
+ `node tests/run.js` sections green locally, every CI `test` shard green
2752
+ ([CLAUDE.md](../CLAUDE.md#definition-of-done)), and a new construct ships a
2753
+ golden `.ll`, an `llvm-as` pass, a native round trip, a negative test, its
2754
+ `LANGUAGE.md` rule and cookbook entry, and a conventional commit message,
2755
+ which is its changelog entry.
2756
+
2757
+ Every `ts nish:ok` and `ts nish:err` block on this page is compiled by
2758
+ `npm test`, so an example that has gone stale is a failing test rather than a
2759
+ program that no longer works.