@amritk/nish 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/AI.md ADDED
@@ -0,0 +1,1076 @@
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 program.ts -o out.ll # emit LLVM IR
39
+ nish program.ts --link prog # build a native binary (needs clang)
40
+ nish --help # the full flag list, stdout, exit 0
41
+ ```
42
+
43
+ `--json` is the surface to automate against. One flat object per line on
44
+ stdout, nothing on stderr, every field 1-based with `endLine`/`endColumn`
45
+ exclusive:
46
+
47
+ ```json
48
+ {"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)"}
49
+ ```
50
+
51
+ - **Key on `code`, never on `message`.** A code is a promise: `NL2249` means
52
+ the same rule next release. The prose may improve; the code may not.
53
+ - **`severity`** is `"error"` or `"performance"`. A performance warning never
54
+ changes the exit code — it is advice, not a rejection.
55
+ - **Bands**: `NL1xxx` the Phase 0 forbidden-syntax sweep, `NL2xxx` the checker,
56
+ `NL3xxx` the driver and modules, `NL4xxx` the interop sidecars, `NL9xxx`
57
+ performance, `NL0001` syntax, `NL0002` toolchain, `NL0003` internal,
58
+ `NL0000` a diagnostic with no rule yet.
59
+ - **Exit codes**: `0` ok, `1` the program was rejected, `2` usage, `3` the C
60
+ toolchain is unusable, `70` an internal compiler error — that last one is a
61
+ bug in `nish`, not in your program, and is worth reporting.
62
+ - Every failure is a `--json` object, toolchain and internal errors included,
63
+ so you never have to parse stderr to find out why a run failed.
64
+
65
+ Two more surfaces worth knowing: `nish --emit-ast f.ts` prints what was parsed
66
+ and `nish --emit-checked f.ts` prints the side tables the emitter reads. Both
67
+ are one node per line, which is to say diffable.
68
+
69
+ ## A whole program
70
+
71
+ ```ts nish:ok
72
+ const gcd = (a: i32, b: i32): i32 => {
73
+ let x = a;
74
+ let y = b;
75
+ while (y !== 0) {
76
+ const t = y;
77
+ y = x % y;
78
+ x = t;
79
+ }
80
+ return x;
81
+ };
82
+
83
+ export const main = (): i32 => {
84
+ console.log(`gcd = ${gcd(1071, 462)}`);
85
+ return 0;
86
+ };
87
+ ```
88
+
89
+ That is the shape: top-level `const` arrows, an exported `main`, explicit types
90
+ everywhere, a template literal instead of `String(n)`.
91
+
92
+ - A module is one `.ts` file. At the top level it may hold **only**
93
+ declarations: `const` arrows (functions), `class`, `interface`, module
94
+ `const`s, `type` aliases, numeric `enum`s, and `import`s. There is no
95
+ top-level code, so there is no initialisation order to think about.
96
+ - `export const main` lives in the entry module only, takes no parameters, and
97
+ returns `void` or an `i32`-lowered `number` — the process exit code. Under
98
+ `--number-mode f64`, declare it `main(): i32`.
99
+ - `number` is `i32` by default and `f64` under `--number-mode f64`. Write the
100
+ explicit width (`i32`, `f64`) when you mean one; it reads the same in both
101
+ modes.
102
+
103
+ ## What you must unlearn
104
+
105
+ Every row is something a TypeScript-trained model writes by reflex and Nish
106
+ rejects. This table is the highest-value part of the page.
107
+
108
+ | Your reflex | What happens | Write instead |
109
+ | --- | --- | --- |
110
+ | `if (xs.length)` | `Condition must be boolean … (Nish has no truthiness)` | `if (xs.length !== 0)` |
111
+ | `x == y` | `Loose equality is forbidden` | `x === y` |
112
+ | `throw new Error(m)` | `` `throw` is forbidden `` | `return Err(m)`, or `panic(m)` to end the process |
113
+ | `try { … } catch { … }` | `` `try`/`catch`/`finally` is forbidden `` | `Result<T, E>` and `isErr()` |
114
+ | `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.sort`, `.shift` | `` Unknown method `map` on i32[] (supported: push, pop, indexOf, join) `` | a `for` loop; there are no function values to pass |
115
+ | `s.toUpperCase()`, `s.split()`, `s.trim()`, `s.replace()` | `` Unknown method … on string `` | index bytes with `charCodeAt` / `substring` |
116
+ | `a?.b`, `a ?? b` | forbidden | `if (a !== null)` first |
117
+ | `x as T`, `<T>x`, `x!` | `Unsupported expression in Phase 1: AsExpression` | there are no casts; `implements` is the only widening |
118
+ | `any`, `unknown` | forbidden | name the real type |
119
+ | `undefined` | forbidden | `null`, with a `T \| null` type |
120
+ | `let total = 0` at the top level | `` Top-level `let` is not supported `` | a module `const`, or a local |
121
+ | a callback: `xs.forEach(f)`, `(cb: (n: i32) => i32)` | `` Unsupported type `(n: i32) => i32` `` | there are **no function values**; inline the body or write a loop |
122
+ | `type Pair<T>` (a generic alias) | `` expected `=`, found `<` `` (a syntax error) | 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 |
123
+ | `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 |
124
+ | `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 |
125
+ | `<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` |
126
+ | `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 |
127
+ | `async` / `await` / `Promise` | forbidden (no event loop) | the I/O builtins are synchronous |
128
+ | `class B extends A` | `` `extends` is not supported: Nish has no inheritance `` | repeat the fields and `implements` an interface |
129
+ | `static` members, getters/setters | not supported | module `const`s and plain methods |
130
+ | `type Pair = { a: i32 }` (inline object type) | `` Unsupported type `{ a: i32 }` `` | declare an `interface` |
131
+ | `A \| B` unions | `` Union types other than `T \| null` are forbidden `` | one type, or an `interface` prefix |
132
+ | `String(n)`, `n.toString()` | `` Unknown function `String` `` / `` Unknown method `toString` on i32 `` | `` `${n}` `` |
133
+ | `xs.length = 0` | `` Cannot assign to `length` of i32[] (array length is read-only; use `push`) `` | build a new array |
134
+ | `for (const k in o)` | `Unsupported statement in Phase 1: ForInStatement` | `for (const x of xs)` over an array |
135
+ | `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"`) |
136
+ | `export default f` | `` `export default` / `export =` are not supported `` | `export const f = …` |
137
+ | `export type T = …`, `export enum K` | cannot be exported | declare the alias/enum in each module that needs it |
138
+ | `new Date()`, `Date.now()` | `` Unknown builtin `Date.now` `` | `monotonicNanos()` for elapsed time; there is no wall clock and no calendar |
139
+ | `JSON.parse`, `RegExp`, `Map`, `Set`, `Promise` | unknown / forbidden | none of these exist; write them or restructure |
140
+ | `let x = 5; x = "s"` | `Cannot initialize …` | types never change and never convert implicitly |
141
+
142
+ A worked pair. This is the single most common rejection:
143
+
144
+ ```ts nish:err-body NL2188
145
+ const xs = [1, 2, 3];
146
+ if (xs.length) {
147
+ console.log("non-empty");
148
+ }
149
+ ```
150
+
151
+ ```ts nish:ok-body
152
+ const xs = [1, 2, 3];
153
+ if (xs.length !== 0) {
154
+ console.log("non-empty");
155
+ }
156
+ ```
157
+
158
+ ## Types
159
+
160
+ Every type is exactly one LLVM first-class type. No boxing, no runtime tag, no
161
+ structural subtyping, **no implicit conversion of any kind**: two values are
162
+ compatible only when their types are identical.
163
+
164
+ | Type | Is | Notes |
165
+ | --- | --- | --- |
166
+ | `number` | `i32`, or `f64` under `--number-mode f64` | the mode's default width |
167
+ | `i32`, `i64` | signed integers | signed overflow is undefined; `--wrapping` wraps |
168
+ | `u8`, `u16`, `u32`, `u64` | unsigned integers | arithmetic **always** wraps; `>>` is logical |
169
+ | `f32`, `f64` | IEEE-754 | `f32` is never the lowering of `number` |
170
+ | `boolean` | `i1` | no truthiness anywhere |
171
+ | `string` | immutable UTF-8 bytes | `length` is the **byte** length |
172
+ | `T[]`, `Array<T>` | one element type, bounds-checked | `readonly T[]` refuses every write |
173
+ | `Int32Array`, `Float32Array`, `Float64Array`, `BigInt64Array` | aliases of `i32[]`, `f32[]`, `f64[]`, `i64[]` | not distinct types |
174
+ | `class C`, `interface I` | a struct, fields in declaration order | no header, no vtable |
175
+ | `enum K` | a **distinct** type represented as `i32` | never interchangeable with `i32` |
176
+ | `T \| null` | `T` a class, interface, array, or string | a scalar can never be nullable |
177
+ | `Result<T, E>` | the only way to report failure | `E` may not be `void` |
178
+ | `void` | return type only | |
179
+
180
+ **Mixing widths is an error, always.** `f32 + f64`, `i64 + number`, even
181
+ `u32 + i32` (same LLVM type, different signedness) are rejected. Convert
182
+ explicitly: `toI32` `toI64` `toU8` `toU16` `toU32` `toU64` `toF32` `toF64`.
183
+
184
+ ```ts nish:err NL2231
185
+ const bad = (a: i32, b: i64): i64 => {
186
+ return a + b;
187
+ };
188
+ ```
189
+
190
+ ```ts nish:ok
191
+ const good = (a: i32, b: i64): i64 => {
192
+ return toI64(a) + b;
193
+ };
194
+ ```
195
+
196
+ **`integer` is reserved, not a type.** It is held for ranged integers
197
+ (`integer<Lo, Hi>`), which the compiler does not build yet, so writing it as a
198
+ type is refused. Declaring a type alias, enum, class, interface or function
199
+ named `integer` is refused too. Use `i32`. A value named `integer` (a local
200
+ or a module constant) is fine.
201
+
202
+ ```ts nish:err NL2333
203
+ const clamp = (x: integer): i32 => x;
204
+ ```
205
+
206
+ ```ts nish:err NL2332
207
+ class integer {
208
+ value: i32 = 0;
209
+ }
210
+ ```
211
+
212
+ ### Numeric literals take their type from the immediate context
213
+
214
+ A literal is the mode's default (`i32`, or `f64` in f64 mode) **unless its
215
+ immediate context demands another numeric type**, and the contexts that do are
216
+ a closed list: an annotated initializer, a `return`, an argument to a user
217
+ *function* or to a constructor, the other operand of a binary operator whose
218
+ type is already known, `Math.min` / `Math.max`, the f64-only `Math.*` and
219
+ `toF64` / `toF32`, `process.exit`, `Number`, an element of an array literal
220
+ that itself has a `T[]` context, a class field's literal initializer, a field
221
+ or element assignment target, a ternary arm taking the conditional's own
222
+ context, and `push` / `indexOf` through a receiver that is a **plain name**.
223
+
224
+ Everything else leaves the literal at the default, however obvious the intent.
225
+ The three that catch you out: an **object-literal property**, a `Result`
226
+ payload, and a **method's** argument.
227
+
228
+ ```ts nish:err NL2269
229
+ interface Pixel { r: u8; g: u8; b: u8; }
230
+
231
+ export const main = (): i32 => {
232
+ const p: Pixel = { r: 255, g: 0, b: 0 };
233
+ console.log(`${p.r}`);
234
+ return 0;
235
+ };
236
+ ```
237
+
238
+ ```ts nish:ok
239
+ interface Pixel { r: u8; g: u8; b: u8; }
240
+
241
+ export const main = (): i32 => {
242
+ const p: Pixel = { r: toU8(255), g: toU8(0), b: toU8(0) };
243
+ console.log(`${p.r}`);
244
+ return 0;
245
+ };
246
+ ```
247
+
248
+ The same rule refuses `const b: u8 = 1 + 2` — that is a sum of two `i32`
249
+ literals, not a literal in a `u8` context. Name the value first, or convert.
250
+
251
+ ## Failure: `Result`, not exceptions
252
+
253
+ There is no `throw`, no `try`, and no unwinding anywhere in the language. A
254
+ function that can fail says so in its return type.
255
+
256
+ ```ts nish:ok
257
+ const half = (n: i32): Result<i32, string> => {
258
+ if (n % 2 !== 0) {
259
+ return Err("odd");
260
+ }
261
+ return Ok(n / 2);
262
+ };
263
+
264
+ const quarter = (n: i32): Result<i32, string> => {
265
+ const h = half(n).orReturn(); // Rust's `?`: propagates the error
266
+ return half(h);
267
+ };
268
+
269
+ export const main = (): i32 => {
270
+ const outcome = quarter(8);
271
+ if (outcome.isErr()) {
272
+ console.error(outcome.error);
273
+ return 1;
274
+ }
275
+ console.log(`${outcome.value}`);
276
+ return 0;
277
+ };
278
+ ```
279
+
280
+ The surface: `Ok(v)` / `Ok()`, `Err(e)`, `r.isOk()`, `r.isErr()`, `r.ok`,
281
+ `r.value`, `r.error`, `r.orReturn()`, `r.unwrapOr(d)`, `r.expect(msg)`. There
282
+ is deliberately no `unwrap()` — `expect` insists on a reason — and no
283
+ `map`/`andThen`, which would need function values.
284
+
285
+ **Three rules, and they are the ones that catch you out:**
286
+
287
+ 1. **A `Result` cannot be dropped.** A call answering one may not stand as an
288
+ expression statement, and a local holding one must be read at least once.
289
+ Passing it on — as an argument, as a `return` — counts as reading it.
290
+ 2. **The error arm comes first.** `r.value` is legal only where the checker
291
+ proved `isOk()`; `r.error` only where it proved `isErr()`. There is no
292
+ spelling that reaches the success payload without deciding what happens to
293
+ the failure.
294
+ 3. **Propagation is contagious.** `r.orReturn()` is legal only inside a
295
+ function that itself returns a `Result` whose error arm accepts `E`. There
296
+ is no implicit error conversion — convert by hand with
297
+ `if (r.isErr()) { return Err(…); }`.
298
+
299
+ ```ts nish:err NL2044
300
+ const half = (n: i32): Result<i32, string> => {
301
+ return Ok(n / 2);
302
+ };
303
+
304
+ export const main = (): i32 => {
305
+ const r = half(8);
306
+ console.log(`${r.value}`);
307
+ return 0;
308
+ };
309
+ ```
310
+
311
+ Narrowing follows the same engine as `T | null` below: it applies to a
312
+ **variable**, never a property path; it ends at any assignment to that
313
+ variable; and it is dropped before a loop that assigns it. `if (r.isOk()) A
314
+ else B` narrows in `A`, and after the `if` when `B` cannot fall through.
315
+
316
+ `panic(message)` is the other ending: message to stderr, exit 1. It is for an
317
+ invariant that cannot hold, not for a failure a caller should handle. It
318
+ terminates control flow, so a non-`void` function may end with it.
319
+
320
+ ## Absence: nullable types
321
+
322
+ `T | null` is available for `T` a class, interface, array, or string — never a
323
+ scalar, which has no null value. It is the same pointer with `null` as one more
324
+ value, so nothing is boxed.
325
+
326
+ ```ts nish:ok
327
+ class Node {
328
+ value: i32;
329
+ next: Node | null = null;
330
+ constructor(value: i32) {
331
+ this.value = value;
332
+ }
333
+ }
334
+
335
+ const total = (head: Node | null): i32 => {
336
+ let sum = 0;
337
+ let cur = head;
338
+ while (cur !== null) {
339
+ sum = sum + cur.value;
340
+ cur = cur.next;
341
+ }
342
+ return sum;
343
+ };
344
+
345
+ export const main = (): i32 => {
346
+ const a = new Node(1);
347
+ a.next = new Node(2);
348
+ console.log(`${total(a)}`);
349
+ return 0;
350
+ };
351
+ ```
352
+
353
+ - Reading anything off an un-narrowed nullable is an error: narrow with
354
+ `!== null` first. `?.` and `??` are forbidden outright.
355
+ - Narrowing applies to a **local or parameter**, never a property path. `if
356
+ (n.next !== null) n.next.v` is rejected — copy into a local first.
357
+ - A narrowing ends at any assignment to the variable, and is dropped before a
358
+ loop whose body, condition or update assigns it.
359
+ - Two nullables cannot be compared with each other; compare each with `null`.
360
+ - `new Array<T | null>(n)` is allowed: the zero fill *is* `null`.
361
+
362
+ ## Declarations
363
+
364
+ ### Functions
365
+
366
+ ```ts nish:ok
367
+ const add = (a: i32, b: i32): i32 => {
368
+ return a + b;
369
+ };
370
+
371
+ const double = (n: i32): i32 => n * 2; // a concise body is that one `return`
372
+ ```
373
+
374
+ - A function is a **module-level `const` bound to an arrow**. `let` is
375
+ rejected; annotating the `const` is rejected (the annotation would be a
376
+ function type, and those are forbidden). The `function` keyword is accepted
377
+ as the legacy spelling and compiles to identical IR.
378
+ - **Every parameter and the return type must be annotated.**
379
+ - **A function is not a value.** `const alias = double` is
380
+ `` Unknown identifier `double` ``. No callbacks, no function types, ever.
381
+ - **Parameters are immutable**: `p = 1`, `p++`, `p += 1` are all rejected. Copy
382
+ into a `let` first.
383
+ - A non-`void` function must return on every path.
384
+ - Rejected: generators, `async`, destructured / rest / optional / default
385
+ parameters, overloads, nested function declarations. Type parameters *are*
386
+ allowed — see [Generic functions](#generic-functions).
387
+
388
+ ### Generic functions
389
+
390
+ A function may declare type parameters, and **each instantiation is compiled to
391
+ its own specialised function** — no boxing, no dictionary, no runtime type
392
+ information. The IR is what somebody would have written by hand at that type.
393
+
394
+ ```ts nish:ok
395
+ const identity = <T>(x: T): T => x;
396
+
397
+ export const main = (): i32 => {
398
+ console.log(identity(7));
399
+ console.log(identity("hi"));
400
+ return 0;
401
+ };
402
+ ```
403
+
404
+ - **The type argument is inferred from the arguments and is never written at a
405
+ call site.** `identity<i32>(7)` is
406
+ `Type arguments are not written at a call site in Nish`. Inference matches the
407
+ shape of each declared parameter against its argument — `T` against `i32`,
408
+ `T[]` against `i32[]`, `Result<T, string>` against `Result<i32, string>` —
409
+ left to right, first binding wins.
410
+ - **A type parameter that appears in no parameter is an error at the
411
+ declaration**, because it could never be inferred.
412
+ - **Every other rule still applies inside.** A type parameter is not a hole to
413
+ smuggle something through: each instantiation is checked with `T` bound to a
414
+ concrete type.
415
+ - **A template may be exported and instantiated from another module.** Each
416
+ instantiation is defined once, in the module that declares the template, and
417
+ `declare`d everywhere else — so one imported name becomes one symbol per
418
+ distinct type-argument tuple, and two modules asking for the same tuple share
419
+ the one definition. A type argument may be a class the declaring module has
420
+ never heard of. A template that is not exported cannot be imported, and an
421
+ imported one may be renamed (`import { identity as id }`) without moving the
422
+ symbol. A type-argument list on an imported name that is *not* a template is
423
+ `` `Point` in `./lib` takes no type arguments ``.
424
+ - **A type parameter has no members unless it is constrained.** Passing a `T`
425
+ on, returning it, storing it and comparing it are fine; `p.x`, `p.x = 1` and
426
+ `p.m()` through a `T` are `` Cannot read `x` of `T`: an unconstrained type
427
+ parameter has no members ``, even when every call passes a type with an `x`.
428
+ It is where the value came from that counts — a local copied from a `T`, an
429
+ element of a `T[]` and a narrowed `T | null` are all a `T` — never its type.
430
+
431
+ ```ts nish:err NL2329
432
+ interface Point { x: i32; y: i32; }
433
+
434
+ const getX = <T>(p: T): i32 => p.x;
435
+
436
+ export const main = (): i32 => {
437
+ const p: Point = { x: 1, y: 2 };
438
+ return getX(p);
439
+ };
440
+ ```
441
+
442
+ - **`<T extends Shape>` gives `T` exactly `Shape`'s members**: its fields, and
443
+ its methods when `Shape` is a class. A constraint is a declared class or
444
+ interface (or `Container<i32>`, with its arguments written out) — never a
445
+ scalar, `string`, an array or another type parameter. Each instantiation still
446
+ reads its own struct directly, with no vtable.
447
+ - **A type argument must satisfy the constraint**: be `Shape` itself, or a
448
+ class that declares `implements Shape`. Matching field names are not enough.
449
+ A class constraint is satisfied by that class alone. The refusal is at the
450
+ call: `` `T` of `areaOf` requires `T extends Shape`, and `i32` does not
451
+ implement it ``.
452
+ - **A constraint is a declaration, not a name.** A `Shape` your module declares
453
+ is not the `Shape` another module's `<T extends Shape>` names, even with the
454
+ same fields, so implementing your own is refused the same way; import the
455
+ template's `Shape` instead.
456
+
457
+ ```ts nish:ok
458
+ interface Shape { area: i32; }
459
+
460
+ class Circle implements Shape {
461
+ area: i32;
462
+ radius: i32;
463
+ constructor(r: i32) { this.area = 3 * r * r; this.radius = r; }
464
+ }
465
+
466
+ const areaOf = <T extends Shape>(s: T): i32 => s.area; // s.radius would be refused
467
+
468
+ export const main = (): i32 => areaOf(new Circle(2)) - 12;
469
+ ```
470
+
471
+ - **Not supported yet**: a default type argument (`<T = string>`) and type
472
+ parameters on a **constructor**, each with its own message, and a generic
473
+ **type alias**, which is only the syntax error `` expected `=`, found `<` ``.
474
+ A generic `main` is refused. There are no multiple bounds (`T extends A & B`)
475
+ and no bound that mentions another parameter.
476
+ - **`$` may not appear in a function, class or interface name** — it is what
477
+ separates a generic's name from its type arguments in the emitted symbol.
478
+ - **Read `identity<i32>` in a message, `identity$i32` in the IR.** Diagnostics,
479
+ `--emit-checked` and the `-g` debug names spell an instantiation as written
480
+ (`identity<Box<i32>>`, `Box<i32>.get`); symbols and `%struct` names are
481
+ mangled (`@identity$$Box$i32`, `%struct.Box$i32`).
482
+ - **A host calls an exported instantiation as `nish_gen_…`**: `identity<i32>`
483
+ is `nish_gen_identity_i32` in the C header, the `.d.ts`, the wasm loader and
484
+ the N-API addon, and `identity<i32[]>` is `nish_gen_identity_arr_i32`. There
485
+ is no `identity` to call — only the instantiations the program makes.
486
+ - **With a sidecar flag, an exported generic must be instantiated.** Nothing
487
+ instantiated means no symbol, so `--emit-header`, `--emit-dts` and
488
+ `--emit-napi` refuse it (NL4007) rather than leave it out; call it somewhere,
489
+ or do not export it. The same flags refuse two names that are one C
490
+ identifier, such as a method `Point.shifted` and a function `Point_shifted`
491
+ (NL4008).
492
+
493
+ ```ts nish:err NL4007 --emit-dts build/docs-ai/nl4007.d.ts
494
+ export const identity = <T>(x: T): T => x; // exported, never called
495
+
496
+ export const main = (): i32 => 0;
497
+ ```
498
+
499
+ ### Generic classes and interfaces
500
+
501
+ A `class` or an `interface` may take type parameters too, and an instantiation
502
+ is **an ordinary struct**: `Box<i32>` is `%struct.Box$i32` with `Box`'s fields
503
+ at `T = i32`, and everything the language does to a struct works on it.
504
+
505
+ ```ts nish:ok
506
+ class Box<T> {
507
+ value: T;
508
+ constructor(v: T) { this.value = v; }
509
+ get(): T { return this.value; }
510
+ }
511
+
512
+ interface Pair<A, B> { first: A; second: B; }
513
+
514
+ export const main = (): i32 => {
515
+ const b = new Box<i32>(7);
516
+ const p: Pair<i32, string> = { first: 1, second: "hi" };
517
+ console.log(p.second);
518
+ return b.get();
519
+ };
520
+ ```
521
+
522
+ - **Write the type arguments out.** `Box` on its own is not a type — there is
523
+ no layout until `T` is bound — so it is `` `Box` is generic: it must be
524
+ written with its type arguments ``. The two positions that take them are an
525
+ annotation (`const b: Box<i32>`) and `new` (`new Box<i32>(7)`); a *call*
526
+ infers instead. A class that is not generic takes none.
527
+ - **A type argument may be anything a type may be**, including another
528
+ instantiation: `Box<Point>`, `Box<Box<i32>>`, `Box<i32[]>`,
529
+ `Box<Result<i32, string>>`.
530
+ - **`implements` may name an instantiation**: `class Box<T> implements
531
+ Container<T>` is checked per instantiation by the usual field-prefix rule.
532
+ - **A field may not grow its own type argument.** `class Nest<T> { inner:
533
+ Nest<T[]> | null }` is `` Monomorphising `Nest` would not terminate ``;
534
+ `Nest<T>` and a type that mentions no parameter are both fine.
535
+ - **A type parameter takes no type arguments of its own.** `T` is whatever the
536
+ instantiation bound it to, so `const y: T<i32>` is
537
+ `` Unsupported type reference `T<i32>` ``.
538
+ - **A template obeys every rule a declared class obeys**: `declare class
539
+ Box<T>`, `export default class Box<T>`, `abstract class Box<T>` and
540
+ `interface Box<T> extends Base` are refused in the words their non-generic
541
+ spellings are refused in.
542
+ - **A template claims its name.** `class Box<T>` is a declaration of `Box`, so
543
+ a function, constant, alias, enum or second class of that name is refused
544
+ whichever was written first: `` `Box` is already declared in this module ``,
545
+ or `` Duplicate declaration of `Box` `` for a second class.
546
+ - **Two modules may not both declare a generic class of one name** once both
547
+ instantiate it: `%struct.Box$i32` is program-wide, so
548
+ `` Generic class `Holder` is also declared in helper.ts ``.
549
+ - **A generic class or interface may be imported**, and the rule is a generic
550
+ function's: `%struct.Box$i32` and every `@Box$i32.*` symbol are defined once,
551
+ by the module that declares `Box<T>`, and `declare`d by every module that
552
+ holds one. Unlike a declared class it may be renamed on import, because the
553
+ name that crosses the ABI is the template's. Named without its type arguments
554
+ it is `` `Crate` is generic: it must be written with its type arguments ``.
555
+ - **A class or interface may constrain its parameters** — `class Holder<T
556
+ extends Shape>` — by the rules a function's follow: its methods may read
557
+ `this.item.area` through a field of type `T`, and `new Holder<Point>` or an
558
+ annotation `Holder<Point>` is refused unless `Point` satisfies `Shape`.
559
+
560
+ ### Generic methods
561
+
562
+ A method — of a generic class or not — may declare its own type parameters.
563
+ They are inferred from the arguments like a generic function's, and each
564
+ (receiver, method type arguments) pair is its own `define`:
565
+ `@Chooser.pick$i32`, `@Box$i32.keep$str`.
566
+
567
+ ```ts nish:ok
568
+ class Chooser {
569
+ flip: boolean = false;
570
+ pick<T>(a: T, b: T): T { return this.flip ? b : a; }
571
+ }
572
+
573
+ class Box<T> {
574
+ value: T;
575
+ constructor(v: T) { this.value = v; }
576
+ keep<U>(other: U): T { return this.value; }
577
+ }
578
+
579
+ export const main = (): i32 => {
580
+ const c = new Chooser();
581
+ const word: string = c.pick("left", "right");
582
+ console.log(word);
583
+ return c.pick(0, 1) + new Box<i32>(7).keep("seven") - 7;
584
+ };
585
+ ```
586
+
587
+ - **Never write the type arguments at the call**: `c.pick<i32>(1, 2)` is
588
+ `` Type arguments are not written at a call site ``. A method type parameter
589
+ that no parameter mentions is `` Cannot infer `U` for `Holder.make` ``.
590
+ - **Do not reuse a class parameter's name**: `class Box<T> { map<T>(…) }` is
591
+ `` Type parameter `T` of `Box.map` shadows `Box`'s own `T` `` (NL2331) —
592
+ call the method's `U`.
593
+ - **Constrain it like a function's** — `apply<U extends Shape>(u: U)` reads
594
+ `u.area` — but the constraint may not mention a type parameter, the class's
595
+ included.
596
+ - **A constructor takes none**; its class's are written after `new`.
597
+
598
+ ```ts nish:err NL2331
599
+ class Box<T> {
600
+ value: T;
601
+ constructor(v: T) { this.value = v; }
602
+ map<T>(other: T): T { return other; } // shadows Box's T: call it U
603
+ }
604
+
605
+ export const main = (): i32 => new Box<i32>(1).value;
606
+ ```
607
+
608
+ ### Calling C
609
+
610
+ `declare function name(params): T;` declares a C function this program calls but
611
+ does not define. The symbol is the identifier, unmangled, and the call is an
612
+ ordinary call.
613
+
614
+ ```ts nish:ok
615
+ declare function abs(n: i32): i32;
616
+
617
+ export const main = (): i32 => {
618
+ console.log(`${abs(-5)}`);
619
+ return 0;
620
+ };
621
+ ```
622
+
623
+ - **Every position must be a scalar** — the integer and float widths, `boolean`,
624
+ `void`. A `string`, array, class or interface is rejected. That is not style:
625
+ a scalar boundary has no pointer for a foreign function to capture or free,
626
+ which is what lets the escape analysis stay correct without knowing anything
627
+ about the callee.
628
+ - **No body, and it cannot be `export`ed.**
629
+ - **It costs the caller its attributes.** Nothing about a body the compiler
630
+ cannot see is provable, so a function that calls C loses `readnone` and
631
+ `willreturn`, and so does everything above it in the call graph. A program
632
+ that calls C is no longer one the compiler can reason about end to end — that
633
+ trade *is* the feature.
634
+
635
+ ### Module constants
636
+
637
+ ```ts nish:ok
638
+ const TOKEN_NAME: i32 = 1;
639
+ const TOKEN_END: i32 = TOKEN_NAME + 1;
640
+ export const PROMPT: string = "> ";
641
+ ```
642
+
643
+ A top-level `const` names a value the compiler already knows. **No symbol is
644
+ emitted and no initialiser runs** — every use lowers to the value itself.
645
+
646
+ - The **type annotation is required**, and so is an initialiser.
647
+ - The type must be `i32`, `i64`, `f64`, `number`, `boolean`, or `string`. An
648
+ array, class or interface constant would need an allocation, and there is no
649
+ code to run it.
650
+ - The initialiser is literals, other constants (declared anywhere, or
651
+ imported), and arithmetic over them. A call, a `new`, a local — anything else
652
+ — is rejected.
653
+ - Folding uses the language's own arithmetic, so an overflowing fold is an
654
+ error rather than a wrap, and `1 / 0` in a constant is refused at compile
655
+ time.
656
+
657
+ ### Classes and interfaces
658
+
659
+ ```ts nish:ok
660
+ interface Shape { width: i32; height: i32; }
661
+
662
+ class Rect implements Shape {
663
+ width: i32;
664
+ height: i32;
665
+ label: string = "r";
666
+
667
+ constructor(width: i32, height: i32) {
668
+ this.width = width;
669
+ this.height = height;
670
+ }
671
+
672
+ area(): i32 {
673
+ return this.width * this.height;
674
+ }
675
+ }
676
+
677
+ const describe = (s: Shape): i32 => s.width + s.height;
678
+
679
+ export const main = (): i32 => {
680
+ const r = new Rect(3, 4);
681
+ console.log(`${r.area()} ${describe(r)}`);
682
+ return 0;
683
+ };
684
+ ```
685
+
686
+ - Fields need annotations; an initializer must be a **literal** of the field's
687
+ type. Layout is declaration order, exactly as clang lays out the same C
688
+ struct.
689
+ - At most one constructor, no parameter properties, no overloads. **Definite
690
+ assignment** is checked syntactically: after the constructor returns every
691
+ field holds a value, and reading `this.f` before every field is assigned is
692
+ an error.
693
+ - Methods need a body and an explicit return type. `readonly` fields may be
694
+ assigned only as `this.f = v` in their own class's constructor.
695
+ - `===` / `!==` on two values of one class is pointer identity; `<` and friends
696
+ are rejected.
697
+ - **No inheritance.** `extends` and `super` are rejected in every spelling.
698
+ - An **interface** is a struct with fields only — no methods, no `extends`, no
699
+ `new`.
700
+ - **`class C implements I` requires `I`'s fields to be `C`'s first fields**, in
701
+ order, with identical types; `C` may declare more after them. A `C` then
702
+ converts to an `I` wherever one is expected. **This is the only widening in
703
+ the language**, and nothing converts back — no downcast, no `instanceof`.
704
+
705
+ ### An array of records is one contiguous block
706
+
707
+ `Point[]` where `Point` is a class or interface stores the records themselves,
708
+ back to back — not an array of pointers. So `ps[i]` is an *interior* pointer,
709
+ and `push` may move the whole block to grow it. **Holding an element across a
710
+ `push` on the same array is therefore refused**, because the write would land
711
+ in the old storage:
712
+
713
+ ```ts nish:err NL2290
714
+ interface Point { x: i32; }
715
+
716
+ export const main = (): i32 => {
717
+ const ps: Point[] = [{ x: 1 }];
718
+ const p = ps[0];
719
+ ps.push({ x: 2 });
720
+ p.x = 3;
721
+ return 0;
722
+ };
723
+ ```
724
+
725
+ Index again after the `push` instead of holding the element across it:
726
+
727
+ ```ts nish:ok
728
+ interface Point { x: i32; }
729
+
730
+ export const main = (): i32 => {
731
+ const ps: Point[] = [{ x: 1 }];
732
+ ps.push({ x: 2 });
733
+ ps[0].x = 3;
734
+ console.log(`${ps[0].x}`);
735
+ return 0;
736
+ };
737
+ ```
738
+
739
+ ### Object literals
740
+
741
+ An object literal takes its type from context — a variable annotation, the
742
+ enclosing function's return type, the parameter it is passed to, the field it
743
+ is assigned to, or a field of an enclosing literal. It must set every field
744
+ exactly once, with plain identifier keys.
745
+
746
+ ```ts nish:ok
747
+ interface Pair { first: i32; second: i32; }
748
+
749
+ const swap = (p: Pair): Pair => ({ first: p.second, second: p.first });
750
+
751
+ export const main = (): i32 => {
752
+ const p: Pair = { first: 1, second: 2 };
753
+ console.log(`${swap(p).first}`);
754
+ return 0;
755
+ };
756
+ ```
757
+
758
+ The context reaches a nested literal and a `null`, and stops there: a numeric
759
+ literal in a property keeps the mode's default type (above), and `[]` has no
760
+ element type at all in that position.
761
+
762
+ ### Type aliases and enums
763
+
764
+ ```ts nish:ok
765
+ type Byte = u8;
766
+ type Bytes = Byte[];
767
+
768
+ enum Kind {
769
+ If = 1,
770
+ While = 2,
771
+ }
772
+
773
+ const name = (k: Kind): string => {
774
+ switch (k) {
775
+ case Kind.If:
776
+ return "if";
777
+ default:
778
+ return "while";
779
+ }
780
+ };
781
+
782
+ export const main = (): i32 => {
783
+ console.log(name(Kind.If));
784
+ return 0;
785
+ };
786
+ ```
787
+
788
+ - A `type` alias is a second name for an existing type, **not a type of its
789
+ own** — the same program with and without its aliases emits byte-identical
790
+ IR. A generic *alias* is still forbidden even though a generic function,
791
+ class and interface are not; built-in names may not be aliased.
792
+ - A numeric `enum` is a **distinct type** represented as `i32`, and that is the
793
+ point: `Kind` and `i32` never convert in either direction. Members must be
794
+ numeric literals. `===` and `!==` are the **only** operators — no arithmetic,
795
+ no bitwise, so **bit flags stay `i32` module constants**.
796
+ - **Neither can be exported.** Both are module-local; declare them in every
797
+ module that needs them. A class or interface is what crosses a module
798
+ boundary.
799
+ - Both are top-level only.
800
+
801
+ ### Modules
802
+
803
+ - `export` goes on `const` (function or constant), `class`, and `interface`
804
+ declarations. No `export default`, no `export { … }`, no `export *`.
805
+ - The only import form is a **named import**:
806
+ `import { square, cube as pow3 } from "./math"`. `.ts` is optional. Default
807
+ imports, namespace imports, side-effect imports and type-only imports are all
808
+ rejected.
809
+ - A specifier is a relative path (`./x`, `../x`), a builtin module (`nish:fs`),
810
+ a standard-library module (`nish/text`, `nish/pair`), or a **package name** (`hash`,
811
+ `@scope/hash`, with a subpath after it if you want one).
812
+ - **A package is resolved through `node_modules` and compiled from source.**
813
+ The package's `package.json` must offer the file under the `nish` export
814
+ condition — `{"exports": {".": {"nish": "./src/index.ts"}}}` — and the mode
815
+ gets a spelling of its own, `nish-i32` / `nish-f64`, for source that is only
816
+ correct under one `--number-mode`. The mode-qualified condition wins wherever
817
+ the package declares it, so a manifest carrying both never compiles the wrong
818
+ one of the two. A package without that condition is
819
+ `` Package `lodash` has no Nish entry point ``, which is what an ordinary npm
820
+ package gets: there is nothing to compile in a `.js` file. One that offers
821
+ only the other mode is `` supports Nish in f64 mode only ``, one whose
822
+ `"engines": {"nish": ">=X.Y.Z"}` is above this compiler `` needs a newer
823
+ compiler ``, and a `package.json` that is not JSON names its line and
824
+ column — each with its own `--json` code (`NL3017`–`NL3021`).
825
+ - Functions may be renamed on import; classes and interfaces may not — the type
826
+ name is part of the ABI.
827
+ - Import cycles are allowed; a shared dependency is compiled once.
828
+ - **A function name is unique across the whole program**, exported or not.
829
+ - An `export`ed function is an external C-ABI symbol; every other function gets
830
+ `internal` linkage so LLVM may inline or drop it.
831
+
832
+ ## Statements
833
+
834
+ - **Conditions must be `boolean`.** There is no truthiness, in `if`, `while`,
835
+ `for`, the ternary, `&&` or `||`.
836
+ - `let` / `const` need an initializer and take their type from it; an
837
+ annotation must match **exactly**. `const` freezes the binding, not the
838
+ contents: `xs[0] = 1` and `xs.push(1)` on a `const xs` are fine.
839
+ - `var` is forbidden. Destructuring is not supported.
840
+ - `for (init; cond; update)` with every clause optional, and
841
+ `for (const x of xs)` over an **array** only — `x` gets the element type and
842
+ must not be annotated. The array's `length` is re-read each iteration, so a
843
+ `push` inside the body extends the loop.
844
+ - `switch` takes an **integer or enum** discriminant; every `case` label is an
845
+ integer constant expression of that type. **There is no implicit
846
+ fallthrough** — a clause with statements ends in `break`, `return`,
847
+ `continue` or `process.exit`, unless it is the last. An *empty* clause does
848
+ fall through, which is how `case 1: case 2:` shares a body. A clause cannot
849
+ declare a variable directly; wrap its body in a block.
850
+ - `break` / `continue` are unlabelled only; labeled statements are forbidden.
851
+ - `process.exit(code)` and `panic(msg)` **terminate control flow**, so a
852
+ non-`void` function may end with either.
853
+ - **Unreachable code is an error**, not a warning: anything after a `return`,
854
+ `break`, `continue`, `process.exit`, an `if` whose branches all return, or an
855
+ infinite loop.
856
+
857
+ ```ts nish:err NL2166
858
+ const classify = (n: i32): string => {
859
+ switch (n) {
860
+ case 0:
861
+ console.log("zero"); // no `break`: a clause with statements must end in one
862
+ case 1:
863
+ return "one";
864
+ default:
865
+ return "many";
866
+ }
867
+ };
868
+
869
+ export const main = (): i32 => {
870
+ console.log(classify(0));
871
+ return 0;
872
+ };
873
+ ```
874
+
875
+ ## Expressions
876
+
877
+ - Arithmetic `+ - * / %` takes **two numbers of one type**. `+` on two strings
878
+ concatenates; `string + number` is rejected (use a template literal).
879
+ - **Integer division is checked**, Rust-style: a zero divisor, or `MIN / -1`,
880
+ prints to stderr and exits 1. Float division is never checked.
881
+ - Bitwise `& | ^ ~` and shifts `<< >> >>>` take **integers of one type**.
882
+ `>>` is arithmetic on a signed type and logical on an unsigned one; the shift
883
+ count is masked to the operand width.
884
+ - `=== !==` on any two values of the same type: integers and booleans by value,
885
+ `f64` by `fcmp` (so `x !== x` is true for `NaN`), **strings by content**,
886
+ arrays and objects by **identity**.
887
+ - `< <= > >=` on two numbers of one type and nothing else — not booleans, not
888
+ strings, not structs.
889
+ - `++` / `--` work on **numeric mutable locals only**, not fields or elements.
890
+ - Compound assignment `+= -= *= /= %=` requires a numeric target (so `+=` never
891
+ concatenates strings); the bitwise forms need an integer target.
892
+ - **Forbidden**: `,` `??` `?.` `in` `instanceof` `typeof` `delete` `void expr`
893
+ `==` `!=` `**` unary `+`.
894
+ - Element access `a[i]` needs an array and a numeric index, and is
895
+ bounds-checked (negative indices fail too). String-keyed access is forbidden.
896
+ The check comes off where the compiler proves the index in range — a loop
897
+ condition `i < xs.length`, or `i < h.xs.length` on a field path — as long as
898
+ nothing between the test and the access calls a function, stores to a field
899
+ the path names, or reassigns its root.
900
+ - Array literals need one element type; `[]` needs a contextual `T[]`. Holes
901
+ and spread are not supported.
902
+ - Template literals accept holes of type `string`, `number`, `i64`, `f64` or
903
+ `boolean` — this is how you convert a number to a string.
904
+
905
+ ## The builtins, in full
906
+
907
+ No import is needed; these are resolved by name, and a user function of the
908
+ same bare name shadows the builtin. **This list is exhaustive** — anything not
909
+ on it does not exist, and inventing a method is the most common way to write a
910
+ program that does not compile.
911
+
912
+ **Output.** `console.log(x)` and `console.error(x)` take exactly one argument
913
+ of `string | number | i64 | f64 | boolean` and are **statement position only**.
914
+ `write(s)` / `writeError(s)` take a `string` and add no newline.
915
+ `panic(message)` writes to stderr and exits 1.
916
+
917
+ **`Math`.** `sqrt` `floor` `ceil` `trunc` `sin` `cos` `exp` `log` (all `f64`
918
+ argument, `f64` result), `pow(x, y)`, `round(x)`, `abs(x)` (any numeric type),
919
+ `min(a, b)` / `max(a, b)` (exactly two, one type), `random()`, and the
920
+ constants `Math.PI` / `Math.E`. The f64-only ones reject an `i32`: write
921
+ `Math.sqrt(toF64(n))`.
922
+
923
+ **Conversions.** `toI32` `toI64` `toU8` `toU16` `toU32` `toU64` `toF32`
924
+ `toF64`; `f64ToBits(x)` / `bitsToF64(b)` reinterpret rather than convert;
925
+ `Number(x)`, `parseInt(s)` (base 10, `i32`, no `NaN` — no digits give `0`),
926
+ `parseFloat(s)`.
927
+
928
+ **Arrays.** `a.length` (read-only), `a.push(v)`, `a.pop()` (panics when empty —
929
+ there is no `undefined` to return), `a.indexOf(v)`, `a.join(sep)` — **`join` is
930
+ `string[]` only**. That is every array method there is.
931
+
932
+ **Strings.** `s.length` (**bytes**), `s.charCodeAt(i)` (the byte, bounds-checked),
933
+ `s.substring(start[, end])`, `s.slice(start[, end])`, `s.indexOf(sub)`,
934
+ `s.startsWith(sub)`, `s.endsWith(sub)`, and `String.fromCharCode(c)`. That is
935
+ every string method there is. `substring` **clamps** an out-of-range offset the
936
+ way JavaScript does; `slice` instead **panics** unless
937
+ `0 <= start <= end <= s.length`, and is the faster of the two when you have
938
+ already established the range. Every offset is a **byte** offset, so cutting a multi-byte character
939
+ in half is possible.
940
+
941
+ **Process.** `process.exit(code)`, `process.argv` (a read-only `string[]`;
942
+ `argv[0]` is the program path, one index earlier than Node),
943
+ `process.platform`, `process.arch`.
944
+
945
+ **Files and the system.** `readFileSync(path)` (exits 1 on failure),
946
+ `readFileSyncOrNull(path)` (`string | null`), `writeFileSync(path, data)`,
947
+ `appendFileSync(path, data)`, `mkdirSync(path)` (one level, `boolean`),
948
+ `isDirectorySync(path)`, `readdirSync(path)` (`string[] | null`, sorted by
949
+ bytes, no `.`/`..`), `spawnSync(argv)`, `spawnSyncTo(argv, outPath, errPath)`,
950
+ `getenv(name)` (`string | null` — unset and empty are different answers),
951
+ `realpathSync(path)` (`string | null`; symbolic links resolved, absolute, and
952
+ `null` when it does not resolve),
953
+ `monotonicNanos()` (`i64`; elapsed time only, **there is no wall clock and no
954
+ `Date`**).
955
+
956
+ **Arena.** `Arena.mark()`, `Arena.release(m)`, `Arena.reset()`, `Arena.used()`
957
+ — see below.
958
+
959
+ ## Memory
960
+
961
+ There is **no garbage collector and no `free`**, and nothing about this changes
962
+ what a program computes — only where memory lives. You do not have to manage
963
+ it, and mostly you should not try:
964
+
965
+ 1. **Stack allocation** for a `new`, object literal, array literal or
966
+ `new Array<T>(literal)` whose value provably does not outlive its function.
967
+ 2. **Automatic arena scopes** for a function whose temporaries all die with it:
968
+ a mark on entry, a release before every `ret`.
969
+ 3. **A reclaim at the call site** for a function that returns a string.
970
+ 4. **Explicit control** with the `Arena` builtins, for code that manages
971
+ batches itself.
972
+
973
+ Everything else is bumped from the arena, which is released when `main`
974
+ returns. **Safety rule**: `Arena.release` / `Arena.reset` while any object,
975
+ array or string allocated after the mark is still referenced is undefined
976
+ behaviour. Reach for them only when you are deliberately managing a batch.
977
+
978
+ ## Recipes for what is missing
979
+
980
+ **Iterate and transform** — there is no `map`, and no function to pass it:
981
+
982
+ ```ts nish:ok-body
983
+ const xs: i32[] = [1, 2, 3];
984
+ const doubled: i32[] = [];
985
+ for (const x of xs) {
986
+ doubled.push(x * 2);
987
+ }
988
+ console.log(`${doubled.length}`);
989
+ ```
990
+
991
+ **Convert a number to a string** — there is no `toString` and no `String()`:
992
+
993
+ ```ts nish:ok-body
994
+ const n = 42;
995
+ const s = `${n}`;
996
+ console.log(s);
997
+ ```
998
+
999
+ **Return two values** — there are no tuples: declare an interface.
1000
+
1001
+ ```ts nish:ok
1002
+ interface Split { head: string; rest: string; }
1003
+
1004
+ const cut = (s: string, at: i32): Split => ({
1005
+ head: s.substring(0, at),
1006
+ rest: s.substring(at),
1007
+ });
1008
+
1009
+ export const main = (): i32 => {
1010
+ console.log(cut("hello", 2).rest);
1011
+ return 0;
1012
+ };
1013
+ ```
1014
+
1015
+ When the two values need no names of their own, `import { Pair } from "nish/pair"`
1016
+ and return `Pair<A, B>`: an object literal `{ first, second }` at the return,
1017
+ exactly as above. It is for returning two values, not storing them — a struct
1018
+ kept in an array or a field wants named fields.
1019
+
1020
+ **A map or a set** — neither exists. Use parallel arrays with a linear scan, or
1021
+ an array indexed by a small integer key, and write the lookup out.
1022
+
1023
+ **Shared behaviour over several shapes** — there is no inheritance and no
1024
+ dispatch. Give each class the interface's fields **first**, `implements` it,
1025
+ and write a free function over the interface:
1026
+
1027
+ ```ts nish:ok
1028
+ interface Sized { width: i32; height: i32; }
1029
+
1030
+ class Photo implements Sized {
1031
+ width: i32;
1032
+ height: i32;
1033
+ path: string = "";
1034
+ constructor(width: i32, height: i32) {
1035
+ this.width = width;
1036
+ this.height = height;
1037
+ }
1038
+ }
1039
+
1040
+ const area = (s: Sized): i32 => s.width * s.height;
1041
+
1042
+ export const main = (): i32 => {
1043
+ console.log(`${area(new Photo(3, 4))}`);
1044
+ return 0;
1045
+ };
1046
+ ```
1047
+
1048
+ **Optional parameters** — there are none. Write two functions with different
1049
+ names, or take the value and document the sentinel.
1050
+
1051
+ **A wall clock, regex, JSON, threads of your own** — not in the language. Say
1052
+ so rather than emitting code that cannot compile.
1053
+
1054
+ ## Before you say it compiles
1055
+
1056
+ Run it. `nish file.ts --json` is one command and it is the only proof.
1057
+
1058
+ 1. Did you **run the compiler**? Nothing else counts.
1059
+ 2. Are all conditions `boolean` — no `if (x)` on a number, string or array?
1060
+ 3. Did you invent a method? Check it against
1061
+ [the builtins list](#the-builtins-in-full).
1062
+ 4. Is every parameter, return type, field and module constant annotated?
1063
+ 5. Does every numeric type match exactly — no `i32` meeting `i64`, no literal
1064
+ in an object-literal property or a method argument expecting a width?
1065
+ 6. Is every `Result` read, and is every `.value` behind an `isOk()`?
1066
+ 7. Is every nullable narrowed with `!== null` before it is touched?
1067
+ 8. Did you use `throw`, `try`, `any`, `undefined`, `?.`, `??`, a cast, a
1068
+ callback, a generic *alias*, or `extends`?
1069
+ 9. If you are adding to this repository: `npm run check` and `npm test` green,
1070
+ and a new construct ships a golden `.ll`, an `llvm-as` pass, a native round
1071
+ trip, a negative test, its `LANGUAGE.md` rule and cookbook entry, and a
1072
+ `CHANGELOG.md` line.
1073
+
1074
+ Every `ts nish:ok` and `ts nish:err` block on this page is compiled by
1075
+ `npm test`, so an example that has gone stale is a failing test rather than a
1076
+ program that no longer works.