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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +12 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +652 -0
- package/dist/lib/index.d.ts +22 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/transformer/analyze/analyze-source-file.d.ts +15 -0
- package/dist/transformer/analyze/analyze-source-file.d.ts.map +1 -0
- package/dist/transformer/analyze/analyze-source-file.js +605 -0
- package/dist/transformer/analyze/analyze-source-file.js.map +1 -0
- package/dist/transformer/analyze/get-contextual-domain.d.ts +19 -0
- package/dist/transformer/analyze/get-contextual-domain.d.ts.map +1 -0
- package/dist/transformer/analyze/get-contextual-domain.js +197 -0
- package/dist/transformer/analyze/get-contextual-domain.js.map +1 -0
- package/dist/transformer/analyze/get-expression-domain.d.ts +26 -0
- package/dist/transformer/analyze/get-expression-domain.d.ts.map +1 -0
- package/dist/transformer/analyze/get-expression-domain.js +804 -0
- package/dist/transformer/analyze/get-expression-domain.js.map +1 -0
- package/dist/transformer/analyze/type-domain.d.ts +41 -0
- package/dist/transformer/analyze/type-domain.d.ts.map +1 -0
- package/dist/transformer/analyze/type-domain.js +260 -0
- package/dist/transformer/analyze/type-domain.js.map +1 -0
- package/dist/transformer/ast.d.ts +10 -0
- package/dist/transformer/ast.d.ts.map +1 -0
- package/dist/transformer/ast.js +115 -0
- package/dist/transformer/ast.js.map +1 -0
- package/dist/transformer/diagnostics.d.ts +17 -0
- package/dist/transformer/diagnostics.d.ts.map +1 -0
- package/dist/transformer/diagnostics.js +30 -0
- package/dist/transformer/diagnostics.js.map +1 -0
- package/dist/transformer/domains.d.ts +11 -0
- package/dist/transformer/domains.d.ts.map +1 -0
- package/dist/transformer/domains.js +32 -0
- package/dist/transformer/domains.js.map +1 -0
- package/dist/transformer/index.d.ts +10 -0
- package/dist/transformer/index.d.ts.map +1 -0
- package/dist/transformer/index.js +60 -0
- package/dist/transformer/index.js.map +1 -0
- package/dist/transformer/operators.d.ts +16 -0
- package/dist/transformer/operators.d.ts.map +1 -0
- package/dist/transformer/operators.js +44 -0
- package/dist/transformer/operators.js.map +1 -0
- package/dist/transformer/options.d.ts +19 -0
- package/dist/transformer/options.d.ts.map +1 -0
- package/dist/transformer/options.js +17 -0
- package/dist/transformer/options.js.map +1 -0
- package/dist/transformer/symbols.d.ts +56 -0
- package/dist/transformer/symbols.d.ts.map +1 -0
- package/dist/transformer/symbols.js +270 -0
- package/dist/transformer/symbols.js.map +1 -0
- package/dist/transformer/transform/erase-imports.d.ts +14 -0
- package/dist/transformer/transform/erase-imports.d.ts.map +1 -0
- package/dist/transformer/transform/erase-imports.js +174 -0
- package/dist/transformer/transform/erase-imports.js.map +1 -0
- package/dist/transformer/transform/generated-coercions.d.ts +9 -0
- package/dist/transformer/transform/generated-coercions.d.ts.map +1 -0
- package/dist/transformer/transform/generated-coercions.js +22 -0
- package/dist/transformer/transform/generated-coercions.js.map +1 -0
- package/dist/transformer/transform/optimize-coercions.d.ts +11 -0
- package/dist/transformer/transform/optimize-coercions.d.ts.map +1 -0
- package/dist/transformer/transform/optimize-coercions.js +1702 -0
- package/dist/transformer/transform/optimize-coercions.js.map +1 -0
- package/dist/transformer/transform/transform-declaration-file.d.ts +9 -0
- package/dist/transformer/transform/transform-declaration-file.d.ts.map +1 -0
- package/dist/transformer/transform/transform-declaration-file.js +376 -0
- package/dist/transformer/transform/transform-declaration-file.js.map +1 -0
- package/dist/transformer/transform/transform-expression.d.ts +24 -0
- package/dist/transformer/transform/transform-expression.d.ts.map +1 -0
- package/dist/transformer/transform/transform-expression.js +545 -0
- package/dist/transformer/transform/transform-expression.js.map +1 -0
- package/dist/transformer/transform/transform-source-file.d.ts +10 -0
- package/dist/transformer/transform/transform-source-file.d.ts.map +1 -0
- package/dist/transformer/transform/transform-source-file.js +52 -0
- package/dist/transformer/transform/transform-source-file.js.map +1 -0
- package/dist/transformer/ts-compat.d.ts +4 -0
- package/dist/transformer/ts-compat.d.ts.map +1 -0
- package/dist/transformer/ts-compat.js +24 -0
- package/dist/transformer/ts-compat.js.map +1 -0
- package/docs/implementation-plan.md +335 -0
- package/docs/lib-implementation.md +77 -0
- package/docs/lowering-optimization-spec.md +1020 -0
- package/docs/project-structure.md +52 -0
- package/docs/transform-spec.md +2114 -0
- 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.
|