numtypes 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +12 -0
  3. package/LICENSE-APACHE +201 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +652 -0
  6. package/dist/lib/index.d.ts +22 -0
  7. package/dist/lib/index.d.ts.map +1 -0
  8. package/dist/lib/index.js +2 -0
  9. package/dist/lib/index.js.map +1 -0
  10. package/dist/transformer/analyze/analyze-source-file.d.ts +15 -0
  11. package/dist/transformer/analyze/analyze-source-file.d.ts.map +1 -0
  12. package/dist/transformer/analyze/analyze-source-file.js +605 -0
  13. package/dist/transformer/analyze/analyze-source-file.js.map +1 -0
  14. package/dist/transformer/analyze/get-contextual-domain.d.ts +19 -0
  15. package/dist/transformer/analyze/get-contextual-domain.d.ts.map +1 -0
  16. package/dist/transformer/analyze/get-contextual-domain.js +197 -0
  17. package/dist/transformer/analyze/get-contextual-domain.js.map +1 -0
  18. package/dist/transformer/analyze/get-expression-domain.d.ts +26 -0
  19. package/dist/transformer/analyze/get-expression-domain.d.ts.map +1 -0
  20. package/dist/transformer/analyze/get-expression-domain.js +804 -0
  21. package/dist/transformer/analyze/get-expression-domain.js.map +1 -0
  22. package/dist/transformer/analyze/type-domain.d.ts +41 -0
  23. package/dist/transformer/analyze/type-domain.d.ts.map +1 -0
  24. package/dist/transformer/analyze/type-domain.js +260 -0
  25. package/dist/transformer/analyze/type-domain.js.map +1 -0
  26. package/dist/transformer/ast.d.ts +10 -0
  27. package/dist/transformer/ast.d.ts.map +1 -0
  28. package/dist/transformer/ast.js +115 -0
  29. package/dist/transformer/ast.js.map +1 -0
  30. package/dist/transformer/diagnostics.d.ts +17 -0
  31. package/dist/transformer/diagnostics.d.ts.map +1 -0
  32. package/dist/transformer/diagnostics.js +30 -0
  33. package/dist/transformer/diagnostics.js.map +1 -0
  34. package/dist/transformer/domains.d.ts +11 -0
  35. package/dist/transformer/domains.d.ts.map +1 -0
  36. package/dist/transformer/domains.js +32 -0
  37. package/dist/transformer/domains.js.map +1 -0
  38. package/dist/transformer/index.d.ts +10 -0
  39. package/dist/transformer/index.d.ts.map +1 -0
  40. package/dist/transformer/index.js +60 -0
  41. package/dist/transformer/index.js.map +1 -0
  42. package/dist/transformer/operators.d.ts +16 -0
  43. package/dist/transformer/operators.d.ts.map +1 -0
  44. package/dist/transformer/operators.js +44 -0
  45. package/dist/transformer/operators.js.map +1 -0
  46. package/dist/transformer/options.d.ts +19 -0
  47. package/dist/transformer/options.d.ts.map +1 -0
  48. package/dist/transformer/options.js +17 -0
  49. package/dist/transformer/options.js.map +1 -0
  50. package/dist/transformer/symbols.d.ts +56 -0
  51. package/dist/transformer/symbols.d.ts.map +1 -0
  52. package/dist/transformer/symbols.js +270 -0
  53. package/dist/transformer/symbols.js.map +1 -0
  54. package/dist/transformer/transform/erase-imports.d.ts +14 -0
  55. package/dist/transformer/transform/erase-imports.d.ts.map +1 -0
  56. package/dist/transformer/transform/erase-imports.js +174 -0
  57. package/dist/transformer/transform/erase-imports.js.map +1 -0
  58. package/dist/transformer/transform/generated-coercions.d.ts +9 -0
  59. package/dist/transformer/transform/generated-coercions.d.ts.map +1 -0
  60. package/dist/transformer/transform/generated-coercions.js +22 -0
  61. package/dist/transformer/transform/generated-coercions.js.map +1 -0
  62. package/dist/transformer/transform/optimize-coercions.d.ts +11 -0
  63. package/dist/transformer/transform/optimize-coercions.d.ts.map +1 -0
  64. package/dist/transformer/transform/optimize-coercions.js +1702 -0
  65. package/dist/transformer/transform/optimize-coercions.js.map +1 -0
  66. package/dist/transformer/transform/transform-declaration-file.d.ts +9 -0
  67. package/dist/transformer/transform/transform-declaration-file.d.ts.map +1 -0
  68. package/dist/transformer/transform/transform-declaration-file.js +376 -0
  69. package/dist/transformer/transform/transform-declaration-file.js.map +1 -0
  70. package/dist/transformer/transform/transform-expression.d.ts +24 -0
  71. package/dist/transformer/transform/transform-expression.d.ts.map +1 -0
  72. package/dist/transformer/transform/transform-expression.js +545 -0
  73. package/dist/transformer/transform/transform-expression.js.map +1 -0
  74. package/dist/transformer/transform/transform-source-file.d.ts +10 -0
  75. package/dist/transformer/transform/transform-source-file.d.ts.map +1 -0
  76. package/dist/transformer/transform/transform-source-file.js +52 -0
  77. package/dist/transformer/transform/transform-source-file.js.map +1 -0
  78. package/dist/transformer/ts-compat.d.ts +4 -0
  79. package/dist/transformer/ts-compat.d.ts.map +1 -0
  80. package/dist/transformer/ts-compat.js +24 -0
  81. package/dist/transformer/ts-compat.js.map +1 -0
  82. package/docs/implementation-plan.md +335 -0
  83. package/docs/lib-implementation.md +77 -0
  84. package/docs/lowering-optimization-spec.md +1020 -0
  85. package/docs/project-structure.md +52 -0
  86. package/docs/transform-spec.md +2114 -0
  87. package/package.json +83 -0
@@ -0,0 +1,2114 @@
1
+ # Transform Spec
2
+
3
+ This document defines the arithmetic closure transform for the branded numeric
4
+ types described in `docs/lib-implementation.md`.
5
+
6
+ The transformer erases every branded numeric value to JavaScript `number` at
7
+ runtime, then inserts JavaScript coercion idioms so that the generated
8
+ expression remains closed over the requested numeric domain.
9
+
10
+ Redundant coercions generated by numtypes lowering may be removed by the
11
+ proof-based coercion optimization pass defined in
12
+ `docs/lowering-optimization-spec.md`. User-authored coercions are preserved. The
13
+ rules in this document remain the conservative correctness baseline.
14
+
15
+ ## Core Rules
16
+
17
+ The four checked cast calls are:
18
+
19
+ ```typescript
20
+ // signed int32
21
+ i32(x) => x | 0
22
+
23
+ // unsigned int32
24
+ u32(x) => x >>> 0
25
+
26
+ // single precision float
27
+ f32(x) => Math.fround(x)
28
+
29
+ // double precision float
30
+ f64(x) => +x
31
+ ```
32
+
33
+ The function symbols are erased cast markers, not runtime utilities. A checked
34
+ cast marker must appear as a direct call with exactly one argument. The callee
35
+ may be either a numtypes imported identifier or a property access whose receiver
36
+ is exactly a numtypes namespace import and whose property is one of `i32`, `u32`,
37
+ `f32`, or `f64`. It must not be aliased, destructured, stored, passed, put into
38
+ an object, parenthesized as the callee, optional-called, optional-accessed,
39
+ spread-called, called with type arguments, or re-exported as a value.
40
+
41
+ ```typescript
42
+ i32(value); // ok
43
+ nt.i32(value); // ok when nt is imported from "numtypes"
44
+ i32(); // error
45
+ i32(...tuple); // error
46
+ i32<number>(value); // error
47
+ const cast = i32; // error
48
+ const cast = nt.i32; // error
49
+ const box = { i32 }; // error
50
+ (i32)(value); // error
51
+ (nt.i32)(value); // error
52
+ export { i32 } from "numtypes"; // error
53
+ export type { i32 } from "numtypes"; // ok
54
+ ```
55
+
56
+ TypeScript assertions are unchecked domain assertions. They erase like normal
57
+ TypeScript assertions and do not insert a coercion by themselves.
58
+
59
+ ```typescript
60
+ x as i32 => x
61
+ <i32>x => x
62
+ ```
63
+
64
+ Use a checked cast call when the expression boundary must perform the runtime
65
+ coercion. Use `as` only when the value is already trusted and the user wants to
66
+ choose the domain for later numtypes operations.
67
+
68
+ Unchecked assertions have no retroactive coercion effect. They can choose the
69
+ domain seen by later transformer analysis, but the asserted expression itself is
70
+ emitted unchanged unless its own operands already form a typed operation.
71
+
72
+ ```typescript
73
+ function trusted(a: i32, b: i32): i32 {
74
+ return (a + b) as i32;
75
+ }
76
+ ```
77
+
78
+ should be transformed to:
79
+
80
+ ```typescript
81
+ function trusted(a: number, b: number): number {
82
+ return ((a | 0) + (b | 0)) | 0;
83
+ }
84
+ ```
85
+
86
+ The operation is lowered because `a + b` is an `i32` operation. The assertion
87
+ only satisfies the branded TypeScript boundary.
88
+
89
+ ```typescript
90
+ function unchecked(a: number, b: number): i32 {
91
+ return (a + b) as i32;
92
+ }
93
+ ```
94
+
95
+ should be transformed to:
96
+
97
+ ```typescript
98
+ function unchecked(a: number, b: number): number {
99
+ return a + b;
100
+ }
101
+ ```
102
+
103
+ Here the operands are plain numbers, so the assertion does not add `| 0`. Use a
104
+ checked cast when boundary coercion is required:
105
+
106
+ ```typescript
107
+ function checked(a: number, b: number): i32 {
108
+ return i32(a + b);
109
+ }
110
+ ```
111
+
112
+ should be transformed to:
113
+
114
+ ```typescript
115
+ function checked(a: number, b: number): number {
116
+ return (a + b) | 0;
117
+ }
118
+ ```
119
+
120
+ Typed operations close ordinary domain operands before the operation and close
121
+ the result after the operation. Unchecked assertion operands are trusted as-is:
122
+ they choose the operation domain but do not receive an operand coercion from the
123
+ assertion itself. For nested expressions, every typed operation result is closed
124
+ at its own operator boundary.
125
+
126
+ The coercion in `i32(0.1)` comes from the checked cast call. The final coercion
127
+ in `(a: i32) + (b: i32)` comes from the `+` operation being dispatched as an
128
+ `i32` operation.
129
+
130
+ The transformer must track numeric domains separately from TypeScript's own
131
+ expression types. TypeScript widens arithmetic over branded numbers back to
132
+ plain `number`, so the transformer cannot use `checker.getTypeAtLocation` alone
133
+ to decide whether an expression is still `i32`, `u32`, `f32`, or `f64`.
134
+
135
+ That TypeScript widening must not silently escape into inferred storage. For
136
+ example, `const x = a + b` with `a: i32` and `b: i32` is a transformer error
137
+ because the inferred storage type of `x` is plain `number`. If the user really
138
+ wants to leave the numtypes domain, the escape must be explicit. Assertions to
139
+ non-numtypes types such as `number`, `unknown`, or `any` are explicit escapes:
140
+
141
+ ```typescript
142
+ const x = (a + b) as number;
143
+ const y = (a + b) as unknown;
144
+ const z = (a + b) as any;
145
+ ```
146
+
147
+ The inner `a + b` operation is still lowered as an `i32` operation, but the
148
+ result is allowed to leave domain storage because the escape is explicit in the
149
+ source.
150
+
151
+ Non-null assertions and `satisfies` clauses are not explicit escapes. They do
152
+ not change the value domain and must not suppress domain-leak diagnostics:
153
+
154
+ ```typescript
155
+ const x = (a + b)!;
156
+ const y = (a + b) satisfies number;
157
+ ```
158
+
159
+ should both be compile errors unless the surrounding storage preserves the
160
+ numtypes domain or the expression is explicitly asserted to a non-numtypes type.
161
+
162
+ Because the public aliases are branded, TypeScript also rejects a raw arithmetic
163
+ result when it crosses back into branded storage. For example, `a / b` has
164
+ TypeScript type `number` even when both operands are `f64`, so `return a / b`
165
+ from a function declared as `(): f64` is a TypeScript error. Users must write a
166
+ checked cast at that boundary:
167
+
168
+ ```typescript
169
+ function x(a: f64, b: f64): f64 {
170
+ return f64(a / b);
171
+ }
172
+ ```
173
+
174
+ should be transformed to:
175
+
176
+ ```typescript
177
+ function x(a: number, b: number): number {
178
+ return +((+a) / (+b));
179
+ }
180
+ ```
181
+
182
+ This rule applies to every branded storage boundary: annotated variables,
183
+ assignments, object properties, array elements, call arguments, and returns. A
184
+ typed operation may still be lowered internally, but the user source must be
185
+ valid TypeScript before the transformer runs. A checked cast call is the normal
186
+ way to turn a `number`-typed operation result back into a branded TypeScript
187
+ value.
188
+
189
+ An unchecked assertion such as `(a / b) as f64` may satisfy TypeScript, but it
190
+ does not perform the boundary coercion. Use it only when the user intentionally
191
+ asserts that no runtime coercion should be inserted at that boundary.
192
+
193
+ A checked cast call is a boundary conversion, not a free contextual domain for
194
+ every nested operation. The argument expression is still analyzed normally. A
195
+ plain `number` expression can be cast directly, but a mixed operation involving
196
+ branded operands and plain `number` operands must cast the plain operand first.
197
+
198
+ ```typescript
199
+ declare const a: i32;
200
+ declare const b: number;
201
+ declare function read(): number;
202
+
203
+ i32(read()); // ok: whole plain-number expression is cast
204
+ i32(a + b); // error: i32 and number are mixed inside the operation
205
+ i32(a + i32(b)); // ok
206
+ ```
207
+
208
+ ### Diagnostic Ownership
209
+
210
+ Examples in this document use "compile error" for both TypeScript diagnostics
211
+ and numtypes diagnostics. The ownership rule is:
212
+
213
+ - TypeScript diagnostics are authoritative for ordinary branded assignability.
214
+ For example, `return a / b` from a function returning `f64` is rejected by
215
+ TypeScript before the transformer can make it valid.
216
+ - Numtypes diagnostics cover transformer-specific semantics that TypeScript
217
+ cannot express, such as domain leaks into inferred `number` storage, ambiguous
218
+ numeric unions, mixed numeric domains, and erased cast misuse.
219
+ - If both can apply, the source should be fixed at the TypeScript boundary first,
220
+ usually by adding a checked cast such as `f64(a / b)`.
221
+
222
+ When a checked cast call converts from one branded type to another, the source
223
+ value is closed first, then the target coercion is applied. For example,
224
+ `i32(a: f32)` lowers as `Math.fround(a) | 0`, not just `a | 0`.
225
+
226
+ When an unchecked assertion changes the apparent domain, the assertion itself
227
+ does not close the source value. For example, `a as i32` lowers as `a`. If that
228
+ asserted value later participates in an `i32` operation, it chooses the `i32`
229
+ operation and the operation result is closed, but the asserted operand itself is
230
+ still emitted as-is.
231
+
232
+ ## Declaration Type Surface
233
+
234
+ JavaScript emit always erases the runtime representation to `number`. Declaration
235
+ emit is configurable because projects have different public API goals.
236
+
237
+ The transformer option is:
238
+
239
+ ```typescript
240
+ interface NumtypesTransformerOptions {
241
+ declarationTypes?: "preserve" | "erase";
242
+ }
243
+
244
+ interface NumtypesSourceTransformerOptions {
245
+ declarationTypes?: "preserve";
246
+ }
247
+ ```
248
+
249
+ The default is `"preserve"`. In this mode, generated `.d.ts` files keep
250
+ `i32`, `u32`, `f32`, and `f64` in the public type surface.
251
+
252
+ ```typescript
253
+ // input
254
+ export declare function read(value: i32): f32;
255
+ export declare const values: i32[];
256
+ export declare const mixed: i32 | f32;
257
+ ```
258
+
259
+ should emit declarations like:
260
+
261
+ ```typescript
262
+ export declare function read(value: i32): f32;
263
+ export declare const values: i32[];
264
+ export declare const mixed: i32 | f32;
265
+ ```
266
+
267
+ When `declarationTypes` is `"erase"`, generated `.d.ts` files replace every
268
+ numtypes type reference with `number`, replace numtypes cast-function value type
269
+ queries with the erased cast signature `(value: number) => number`, remove
270
+ now-unused numtypes imports, and collapse duplicate numeric union members.
271
+ Re-exports that expose numtypes symbols are removed from the declaration
272
+ surface. Re-exports from other modules are preserved even when their exported
273
+ names overlap with local numtypes import aliases.
274
+
275
+ Only references that still resolve to the imported numtypes type or cast marker
276
+ surface are erased. Local declarations that shadow imported names remain local
277
+ declarations. Value-side type queries such as `typeof i32`, `typeof nt.i32`, and
278
+ `typeof import("numtypes").i32` are erased so declaration emit does not keep a
279
+ numtypes import solely to describe erased cast marker values.
280
+
281
+ ```typescript
282
+ // input
283
+ export declare function read(value: i32): f32;
284
+ export declare const values: i32[];
285
+ export declare const maybe: i32 | null;
286
+ export declare const mixed: i32 | f32;
287
+ export type Cast = typeof import("numtypes").i32;
288
+ ```
289
+
290
+ should emit declarations like:
291
+
292
+ ```typescript
293
+ export declare function read(value: number): number;
294
+ export declare const values: number[];
295
+ export declare const maybe: number | null;
296
+ export declare const mixed: number;
297
+ export type Cast = (value: number) => number;
298
+ ```
299
+
300
+ Use `"preserve"` when downstream TypeScript users should see and enforce the
301
+ branded API. Use `"erase"` when the emitted declarations should expose only the
302
+ runtime JavaScript contract.
303
+
304
+ For declaration emit, use the combined transformer factory so the before
305
+ transformer and declaration transformer are both installed:
306
+
307
+ ```typescript
308
+ import { createTransformers } from "numtypes/transformer";
309
+
310
+ program.emit(
311
+ undefined,
312
+ writeFile,
313
+ undefined,
314
+ false,
315
+ createTransformers(program, { declarationTypes: "erase" })
316
+ );
317
+ ```
318
+
319
+ Calling `createTransformer(program, { declarationTypes: "erase" })` is a
320
+ TypeScript type error and a runtime configuration error because a source
321
+ transformer cannot rewrite declaration emit. Use `createTransformers()` for full
322
+ emit integration.
323
+
324
+ `optimizeTypedArrayElementAccess` defaults to `false`. When enabled, the
325
+ optimization pass may remove generated coercions around numeric TypedArray
326
+ element reads only when it proves a matching loop bounds check. This option is a
327
+ runtime trust boundary: code typed as `Float64Array`, `Int32Array`, and other
328
+ numeric TypedArrays must actually receive those intrinsic objects for the
329
+ optimized region. See `docs/lowering-optimization-spec.md` for the exact proof
330
+ rules.
331
+
332
+ ## Container And Generic Type Arguments
333
+
334
+ Numtypes annotations are allowed inside container and generic type syntax. This
335
+ includes array shorthand, generic array forms, tuples, object properties, and
336
+ application-specific generic types.
337
+
338
+ ```typescript
339
+ type Values = i32[];
340
+ type ReadonlyValues = ReadonlyArray<i32>;
341
+ type Pair = [i32, f32];
342
+ type Boxed = Box<i32>;
343
+ type Lookup = Map<string, i32>;
344
+ type Deferred = Promise<i32>;
345
+ ```
346
+
347
+ The transformer may use these type surfaces for declaration preservation/erasure
348
+ and for direct storage boundaries that TypeScript exposes, such as object
349
+ properties or array element assignments. It does not assign special runtime
350
+ semantics to arbitrary generic types or methods. `Promise<i32>` does not mean the
351
+ transformer traces promise resolution, and `Map<string, i32>` does not mean the
352
+ transformer interprets `Map.prototype.set`.
353
+
354
+ Call and method boundaries are governed by their TypeScript signatures. When a
355
+ number-typed operation result crosses into a branded generic slot, the user must
356
+ write a checked cast at that boundary.
357
+
358
+ ```typescript
359
+ function arrayPush(a: i32, b: i32, values: i32[]): void {
360
+ values.push(a + b);
361
+ }
362
+ ```
363
+
364
+ is rejected by TypeScript because `a + b` has TypeScript type `number`.
365
+
366
+ ```typescript
367
+ function arrayPush(a: i32, b: i32, values: i32[]): void {
368
+ values.push(i32(a + b));
369
+ }
370
+ ```
371
+
372
+ should be transformed to:
373
+
374
+ ```typescript
375
+ function arrayPush(a: number, b: number, values: number[]): void {
376
+ values.push(((a | 0) + (b | 0)) | 0);
377
+ }
378
+ ```
379
+
380
+ The same rule applies to user generics and standard library generics.
381
+
382
+ ```typescript
383
+ declare function resolveI32(value: i32): Promise<i32>;
384
+
385
+ function x(a: i32, b: i32): Promise<i32> {
386
+ return resolveI32(i32(a + b));
387
+ }
388
+ ```
389
+
390
+ The transformer should not infer an implicit cast merely because a generic type
391
+ argument contains a numtypes domain.
392
+
393
+ When a domain-bound operation result crosses into a plain `number` call slot, the
394
+ escape must also be explicit. This includes rest parameters and standard library
395
+ methods whose parameter is a rest `number[]` element.
396
+
397
+ ```typescript
398
+ declare function take(...values: number[]): void;
399
+
400
+ function x(a: i32, b: i32, values: number[]): void {
401
+ take(a + b);
402
+ values.push(a + b);
403
+ }
404
+ ```
405
+
406
+ should be a compile error:
407
+
408
+ ```bash
409
+ error: result of i32 operation is passed to number parameter without explicit cast
410
+ ```
411
+
412
+ The same rule applies when the call slot is a union containing a plain `number`
413
+ branch, such as `number | i32`. The plain `number` branch would otherwise allow a
414
+ domain-bound operation result to leave the numtypes domain implicitly.
415
+
416
+ Use an explicit non-numtypes escape when plain number storage is intended.
417
+
418
+ ```typescript
419
+ declare function take(...values: number[]): void;
420
+
421
+ function x(a: i32, b: i32, values: number[]): void {
422
+ take((a + b) as number);
423
+ values.push((a + b) as number);
424
+ }
425
+ ```
426
+
427
+ ```typescript
428
+ function x(a: i32, b: i32, c: i32): i32 {
429
+ return i32(a + b + c);
430
+ }
431
+ ```
432
+
433
+ should be transformed to:
434
+
435
+ ```typescript
436
+ function x(a: number, b: number, c: number): number {
437
+ return ((((a | 0) + (b | 0)) | 0) + (c | 0)) | 0;
438
+ }
439
+ ```
440
+
441
+ The transformer should reject implicit mixing between branded numeric types and
442
+ plain `number` values. Use an explicit constructor cast when a domain-changing
443
+ conversion is intended.
444
+
445
+ Numeric literals may be contextually coerced by the transformer only when the
446
+ target domain is already known from a checked cast call or from a typed
447
+ operation. A declaration annotation alone does not make `const x: i32 = 1`
448
+ valid TypeScript; use `const x: i32 = i32(1)`. Plain `number` variables must be
449
+ explicitly cast.
450
+
451
+ ```typescript
452
+ function x(a: i32, b: number): i32 {
453
+ return a + b;
454
+ }
455
+ ```
456
+
457
+ should be a compile error:
458
+
459
+ ```bash
460
+ error: cannot add i32 and number without explicit cast
461
+ ```
462
+
463
+ ```typescript
464
+ function x(a: i32, b: number): i32 {
465
+ return i32(a + i32(b));
466
+ }
467
+ ```
468
+
469
+ should be transformed to:
470
+
471
+ ```typescript
472
+ function x(a: number, b: number): number {
473
+ return ((a | 0) + (b | 0)) | 0;
474
+ }
475
+ ```
476
+
477
+ ## i32
478
+
479
+ An `i32` expression is closed by `| 0`. Arithmetic multiplication must use
480
+ `Math.imul` when both operands are in the i32/u32 integer domain. Rewriting
481
+ integer multiplication to normal `*` is not equivalent for all 32-bit values.
482
+
483
+ ### i32 Casts
484
+
485
+ Checked cast calls perform the `ToInt32` coercion at the call boundary.
486
+
487
+ ```typescript
488
+ function x(a: number): i32 {
489
+ return i32(a);
490
+ }
491
+ ```
492
+
493
+ should be transformed to:
494
+
495
+ ```typescript
496
+ function x(a: number): number {
497
+ return a | 0;
498
+ }
499
+ ```
500
+
501
+ ```typescript
502
+ function x(): i32 {
503
+ return i32(1.2);
504
+ }
505
+ ```
506
+
507
+ should be transformed to:
508
+
509
+ ```typescript
510
+ function x(): number {
511
+ return 1.2 | 0;
512
+ }
513
+ ```
514
+
515
+ Unchecked assertions do not perform that coercion.
516
+
517
+ ```typescript
518
+ function x(): i32 {
519
+ return 1.2 as i32;
520
+ }
521
+ ```
522
+
523
+ should be transformed to:
524
+
525
+ ```typescript
526
+ function x(): number {
527
+ return 1.2;
528
+ }
529
+ ```
530
+
531
+ Asserted operands choose the later operation domain, but the operands themselves
532
+ remain unchecked. TypeScript assertion syntax is erased, but parentheses around
533
+ the asserted operand are ordinary expression wrappers and may be preserved by the
534
+ printer.
535
+
536
+ ```typescript
537
+ function x(): i32 {
538
+ return i32((0.1 as i32) + (0.2 as i32));
539
+ }
540
+ ```
541
+
542
+ should be transformed to:
543
+
544
+ ```typescript
545
+ function x(): number {
546
+ return ((0.1) + (0.2)) | 0;
547
+ }
548
+ ```
549
+
550
+ ### i32 Unary Arithmetic
551
+
552
+ ```typescript
553
+ function plus(a: i32): i32 {
554
+ return i32(+a);
555
+ }
556
+
557
+ function neg(a: i32): i32 {
558
+ return i32(-a);
559
+ }
560
+ ```
561
+
562
+ should be transformed to:
563
+
564
+ ```typescript
565
+ function plus(a: number): number {
566
+ return (+(a | 0)) | 0;
567
+ }
568
+
569
+ function neg(a: number): number {
570
+ return (-(a | 0)) | 0;
571
+ }
572
+ ```
573
+
574
+ ### i32 Binary Arithmetic
575
+
576
+ The binary arithmetic operators `+`, `-`, `*`, `/`, `%`, and `**` are closed as
577
+ follows:
578
+
579
+ ```typescript
580
+ function add(a: i32, b: i32): i32 {
581
+ return i32(a + b);
582
+ }
583
+
584
+ function sub(a: i32, b: i32): i32 {
585
+ return i32(a - b);
586
+ }
587
+
588
+ function mul(a: i32, b: i32): i32 {
589
+ return i32(a * b);
590
+ }
591
+
592
+ function div(a: i32, b: i32): i32 {
593
+ return i32(a / b);
594
+ }
595
+
596
+ function rem(a: i32, b: i32): i32 {
597
+ return i32(a % b);
598
+ }
599
+
600
+ function pow(a: i32, b: i32): i32 {
601
+ return i32(a ** b);
602
+ }
603
+ ```
604
+
605
+ should be transformed to:
606
+
607
+ ```typescript
608
+ function add(a: number, b: number): number {
609
+ return ((a | 0) + (b | 0)) | 0;
610
+ }
611
+
612
+ function sub(a: number, b: number): number {
613
+ return ((a | 0) - (b | 0)) | 0;
614
+ }
615
+
616
+ function mul(a: number, b: number): number {
617
+ return Math.imul(a | 0, b | 0) | 0;
618
+ }
619
+
620
+ function div(a: number, b: number): number {
621
+ return ((a | 0) / (b | 0)) | 0;
622
+ }
623
+
624
+ function rem(a: number, b: number): number {
625
+ return ((a | 0) % (b | 0)) | 0;
626
+ }
627
+
628
+ function pow(a: number, b: number): number {
629
+ return ((a | 0) ** (b | 0)) | 0;
630
+ }
631
+ ```
632
+
633
+ `/`, `%`, and `**` are closed by final `ToInt32` coercion. They do not imply a
634
+ native int32 division, remainder, or exponentiation instruction. Their runtime
635
+ semantics remain JavaScript number semantics followed by `| 0`.
636
+
637
+ ### i32 Nested Arithmetic
638
+
639
+ ```typescript
640
+ function x(a: i32, b: i32, c: i32, d: i32): i32 {
641
+ return i32(a + b * c - d);
642
+ }
643
+ ```
644
+
645
+ should be transformed to:
646
+
647
+ ```typescript
648
+ function x(a: number, b: number, c: number, d: number): number {
649
+ return ((((a | 0) + (Math.imul(b | 0, c | 0) | 0)) | 0) - (d | 0)) | 0;
650
+ }
651
+ ```
652
+
653
+ ### i32 With Numeric Literals
654
+
655
+ Integer literals can be contextually coerced to `i32`.
656
+
657
+ ```typescript
658
+ function x(a: i32): i32 {
659
+ return i32(a + 1);
660
+ }
661
+ ```
662
+
663
+ should be transformed to:
664
+
665
+ ```typescript
666
+ function x(a: number): number {
667
+ return ((a | 0) + (1 | 0)) | 0;
668
+ }
669
+ ```
670
+
671
+ Fractional literals should require an explicit cast when the target is an
672
+ integer domain.
673
+
674
+ ```typescript
675
+ function x(a: i32): i32 {
676
+ return a * 1.2;
677
+ }
678
+ ```
679
+
680
+ should be a compile error:
681
+
682
+ ```bash
683
+ error: cannot multiply i32 by number without explicit cast
684
+ ```
685
+
686
+ ```typescript
687
+ function x(a: i32): i32 {
688
+ return i32(a * i32(1.2));
689
+ }
690
+ ```
691
+
692
+ should be transformed to:
693
+
694
+ ```typescript
695
+ function x(a: number): number {
696
+ return Math.imul(a | 0, 1.2 | 0) | 0;
697
+ }
698
+ ```
699
+
700
+ ### i32 Explicit Promotion
701
+
702
+ ```typescript
703
+ function x(a: i32, b: i32): f64 {
704
+ return f64(f64(a) + f64(b) * 1.2);
705
+ }
706
+ ```
707
+
708
+ should be transformed to:
709
+
710
+ ```typescript
711
+ function x(a: number, b: number): number {
712
+ return +((+(a | 0)) + (+((+(b | 0)) * (+1.2))));
713
+ }
714
+ ```
715
+
716
+ The `a | 0` and `b | 0` coercions preserve the source argument type. The `+`
717
+ coercions then promote the values into the f64 expression domain.
718
+
719
+ ### i32 Bitwise Operators
720
+
721
+ Bitwise operators are already int32-oriented in JavaScript. The transformer
722
+ still closes the result to the declared target type.
723
+
724
+ ```typescript
725
+ function band(a: i32, b: i32): i32 {
726
+ return i32(a & b);
727
+ }
728
+
729
+ function bor(a: i32, b: i32): i32 {
730
+ return i32(a | b);
731
+ }
732
+
733
+ function bxor(a: i32, b: i32): i32 {
734
+ return i32(a ^ b);
735
+ }
736
+
737
+ function shl(a: i32, b: i32): i32 {
738
+ return i32(a << b);
739
+ }
740
+
741
+ function shr(a: i32, b: i32): i32 {
742
+ return i32(a >> b);
743
+ }
744
+
745
+ function ushr(a: i32, b: i32): i32 {
746
+ return i32(a >>> b);
747
+ }
748
+ ```
749
+
750
+ should be transformed to:
751
+
752
+ ```typescript
753
+ function band(a: number, b: number): number {
754
+ return ((a | 0) & (b | 0)) | 0;
755
+ }
756
+
757
+ function bor(a: number, b: number): number {
758
+ return ((a | 0) | (b | 0)) | 0;
759
+ }
760
+
761
+ function bxor(a: number, b: number): number {
762
+ return ((a | 0) ^ (b | 0)) | 0;
763
+ }
764
+
765
+ function shl(a: number, b: number): number {
766
+ return ((a | 0) << (b | 0)) | 0;
767
+ }
768
+
769
+ function shr(a: number, b: number): number {
770
+ return ((a | 0) >> (b | 0)) | 0;
771
+ }
772
+
773
+ function ushr(a: number, b: number): number {
774
+ return ((a | 0) >>> (b | 0)) | 0;
775
+ }
776
+ ```
777
+
778
+ `>>>` naturally produces a uint32 bit pattern. If the declared target is `i32`,
779
+ the final `| 0` interprets that bit pattern as signed int32.
780
+
781
+ ## u32
782
+
783
+ A `u32` expression is closed by `>>> 0`. Arithmetic multiplication should use
784
+ `Math.imul`, then reinterpret the low 32 bits with `>>> 0`.
785
+
786
+ ### u32 Casts
787
+
788
+ ```typescript
789
+ function x(a: number): u32 {
790
+ return u32(a);
791
+ }
792
+ ```
793
+
794
+ should be transformed to:
795
+
796
+ ```typescript
797
+ function x(a: number): number {
798
+ return a >>> 0;
799
+ }
800
+ ```
801
+
802
+ ```typescript
803
+ function x(): u32 {
804
+ return u32(-1);
805
+ }
806
+ ```
807
+
808
+ should be transformed to:
809
+
810
+ ```typescript
811
+ function x(): number {
812
+ return (-1) >>> 0;
813
+ }
814
+ ```
815
+
816
+ ### u32 Unary Arithmetic
817
+
818
+ ```typescript
819
+ function plus(a: u32): u32 {
820
+ return u32(+a);
821
+ }
822
+
823
+ function neg(a: u32): u32 {
824
+ return u32(-a);
825
+ }
826
+ ```
827
+
828
+ should be transformed to:
829
+
830
+ ```typescript
831
+ function plus(a: number): number {
832
+ return (+(a >>> 0)) >>> 0;
833
+ }
834
+
835
+ function neg(a: number): number {
836
+ return (-(a >>> 0)) >>> 0;
837
+ }
838
+ ```
839
+
840
+ ### u32 Binary Arithmetic
841
+
842
+ ```typescript
843
+ function add(a: u32, b: u32): u32 {
844
+ return u32(a + b);
845
+ }
846
+
847
+ function sub(a: u32, b: u32): u32 {
848
+ return u32(a - b);
849
+ }
850
+
851
+ function mul(a: u32, b: u32): u32 {
852
+ return u32(a * b);
853
+ }
854
+
855
+ function div(a: u32, b: u32): u32 {
856
+ return u32(a / b);
857
+ }
858
+
859
+ function rem(a: u32, b: u32): u32 {
860
+ return u32(a % b);
861
+ }
862
+
863
+ function pow(a: u32, b: u32): u32 {
864
+ return u32(a ** b);
865
+ }
866
+ ```
867
+
868
+ should be transformed to:
869
+
870
+ ```typescript
871
+ function add(a: number, b: number): number {
872
+ return ((a >>> 0) + (b >>> 0)) >>> 0;
873
+ }
874
+
875
+ function sub(a: number, b: number): number {
876
+ return ((a >>> 0) - (b >>> 0)) >>> 0;
877
+ }
878
+
879
+ function mul(a: number, b: number): number {
880
+ return Math.imul(a >>> 0, b >>> 0) >>> 0;
881
+ }
882
+
883
+ function div(a: number, b: number): number {
884
+ return ((a >>> 0) / (b >>> 0)) >>> 0;
885
+ }
886
+
887
+ function rem(a: number, b: number): number {
888
+ return ((a >>> 0) % (b >>> 0)) >>> 0;
889
+ }
890
+
891
+ function pow(a: number, b: number): number {
892
+ return ((a >>> 0) ** (b >>> 0)) >>> 0;
893
+ }
894
+ ```
895
+
896
+ As with `i32`, `/`, `%`, and `**` are JavaScript number operations followed by
897
+ `ToUint32` coercion.
898
+
899
+ ### u32 Nested Arithmetic
900
+
901
+ ```typescript
902
+ function x(a: u32, b: u32, c: u32, d: u32): u32 {
903
+ return u32(a + b * c - d);
904
+ }
905
+ ```
906
+
907
+ should be transformed to:
908
+
909
+ ```typescript
910
+ function x(a: number, b: number, c: number, d: number): number {
911
+ return ((((a >>> 0) + (Math.imul(b >>> 0, c >>> 0) >>> 0)) >>> 0) - (d >>> 0)) >>> 0;
912
+ }
913
+ ```
914
+
915
+ ### u32 With Numeric Literals
916
+
917
+ Integer literals in the uint32 range can be contextually coerced to `u32`.
918
+
919
+ ```typescript
920
+ function x(a: u32): u32 {
921
+ return u32(a + 1);
922
+ }
923
+ ```
924
+
925
+ should be transformed to:
926
+
927
+ ```typescript
928
+ function x(a: number): number {
929
+ return ((a >>> 0) + (1 >>> 0)) >>> 0;
930
+ }
931
+ ```
932
+
933
+ Negative or fractional literals should require an explicit cast in a `u32`
934
+ expression.
935
+
936
+ ```typescript
937
+ function x(a: u32): u32 {
938
+ return a + -1;
939
+ }
940
+ ```
941
+
942
+ should be a compile error:
943
+
944
+ ```bash
945
+ error: cannot add u32 and number without explicit cast
946
+ ```
947
+
948
+ ```typescript
949
+ function x(a: u32): u32 {
950
+ return u32(a + u32(-1));
951
+ }
952
+ ```
953
+
954
+ should be transformed to:
955
+
956
+ ```typescript
957
+ function x(a: number): number {
958
+ return ((a >>> 0) + ((-1) >>> 0)) >>> 0;
959
+ }
960
+ ```
961
+
962
+ ### u32 Explicit Promotion
963
+
964
+ ```typescript
965
+ function x(a: u32, b: u32): f32 {
966
+ return f32(f32(a) / f32(b));
967
+ }
968
+ ```
969
+
970
+ should be transformed to:
971
+
972
+ ```typescript
973
+ function x(a: number, b: number): number {
974
+ return Math.fround(Math.fround(a >>> 0) / Math.fround(b >>> 0));
975
+ }
976
+ ```
977
+
978
+ ### u32 Bitwise Operators
979
+
980
+ ```typescript
981
+ function band(a: u32, b: u32): u32 {
982
+ return u32(a & b);
983
+ }
984
+
985
+ function bor(a: u32, b: u32): u32 {
986
+ return u32(a | b);
987
+ }
988
+
989
+ function bxor(a: u32, b: u32): u32 {
990
+ return u32(a ^ b);
991
+ }
992
+
993
+ function shl(a: u32, b: u32): u32 {
994
+ return u32(a << b);
995
+ }
996
+
997
+ function shr(a: u32, b: u32): u32 {
998
+ return u32(a >> b);
999
+ }
1000
+
1001
+ function ushr(a: u32, b: u32): u32 {
1002
+ return u32(a >>> b);
1003
+ }
1004
+ ```
1005
+
1006
+ should be transformed to:
1007
+
1008
+ ```typescript
1009
+ function band(a: number, b: number): number {
1010
+ return ((a >>> 0) & (b >>> 0)) >>> 0;
1011
+ }
1012
+
1013
+ function bor(a: number, b: number): number {
1014
+ return ((a >>> 0) | (b >>> 0)) >>> 0;
1015
+ }
1016
+
1017
+ function bxor(a: number, b: number): number {
1018
+ return ((a >>> 0) ^ (b >>> 0)) >>> 0;
1019
+ }
1020
+
1021
+ function shl(a: number, b: number): number {
1022
+ return ((a >>> 0) << (b >>> 0)) >>> 0;
1023
+ }
1024
+
1025
+ function shr(a: number, b: number): number {
1026
+ return ((a >>> 0) >> (b >>> 0)) >>> 0;
1027
+ }
1028
+
1029
+ function ushr(a: number, b: number): number {
1030
+ return ((a >>> 0) >>> (b >>> 0)) >>> 0;
1031
+ }
1032
+ ```
1033
+
1034
+ `>>` is signed arithmetic right shift followed by uint32 reinterpretation. Use
1035
+ `>>>` for logical uint32 right shift.
1036
+
1037
+ ## f32
1038
+
1039
+ An `f32` expression is closed by `Math.fround`. The JavaScript operation itself
1040
+ still executes in the Number domain unless the engine recognizes the fround
1041
+ pattern and lowers it.
1042
+
1043
+ ### f32 Casts
1044
+
1045
+ ```typescript
1046
+ function x(a: number): f32 {
1047
+ return f32(a);
1048
+ }
1049
+ ```
1050
+
1051
+ should be transformed to:
1052
+
1053
+ ```typescript
1054
+ function x(a: number): number {
1055
+ return Math.fround(a);
1056
+ }
1057
+ ```
1058
+
1059
+ ```typescript
1060
+ function x(): f32 {
1061
+ return f32(1.2);
1062
+ }
1063
+ ```
1064
+
1065
+ should be transformed to:
1066
+
1067
+ ```typescript
1068
+ function x(): number {
1069
+ return Math.fround(1.2);
1070
+ }
1071
+ ```
1072
+
1073
+ ### f32 Unary Arithmetic
1074
+
1075
+ ```typescript
1076
+ function plus(a: f32): f32 {
1077
+ return f32(+a);
1078
+ }
1079
+
1080
+ function neg(a: f32): f32 {
1081
+ return f32(-a);
1082
+ }
1083
+ ```
1084
+
1085
+ should be transformed to:
1086
+
1087
+ ```typescript
1088
+ function plus(a: number): number {
1089
+ return Math.fround(+Math.fround(a));
1090
+ }
1091
+
1092
+ function neg(a: number): number {
1093
+ return Math.fround(-Math.fround(a));
1094
+ }
1095
+ ```
1096
+
1097
+ ### f32 Binary Arithmetic
1098
+
1099
+ ```typescript
1100
+ function add(a: f32, b: f32): f32 {
1101
+ return f32(a + b);
1102
+ }
1103
+
1104
+ function sub(a: f32, b: f32): f32 {
1105
+ return f32(a - b);
1106
+ }
1107
+
1108
+ function mul(a: f32, b: f32): f32 {
1109
+ return f32(a * b);
1110
+ }
1111
+
1112
+ function div(a: f32, b: f32): f32 {
1113
+ return f32(a / b);
1114
+ }
1115
+
1116
+ function rem(a: f32, b: f32): f32 {
1117
+ return f32(a % b);
1118
+ }
1119
+
1120
+ function pow(a: f32, b: f32): f32 {
1121
+ return f32(a ** b);
1122
+ }
1123
+ ```
1124
+
1125
+ should be transformed to:
1126
+
1127
+ ```typescript
1128
+ function add(a: number, b: number): number {
1129
+ return Math.fround(Math.fround(a) + Math.fround(b));
1130
+ }
1131
+
1132
+ function sub(a: number, b: number): number {
1133
+ return Math.fround(Math.fround(a) - Math.fround(b));
1134
+ }
1135
+
1136
+ function mul(a: number, b: number): number {
1137
+ return Math.fround(Math.fround(a) * Math.fround(b));
1138
+ }
1139
+
1140
+ function div(a: number, b: number): number {
1141
+ return Math.fround(Math.fround(a) / Math.fround(b));
1142
+ }
1143
+
1144
+ function rem(a: number, b: number): number {
1145
+ return Math.fround(Math.fround(a) % Math.fround(b));
1146
+ }
1147
+
1148
+ function pow(a: number, b: number): number {
1149
+ return Math.fround(Math.fround(a) ** Math.fround(b));
1150
+ }
1151
+ ```
1152
+
1153
+ `**` remains JavaScript exponentiation followed by f32 rounding. It should be
1154
+ considered closed over the f32 branded type, but not guaranteed to lower to a
1155
+ native f32 pow instruction.
1156
+
1157
+ ### f32 Nested Arithmetic
1158
+
1159
+ ```typescript
1160
+ function x(a: f32, b: f32, c: f32, d: f32): f32 {
1161
+ return f32(a + b * c - d);
1162
+ }
1163
+ ```
1164
+
1165
+ should be transformed to:
1166
+
1167
+ ```typescript
1168
+ function x(a: number, b: number, c: number, d: number): number {
1169
+ return Math.fround(
1170
+ Math.fround(
1171
+ Math.fround(a) +
1172
+ Math.fround(Math.fround(b) * Math.fround(c))
1173
+ ) -
1174
+ Math.fround(d)
1175
+ );
1176
+ }
1177
+ ```
1178
+
1179
+ A minifier may format this as a single expression. The important property is
1180
+ that every typed operation result is wrapped with `Math.fround`.
1181
+
1182
+ ### f32 With Numeric Literals
1183
+
1184
+ Numeric literals can be contextually coerced to `f32`.
1185
+
1186
+ ```typescript
1187
+ function x(a: f32): f32 {
1188
+ return f32(a * 1.2);
1189
+ }
1190
+ ```
1191
+
1192
+ should be transformed to:
1193
+
1194
+ ```typescript
1195
+ function x(a: number): number {
1196
+ return Math.fround(Math.fround(a) * Math.fround(1.2));
1197
+ }
1198
+ ```
1199
+
1200
+ Plain `number` variables still require explicit casts.
1201
+
1202
+ ```typescript
1203
+ function x(a: f32, b: number): f32 {
1204
+ return a * b;
1205
+ }
1206
+ ```
1207
+
1208
+ should be a compile error:
1209
+
1210
+ ```bash
1211
+ error: cannot multiply f32 by number without explicit cast
1212
+ ```
1213
+
1214
+ ```typescript
1215
+ function x(a: f32, b: number): f32 {
1216
+ return f32(a * f32(b));
1217
+ }
1218
+ ```
1219
+
1220
+ should be transformed to:
1221
+
1222
+ ```typescript
1223
+ function x(a: number, b: number): number {
1224
+ return Math.fround(Math.fround(a) * Math.fround(b));
1225
+ }
1226
+ ```
1227
+
1228
+ ### f32 Integer Promotion
1229
+
1230
+ ```typescript
1231
+ function x(a: i32, b: u32): f32 {
1232
+ return f32(f32(a) + f32(b));
1233
+ }
1234
+ ```
1235
+
1236
+ should be transformed to:
1237
+
1238
+ ```typescript
1239
+ function x(a: number, b: number): number {
1240
+ return Math.fround(Math.fround(a | 0) + Math.fround(b >>> 0));
1241
+ }
1242
+ ```
1243
+
1244
+ ### f32 Disallowed Bitwise Operators
1245
+
1246
+ Bitwise operators on `f32` should be rejected unless the value is explicitly
1247
+ converted to an integer type.
1248
+
1249
+ ```typescript
1250
+ function x(a: f32, b: f32): f32 {
1251
+ return a & b;
1252
+ }
1253
+ ```
1254
+
1255
+ should be a compile error:
1256
+
1257
+ ```bash
1258
+ error: cannot apply bitwise operator '&' to f32
1259
+ ```
1260
+
1261
+ ```typescript
1262
+ function x(a: f32, b: f32): i32 {
1263
+ return i32(i32(a) & i32(b));
1264
+ }
1265
+ ```
1266
+
1267
+ should be transformed to:
1268
+
1269
+ ```typescript
1270
+ function x(a: number, b: number): number {
1271
+ return ((Math.fround(a) | 0) & (Math.fround(b) | 0)) | 0;
1272
+ }
1273
+ ```
1274
+
1275
+ ## f64
1276
+
1277
+ An `f64` expression is closed by unary `+`. JavaScript Number arithmetic is
1278
+ already binary64, so `f64` primarily prevents accidental non-number inputs and
1279
+ documents the numeric domain in the type system.
1280
+
1281
+ ### f64 Casts
1282
+
1283
+ ```typescript
1284
+ function x(a: number): f64 {
1285
+ return f64(a);
1286
+ }
1287
+ ```
1288
+
1289
+ should be transformed to:
1290
+
1291
+ ```typescript
1292
+ function x(a: number): number {
1293
+ return +a;
1294
+ }
1295
+ ```
1296
+
1297
+ ```typescript
1298
+ function x(): f64 {
1299
+ return f64(1.2);
1300
+ }
1301
+ ```
1302
+
1303
+ should be transformed to:
1304
+
1305
+ ```typescript
1306
+ function x(): number {
1307
+ return +1.2;
1308
+ }
1309
+ ```
1310
+
1311
+ ### f64 Unary Arithmetic
1312
+
1313
+ ```typescript
1314
+ function plus(a: f64): f64 {
1315
+ return f64(+a);
1316
+ }
1317
+
1318
+ function neg(a: f64): f64 {
1319
+ return f64(-a);
1320
+ }
1321
+ ```
1322
+
1323
+ should be transformed to:
1324
+
1325
+ ```typescript
1326
+ function plus(a: number): number {
1327
+ return +(+a);
1328
+ }
1329
+
1330
+ function neg(a: number): number {
1331
+ return +(-(+a));
1332
+ }
1333
+ ```
1334
+
1335
+ ### f64 Binary Arithmetic
1336
+
1337
+ ```typescript
1338
+ function add(a: f64, b: f64): f64 {
1339
+ return f64(a + b);
1340
+ }
1341
+
1342
+ function sub(a: f64, b: f64): f64 {
1343
+ return f64(a - b);
1344
+ }
1345
+
1346
+ function mul(a: f64, b: f64): f64 {
1347
+ return f64(a * b);
1348
+ }
1349
+
1350
+ function div(a: f64, b: f64): f64 {
1351
+ return f64(a / b);
1352
+ }
1353
+
1354
+ function rem(a: f64, b: f64): f64 {
1355
+ return f64(a % b);
1356
+ }
1357
+
1358
+ function pow(a: f64, b: f64): f64 {
1359
+ return f64(a ** b);
1360
+ }
1361
+ ```
1362
+
1363
+ should be transformed to:
1364
+
1365
+ ```typescript
1366
+ function add(a: number, b: number): number {
1367
+ return +((+a) + (+b));
1368
+ }
1369
+
1370
+ function sub(a: number, b: number): number {
1371
+ return +((+a) - (+b));
1372
+ }
1373
+
1374
+ function mul(a: number, b: number): number {
1375
+ return +((+a) * (+b));
1376
+ }
1377
+
1378
+ function div(a: number, b: number): number {
1379
+ return +((+a) / (+b));
1380
+ }
1381
+
1382
+ function rem(a: number, b: number): number {
1383
+ return +((+a) % (+b));
1384
+ }
1385
+
1386
+ function pow(a: number, b: number): number {
1387
+ return +((+a) ** (+b));
1388
+ }
1389
+ ```
1390
+
1391
+ ### f64 Nested Arithmetic
1392
+
1393
+ ```typescript
1394
+ function x(a: f64, b: f64, c: f64, d: f64): f64 {
1395
+ return f64(a + b * c - d);
1396
+ }
1397
+ ```
1398
+
1399
+ should be transformed to:
1400
+
1401
+ ```typescript
1402
+ function x(a: number, b: number, c: number, d: number): number {
1403
+ return +((+((+a) + (+((+b) * (+c))))) - (+d));
1404
+ }
1405
+ ```
1406
+
1407
+ The extra unary `+` calls are intentionally redundant from a pure JavaScript
1408
+ runtime perspective. They keep the generated code shaped like the declared f64
1409
+ domain and prevent string concatenation from being introduced through untyped
1410
+ inputs.
1411
+
1412
+ ### f64 With Numeric Literals
1413
+
1414
+ Numeric literals can be contextually coerced to `f64`.
1415
+
1416
+ ```typescript
1417
+ function x(a: f64): f64 {
1418
+ return f64(a / 1.2);
1419
+ }
1420
+ ```
1421
+
1422
+ should be transformed to:
1423
+
1424
+ ```typescript
1425
+ function x(a: number): number {
1426
+ return +((+a) / (+1.2));
1427
+ }
1428
+ ```
1429
+
1430
+ Plain `number` variables require explicit casts.
1431
+
1432
+ ```typescript
1433
+ function x(a: f64, b: number): f64 {
1434
+ return a / b;
1435
+ }
1436
+ ```
1437
+
1438
+ should be a compile error:
1439
+
1440
+ ```bash
1441
+ error: cannot divide f64 by number without explicit cast
1442
+ ```
1443
+
1444
+ ```typescript
1445
+ function x(a: f64, b: number): f64 {
1446
+ return f64(a / f64(b));
1447
+ }
1448
+ ```
1449
+
1450
+ should be transformed to:
1451
+
1452
+ ```typescript
1453
+ function x(a: number, b: number): number {
1454
+ return +((+a) / (+b));
1455
+ }
1456
+ ```
1457
+
1458
+ ### f64 Integer And f32 Promotion
1459
+
1460
+ ```typescript
1461
+ function x(a: i32, b: u32, c: f32): f64 {
1462
+ return f64(f64(a) + f64(b) + f64(c));
1463
+ }
1464
+ ```
1465
+
1466
+ should be transformed to:
1467
+
1468
+ ```typescript
1469
+ function x(a: number, b: number, c: number): number {
1470
+ return +((+((+(a | 0)) + (+(b >>> 0)))) + (+Math.fround(c)));
1471
+ }
1472
+ ```
1473
+
1474
+ ### f64 Disallowed Bitwise Operators
1475
+
1476
+ Bitwise operators on `f64` should be rejected unless the value is explicitly
1477
+ converted to an integer type.
1478
+
1479
+ ```typescript
1480
+ function x(a: f64, b: f64): f64 {
1481
+ return a | b;
1482
+ }
1483
+ ```
1484
+
1485
+ should be a compile error:
1486
+
1487
+ ```bash
1488
+ error: cannot apply bitwise operator '|' to f64
1489
+ ```
1490
+
1491
+ ```typescript
1492
+ function x(a: f64, b: f64): u32 {
1493
+ return u32(u32(a) | u32(b));
1494
+ }
1495
+ ```
1496
+
1497
+ should be transformed to:
1498
+
1499
+ ```typescript
1500
+ function x(a: number, b: number): number {
1501
+ return (((+a) >>> 0) | ((+b) >>> 0)) >>> 0;
1502
+ }
1503
+ ```
1504
+
1505
+ ## Mixed Typed Arithmetic
1506
+
1507
+ Operations between different branded numeric domains should require an explicit
1508
+ cast. This keeps the transform predictable and prevents accidental precision or
1509
+ signedness changes.
1510
+
1511
+ ```typescript
1512
+ function x(a: i32, b: u32): i32 {
1513
+ return a + b;
1514
+ }
1515
+ ```
1516
+
1517
+ should be a compile error:
1518
+
1519
+ ```bash
1520
+ error: cannot add i32 and u32 without explicit cast
1521
+ ```
1522
+
1523
+ ```typescript
1524
+ function x(a: i32, b: u32): i32 {
1525
+ return i32(a + i32(b));
1526
+ }
1527
+ ```
1528
+
1529
+ should be transformed to:
1530
+
1531
+ ```typescript
1532
+ function x(a: number, b: number): number {
1533
+ return ((a | 0) + ((b >>> 0) | 0)) | 0;
1534
+ }
1535
+ ```
1536
+
1537
+ ```typescript
1538
+ function x(a: i32, b: u32): u32 {
1539
+ return u32(u32(a) + b);
1540
+ }
1541
+ ```
1542
+
1543
+ should be transformed to:
1544
+
1545
+ ```typescript
1546
+ function x(a: number, b: number): number {
1547
+ return (((a | 0) >>> 0) + (b >>> 0)) >>> 0;
1548
+ }
1549
+ ```
1550
+
1551
+ ```typescript
1552
+ function x(a: i32, b: f32): f32 {
1553
+ return f32(f32(a) + b);
1554
+ }
1555
+ ```
1556
+
1557
+ should be transformed to:
1558
+
1559
+ ```typescript
1560
+ function x(a: number, b: number): number {
1561
+ return Math.fround(Math.fround(a | 0) + Math.fround(b));
1562
+ }
1563
+ ```
1564
+
1565
+ ### Storage Unions And Operation Unions
1566
+
1567
+ Any union type that contains a numtypes domain is allowed as storage. This
1568
+ includes pure numtypes unions such as `i32 | f32`, nullable storage such as
1569
+ `i32 | null`, optional storage such as `i32 | undefined`, and mixed application
1570
+ unions such as `Promise<void> | i32`. The storage may be written explicitly, or
1571
+ it may be inferred by TypeScript when TypeScript preserves the branded union.
1572
+
1573
+ Storage acceptance does not make the union a single arithmetic domain. A union
1574
+ value containing a numtypes domain is not a valid operand for typed arithmetic,
1575
+ bitwise, unary arithmetic, or update operations until the user narrows it or
1576
+ uses an explicit cast.
1577
+
1578
+ ```typescript
1579
+ function x(a: i32 | u32, b: i32): i32 {
1580
+ return a + b;
1581
+ }
1582
+ ```
1583
+
1584
+ should be a compile error:
1585
+
1586
+ ```bash
1587
+ error: cannot add union containing i32 | u32; narrow or explicitly cast before the operation
1588
+ ```
1589
+
1590
+ Nullable storage is valid.
1591
+
1592
+ ```typescript
1593
+ function store(a: i32): i32 | null {
1594
+ const value: i32 | null = a;
1595
+ return value;
1596
+ }
1597
+ ```
1598
+
1599
+ should be transformed to:
1600
+
1601
+ ```typescript
1602
+ function store(a: number): number | null {
1603
+ const value: number | null = a | 0;
1604
+ return value;
1605
+ }
1606
+ ```
1607
+
1608
+ But nullable values must be narrowed or explicitly cast before arithmetic.
1609
+
1610
+ ```typescript
1611
+ function x(a: i32 | null, b: i32): i32 {
1612
+ return a + b;
1613
+ }
1614
+ ```
1615
+
1616
+ should be a compile error:
1617
+
1618
+ ```bash
1619
+ error: cannot add union containing i32; narrow or explicitly cast before the operation
1620
+ ```
1621
+
1622
+ TypeScript control-flow narrowing is respected. Once a nullable value has been
1623
+ narrowed to the branded member, it can participate in the branded operation.
1624
+
1625
+ ```typescript
1626
+ function x(a: i32 | null, b: i32): i32 {
1627
+ if (a != null) {
1628
+ return i32(a + b);
1629
+ }
1630
+
1631
+ return b;
1632
+ }
1633
+ ```
1634
+
1635
+ should be transformed to:
1636
+
1637
+ ```typescript
1638
+ function x(a: number | null, b: number): number {
1639
+ if (a != null) {
1640
+ return ((a | 0) + (b | 0)) | 0;
1641
+ }
1642
+
1643
+ return b | 0;
1644
+ }
1645
+ ```
1646
+
1647
+ This same operation rule applies to any mixed union containing a numtype.
1648
+
1649
+ ```typescript
1650
+ declare function read(): Promise<void> | i32;
1651
+
1652
+ function x(b: i32): i32 {
1653
+ return read() + b;
1654
+ }
1655
+ ```
1656
+
1657
+ should be a compile error:
1658
+
1659
+ ```bash
1660
+ error: cannot add union containing i32; narrow or explicitly cast before the operation
1661
+ ```
1662
+
1663
+ The user must narrow the value or use an explicit cast before the operation.
1664
+
1665
+ ```typescript
1666
+ function x(a: i32 | u32, b: i32): i32 {
1667
+ return i32(i32(a) + b);
1668
+ }
1669
+ ```
1670
+
1671
+ should be transformed to:
1672
+
1673
+ ```typescript
1674
+ function x(a: number, b: number): number {
1675
+ return ((a | 0) + (b | 0)) | 0;
1676
+ }
1677
+ ```
1678
+
1679
+ ### Conditional Numeric Unions
1680
+
1681
+ A conditional expression may produce a numeric-union expression when possible
1682
+ branches are closed over different numtypes domains, or when a closed numtypes
1683
+ branch is selected with a non-numtypes branch such as `null`, `undefined`, or an
1684
+ application object. The result must be stored in a matching union annotation,
1685
+ returned from a matching union return type, preserved by TypeScript's inferred
1686
+ storage type, or explicitly cast before it is used as a single-domain value.
1687
+
1688
+ ```typescript
1689
+ function x(cond: boolean, a: i32, b: f32) {
1690
+ const value = cond ? a : b * f32(1);
1691
+ return value;
1692
+ }
1693
+ ```
1694
+
1695
+ should be a compile error:
1696
+
1697
+ ```bash
1698
+ error: result of i32 | f32 expression is assigned to untyped variable 'value'; use a matching union annotation with checked casts for number-typed branches or explicitly escape to a non-numtypes type
1699
+ ```
1700
+
1701
+ The lvalue must preserve the union explicitly, and branches whose TypeScript
1702
+ expression type widens to `number` must be closed with checked casts.
1703
+
1704
+ ```typescript
1705
+ function x(cond: boolean, a: i32, b: f32): i32 | f32 {
1706
+ const value: i32 | f32 = cond ? a : f32(b * f32(1));
1707
+ return value;
1708
+ }
1709
+ ```
1710
+
1711
+ should be transformed to:
1712
+
1713
+ ```typescript
1714
+ function x(cond: boolean, a: number, b: number): number {
1715
+ const value: number = cond ? a | 0 : Math.fround(Math.fround(b) * Math.fround(1));
1716
+ return value;
1717
+ }
1718
+ ```
1719
+
1720
+ The transformer closes each branch independently. It must not apply one final
1721
+ coercion to the whole conditional, because `i32 | f32` is not one numeric
1722
+ domain.
1723
+
1724
+ A numeric-union conditional is still not a valid operand without narrowing or
1725
+ an explicit cast.
1726
+
1727
+ ```typescript
1728
+ function x(cond: boolean, a: i32, b: f32): i32 {
1729
+ return (cond ? a : b) + i32(1);
1730
+ }
1731
+ ```
1732
+
1733
+ should be a compile error:
1734
+
1735
+ ```bash
1736
+ error: cannot add union containing i32 | f32; narrow or explicitly cast before the operation
1737
+ ```
1738
+
1739
+ A conditional that selects nullable storage remains a union operand until it is
1740
+ narrowed. Even if every numtypes-bearing branch contains only `i32`, the
1741
+ presence of `null` means the whole conditional is not an `i32` operand.
1742
+
1743
+ ```typescript
1744
+ function x(cond: boolean, a: i32 | null, b: i32): i32 {
1745
+ return (cond ? a : b) + b;
1746
+ }
1747
+ ```
1748
+
1749
+ should be a compile error:
1750
+
1751
+ ```bash
1752
+ error: cannot add union containing i32; narrow or explicitly cast before the operation
1753
+ ```
1754
+
1755
+ When an operation result is unioned with a non-numtypes branch, TypeScript would
1756
+ infer a plain `number` union such as `number | null`. The transformer must treat
1757
+ that as a domain leak unless the lvalue preserves the branded member with a
1758
+ matching union annotation.
1759
+
1760
+ ```typescript
1761
+ function x(cond: boolean, a: i32, b: i32) {
1762
+ const value = cond ? a + b : null;
1763
+ return value;
1764
+ }
1765
+ ```
1766
+
1767
+ should be a compile error:
1768
+
1769
+ ```bash
1770
+ error: result of union containing i32 expression is assigned to untyped variable 'value'; use a matching union annotation with checked casts for number-typed branches or explicitly escape to a non-numtypes type
1771
+ ```
1772
+
1773
+ ```typescript
1774
+ function x(cond: boolean, a: i32, b: i32): i32 | null {
1775
+ const value: i32 | null = cond ? i32(a + b) : null;
1776
+ return value;
1777
+ }
1778
+ ```
1779
+
1780
+ should be transformed to:
1781
+
1782
+ ```typescript
1783
+ function x(cond: boolean, a: number, b: number): number | null {
1784
+ const value: number | null = cond ? ((a | 0) + (b | 0)) | 0 : null;
1785
+ return value;
1786
+ }
1787
+ ```
1788
+
1789
+ Plain `number` branches do not become part of a numeric union. They require an
1790
+ explicit cast to one of the union domains.
1791
+
1792
+ ```typescript
1793
+ function x(cond: boolean, a: i32, n: number): i32 | f32 {
1794
+ return cond ? a : n;
1795
+ }
1796
+ ```
1797
+
1798
+ should be a compile error:
1799
+
1800
+ ```bash
1801
+ error: cannot select i32 and number without explicit cast
1802
+ ```
1803
+
1804
+ The same rule applies when the numtypes branch is already stored in a union.
1805
+
1806
+ ```typescript
1807
+ function x(cond: boolean, a: i32 | null, n: number) {
1808
+ return cond ? a : n;
1809
+ }
1810
+ ```
1811
+
1812
+ should be a compile error:
1813
+
1814
+ ```bash
1815
+ error: cannot select union containing i32 and number without explicit cast
1816
+ ```
1817
+
1818
+ ```typescript
1819
+ function x(a: f32, b: f64): f64 {
1820
+ return f64(f64(a) + b);
1821
+ }
1822
+ ```
1823
+
1824
+ should be transformed to:
1825
+
1826
+ ```typescript
1827
+ function x(a: number, b: number): number {
1828
+ return +((+Math.fround(a)) + (+b));
1829
+ }
1830
+ ```
1831
+
1832
+ ## Assignments And Updates
1833
+
1834
+ When assigning to a typed local, parameter, property, or array element, the user
1835
+ source must still satisfy TypeScript's branded type system. If the right-hand
1836
+ side is a raw operation result, it must be wrapped in the checked cast for the
1837
+ target storage domain. The transformer then erases that checked cast into the
1838
+ closed operation.
1839
+
1840
+ ```typescript
1841
+ function x(a: i32, b: i32): i32 {
1842
+ let c: i32 = i32(a + b);
1843
+ c = i32(c * 2);
1844
+ return c;
1845
+ }
1846
+ ```
1847
+
1848
+ should be transformed to:
1849
+
1850
+ ```typescript
1851
+ function x(a: number, b: number): number {
1852
+ let c: number = ((a | 0) + (b | 0)) | 0;
1853
+ c = Math.imul(c, 2 | 0);
1854
+ return c;
1855
+ }
1856
+ ```
1857
+
1858
+ A domain-bound expression must not initialize unannotated storage if TypeScript
1859
+ would infer that storage as plain `number`.
1860
+
1861
+ ```typescript
1862
+ function x(a: i32, b: i32) {
1863
+ const c = a + b;
1864
+ return c;
1865
+ }
1866
+ ```
1867
+
1868
+ should be a compile error:
1869
+
1870
+ ```bash
1871
+ error: result of i32 operation is assigned to untyped variable 'c'; use a checked cast such as i32(...) at the boundary or explicitly escape to a non-numtypes type
1872
+ ```
1873
+
1874
+ If non-domain storage is intentional, the user must write an explicit escape:
1875
+
1876
+ ```typescript
1877
+ function x(a: i32, b: i32) {
1878
+ const c = (a + b) as number;
1879
+ return c;
1880
+ }
1881
+ ```
1882
+
1883
+ should be transformed to:
1884
+
1885
+ ```typescript
1886
+ function x(a: number, b: number) {
1887
+ const c = ((a | 0) + (b | 0)) | 0;
1888
+ return c;
1889
+ }
1890
+ ```
1891
+
1892
+ The user must preserve the domain at the lvalue boundary with an annotation and,
1893
+ when TypeScript sees the expression as `number`, with a checked cast.
1894
+
1895
+ ```typescript
1896
+ function x(a: i32, b: i32): i32 {
1897
+ const c: i32 = i32(a + b);
1898
+ return c;
1899
+ }
1900
+ ```
1901
+
1902
+ should be transformed to:
1903
+
1904
+ ```typescript
1905
+ function x(a: number, b: number): number {
1906
+ const c: number = ((a | 0) + (b | 0)) | 0;
1907
+ return c;
1908
+ }
1909
+ ```
1910
+
1911
+ Object literal properties and array literal elements are also lvalue boundaries.
1912
+ If a domain-bound operation is placed into an untyped aggregate, TypeScript will
1913
+ infer a plain `number` property or element type, so the transformer must reject
1914
+ it.
1915
+
1916
+ ```typescript
1917
+ function objectValue(a: i32, b: i32) {
1918
+ const box = { value: a + b };
1919
+ return box;
1920
+ }
1921
+
1922
+ function arrayValue(a: i32, b: i32) {
1923
+ const values = [a + b];
1924
+ return values;
1925
+ }
1926
+ ```
1927
+
1928
+ should be compile errors:
1929
+
1930
+ ```bash
1931
+ error: result of i32 operation initializes object property 'value' without a checked cast
1932
+ error: result of i32 operation initializes an array element without a checked cast
1933
+ ```
1934
+
1935
+ Typed aggregate storage records the domain, but operation results still need a
1936
+ checked cast at the branded element or property boundary.
1937
+
1938
+ ```typescript
1939
+ function objectValue(a: i32, b: i32): { value: i32 } {
1940
+ return { value: i32(a + b) };
1941
+ }
1942
+
1943
+ function arrayValue(a: i32, b: i32): i32[] {
1944
+ const values: i32[] = [i32(a + b)];
1945
+ values[0] = i32(a + b);
1946
+ return values;
1947
+ }
1948
+ ```
1949
+
1950
+ should be transformed to:
1951
+
1952
+ ```typescript
1953
+ function objectValue(a: number, b: number): { value: number } {
1954
+ return { value: ((a | 0) + (b | 0)) | 0 };
1955
+ }
1956
+
1957
+ function arrayValue(a: number, b: number): number[] {
1958
+ const values: number[] = [((a | 0) + (b | 0)) | 0];
1959
+ values[0] = ((a | 0) + (b | 0)) | 0;
1960
+ return values;
1961
+ }
1962
+ ```
1963
+
1964
+ Array methods and ordinary function calls are not implicit domain contexts. For
1965
+ branded call slots, the transformer relies on TypeScript to reject `number`
1966
+ arguments where a branded type is required, and the user must cross the call
1967
+ boundary with an explicit cast.
1968
+
1969
+ ```typescript
1970
+ function arrayPush(a: i32, b: i32, values: i32[]) {
1971
+ values.push(a + b);
1972
+ }
1973
+ ```
1974
+
1975
+ is rejected by TypeScript because `a + b` has TypeScript type `number`.
1976
+
1977
+ ```typescript
1978
+ function arrayPush(a: i32, b: i32, values: i32[]) {
1979
+ values.push(i32(a + b));
1980
+ }
1981
+ ```
1982
+
1983
+ should be transformed to:
1984
+
1985
+ ```typescript
1986
+ function arrayPush(a: number, b: number, values: number[]) {
1987
+ values.push(((a | 0) + (b | 0)) | 0);
1988
+ }
1989
+ ```
1990
+
1991
+ For plain `number` call slots, TypeScript allows the operation result, so the
1992
+ transformer owns the explicit-escape diagnostic.
1993
+
1994
+ ```typescript
1995
+ declare function take(...values: number[]): void;
1996
+
1997
+ function arrayPush(a: i32, b: i32, values: number[]) {
1998
+ take(a + b);
1999
+ values.push(a + b);
2000
+ }
2001
+ ```
2002
+
2003
+ should be compile errors unless the operation result is explicitly escaped, for
2004
+ example with `(a + b) as number`.
2005
+
2006
+ An explicit cast call can also make the inferred variable type branded before
2007
+ the transformer erases the call.
2008
+
2009
+ ```typescript
2010
+ function x(a: i32, b: i32) {
2011
+ const c = i32(a + b);
2012
+ return c;
2013
+ }
2014
+ ```
2015
+
2016
+ should be transformed to:
2017
+
2018
+ ```typescript
2019
+ function x(a: number, b: number): number {
2020
+ const c: number = ((a | 0) + (b | 0)) | 0;
2021
+ return c;
2022
+ }
2023
+ ```
2024
+
2025
+ Compound assignment to branded storage is not valid TypeScript. The operation
2026
+ result has TypeScript type `number`, and TypeScript rejects assigning that result
2027
+ back into an `i32`, `u32`, `f32`, or `f64` lvalue. Users should write the
2028
+ equivalent checked assignment explicitly.
2029
+
2030
+ ```typescript
2031
+ function x(a: i32, b: i32): i32 {
2032
+ a = i32(a + b);
2033
+ a = i32(a * 3);
2034
+ return a;
2035
+ }
2036
+ ```
2037
+
2038
+ should be transformed to:
2039
+
2040
+ ```typescript
2041
+ function x(a: number, b: number): number {
2042
+ a = ((a | 0) + (b | 0)) | 0;
2043
+ a = Math.imul(a, 3 | 0);
2044
+ return a;
2045
+ }
2046
+ ```
2047
+
2048
+ The same source rule applies to every operation that would otherwise be written
2049
+ as a compound assignment:
2050
+
2051
+ | Target | Explicit checked assignment form |
2052
+ | --- | --- |
2053
+ | `i32` | `x = i32(x + y)`, `x = i32(x * y)`, `x = i32(x & y)`, etc. |
2054
+ | `u32` | `x = u32(x + y)`, `x = u32(x * y)`, `x = u32(x >>> y)`, etc. |
2055
+ | `f32` | `x = f32(x + y)`, `x = f32(x * y)`, etc. |
2056
+ | `f64` | `x = f64(x + y)`, `x = f64(x * y)`, etc. |
2057
+
2058
+ Prefix and postfix updates should preserve JavaScript value-result semantics,
2059
+ but the stored value must be closed. Standalone updates are valid operation
2060
+ sites. If the update result itself crosses a branded boundary, wrap that update
2061
+ expression in the checked cast for the target domain.
2062
+
2063
+ ```typescript
2064
+ function pre(a: i32): i32 {
2065
+ return i32(++a);
2066
+ }
2067
+
2068
+ function post(a: i32): i32 {
2069
+ return i32(a++);
2070
+ }
2071
+
2072
+ function dec(a: i32): i32 {
2073
+ return i32(--a);
2074
+ }
2075
+ ```
2076
+
2077
+ can be transformed conceptually to:
2078
+
2079
+ ```typescript
2080
+ function pre(a: number): number {
2081
+ a = ((a | 0) + (1 | 0)) | 0;
2082
+ return a | 0;
2083
+ }
2084
+
2085
+ function post(a: number): number {
2086
+ const old: number = a | 0;
2087
+ a = ((a | 0) + (1 | 0)) | 0;
2088
+ return old | 0;
2089
+ }
2090
+
2091
+ function dec(a: number): number {
2092
+ a = ((a | 0) - (1 | 0)) | 0;
2093
+ return a | 0;
2094
+ }
2095
+ ```
2096
+
2097
+ For complex left-hand sides, the actual implementation must preserve single
2098
+ evaluation of the receiver, property key, and index expression.
2099
+
2100
+ ## Operator Coverage Summary
2101
+
2102
+ The arithmetic closure surface is:
2103
+
2104
+ | Type | Unary | Binary arithmetic | Integer bitwise |
2105
+ | --- | --- | --- | --- |
2106
+ | `i32` | `+`, `-` | `+`, `-`, `*`, `/`, `%`, `**` | `&`, `|`, `^`, `<<`, `>>`, `>>>` |
2107
+ | `u32` | `+`, `-` | `+`, `-`, `*`, `/`, `%`, `**` | `&`, `|`, `^`, `<<`, `>>`, `>>>` |
2108
+ | `f32` | `+`, `-` | `+`, `-`, `*`, `/`, `%`, `**` | explicit integer cast required |
2109
+ | `f64` | `+`, `-` | `+`, `-`, `*`, `/`, `%`, `**` | explicit integer cast required |
2110
+
2111
+ The transformer should not change comparison, logical, or conditional expression
2112
+ truthiness semantics. If a branch or condition returns a branded numeric value,
2113
+ only the selected result expression should be closed to the contextual target
2114
+ type.