@amritk/nish-x86_64-linux 0.20.0 → 0.22.1

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