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,24 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
export function isImportClauseTypeOnly(importClause) {
|
|
3
|
+
const fields = getImportClauseCompatFields(importClause);
|
|
4
|
+
if ("phaseModifier" in fields) {
|
|
5
|
+
return fields.phaseModifier === ts.SyntaxKind.TypeKeyword;
|
|
6
|
+
}
|
|
7
|
+
return fields.isTypeOnly === true;
|
|
8
|
+
}
|
|
9
|
+
export function updateImportClauseCompat(factory, importClause, name, namedBindings) {
|
|
10
|
+
const fields = getImportClauseCompatFields(importClause);
|
|
11
|
+
if ("phaseModifier" in fields) {
|
|
12
|
+
return factory.updateImportClause(importClause, fields.phaseModifier, name, namedBindings);
|
|
13
|
+
}
|
|
14
|
+
return updateImportClauseWithIsTypeOnly(factory, importClause, fields.isTypeOnly === true, name, namedBindings);
|
|
15
|
+
}
|
|
16
|
+
function updateImportClauseWithIsTypeOnly(factory, importClause, isTypeOnly, name, namedBindings) {
|
|
17
|
+
// TypeScript 5.8 exposes only the boolean import-clause update overload.
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
19
|
+
return factory.updateImportClause(importClause, isTypeOnly, name, namedBindings);
|
|
20
|
+
}
|
|
21
|
+
function getImportClauseCompatFields(importClause) {
|
|
22
|
+
return importClause;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=ts-compat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts-compat.js","sourceRoot":"","sources":["../../src/transformer/ts-compat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAgBjC,MAAM,UAAU,sBAAsB,CAAC,YAA6B;IAClE,MAAM,MAAM,GACV,2BAA2B,CAAC,YAAY,CAAC,CAAC;IAE5C,IAAI,eAAe,IAAI,MAAM,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,aAAa,KAAK,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;IAC5D,CAAC;IAED,OAAO,MAAM,CAAC,UAAU,KAAK,IAAI,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,OAAuB,EACvB,YAA6B,EAC7B,IAA+B,EAC/B,aAAiD;IAEjD,MAAM,MAAM,GACV,2BAA2B,CAAC,YAAY,CAAC,CAAC;IAE5C,IAAI,eAAe,IAAI,MAAM,EAAE,CAAC;QAC9B,OAAQ,OAA4C,CAAC,kBAAkB,CACrE,YAAY,EACZ,MAAM,CAAC,aAAa,EACpB,IAAI,EACJ,aAAa,CACd,CAAC;IACJ,CAAC;IAED,OAAO,gCAAgC,CACrC,OAAO,EACP,YAAY,EACZ,MAAM,CAAC,UAAU,KAAK,IAAI,EAC1B,IAAI,EACJ,aAAa,CACd,CAAC;AACJ,CAAC;AAED,SAAS,gCAAgC,CACvC,OAAuB,EACvB,YAA6B,EAC7B,UAAmB,EACnB,IAA+B,EAC/B,aAAiD;IAEjD,yEAAyE;IACzE,4DAA4D;IAC5D,OAAO,OAAO,CAAC,kBAAkB,CAC/B,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,aAAa,CACd,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAClC,YAA6B;IAE7B,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Implementation Plan
|
|
2
|
+
|
|
3
|
+
This document describes the staged implementation plan for the transformer.
|
|
4
|
+
|
|
5
|
+
The core constraint is that TypeScript widens most branded numeric operations
|
|
6
|
+
back to `number`. For example, if `a: i32` and `b: i32`, TypeScript evaluates
|
|
7
|
+
`a + b` as `number`, not `i32`. The transformer must therefore maintain its own
|
|
8
|
+
numeric-domain analysis instead of relying only on `checker.getTypeAtLocation`.
|
|
9
|
+
|
|
10
|
+
## Terminology
|
|
11
|
+
|
|
12
|
+
`TypeScript type` means the type reported by the TypeScript checker.
|
|
13
|
+
|
|
14
|
+
`Numeric domain` means the transformer's internal classification:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
i32 | u32 | f32 | f64 | plain-number | unknown
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`Domain-bound expression` means an expression whose result is known by the
|
|
21
|
+
transformer to be closed over one numeric domain, even if TypeScript reports the
|
|
22
|
+
expression as `number`.
|
|
23
|
+
|
|
24
|
+
`Domain storage` means an lvalue location whose TypeScript type records a
|
|
25
|
+
numeric domain for later operation dispatch. It does not guarantee that the
|
|
26
|
+
stored JavaScript value has already been coerced into that domain.
|
|
27
|
+
|
|
28
|
+
- a variable, parameter, property, or return position annotated with a numtypes
|
|
29
|
+
branded type;
|
|
30
|
+
- a value inferred from a checked cast call such as `i32(expr)` or an unchecked
|
|
31
|
+
TypeScript assertion such as `expr as i32`;
|
|
32
|
+
- a symbol already recorded by the transformer as domain storage.
|
|
33
|
+
|
|
34
|
+
`Domain leak` means assigning a domain-bound expression into storage that
|
|
35
|
+
TypeScript treats as plain `number`. Domain leaks are compile errors unless the
|
|
36
|
+
user explicitly escapes the domain with a non-numtypes assertion such as
|
|
37
|
+
`(a + b) as number`, `(a + b) as unknown`, or `(a + b) as any`. The operation
|
|
38
|
+
itself is still lowered according to the operand domain; only the storage escape
|
|
39
|
+
is explicit.
|
|
40
|
+
|
|
41
|
+
Wrappers that do not change the value domain, such as non-null assertions and
|
|
42
|
+
`satisfies` clauses, are not escapes. Domain-leak detection must unwrap them and
|
|
43
|
+
inspect the real storage boundary.
|
|
44
|
+
|
|
45
|
+
The transformer intentionally does not duplicate every check that TypeScript's
|
|
46
|
+
type system already performs. When a normal TypeScript assignment or call
|
|
47
|
+
signature rejects `number` where a branded type is required, that TypeScript
|
|
48
|
+
diagnostic is part of the library contract. The transformer adds diagnostics for
|
|
49
|
+
numtypes-specific semantics that TypeScript cannot express, such as arithmetic
|
|
50
|
+
domain tracking, erased cast marker misuse, implicit widening boundaries, and
|
|
51
|
+
ambiguous numeric unions.
|
|
52
|
+
|
|
53
|
+
Because arithmetic over branded operands has TypeScript type `number`, a raw
|
|
54
|
+
operation result cannot be assigned or returned directly where a branded type is
|
|
55
|
+
required. Users must cross that TypeScript boundary with a checked cast call,
|
|
56
|
+
for example `return f64(a / b)` rather than `return a / b` in a function declared
|
|
57
|
+
as returning `f64`.
|
|
58
|
+
|
|
59
|
+
Unchecked assertions are trusted TypeScript assertions. They may satisfy a
|
|
60
|
+
branded boundary and select a domain for later analysis, but they never insert a
|
|
61
|
+
runtime coercion by themselves. `(a + b) as i32` lowers the `a + b` operation
|
|
62
|
+
only if that operation is already domain-bound by its operands. `(a + b) as i32`
|
|
63
|
+
with plain `number` operands emits as `a + b`.
|
|
64
|
+
|
|
65
|
+
Checked cast calls are boundary conversions, not implicit contexts for every
|
|
66
|
+
nested operation. `i32(readNumber())` is valid because the whole plain-number
|
|
67
|
+
expression is cast. `i32(a + b)` is still invalid when `a: i32` and `b: number`;
|
|
68
|
+
the user must write `i32(a + i32(b))`.
|
|
69
|
+
|
|
70
|
+
## Phase 1: Symbol Resolution
|
|
71
|
+
|
|
72
|
+
Resolve the actual package symbols before doing any analysis.
|
|
73
|
+
|
|
74
|
+
Tasks:
|
|
75
|
+
|
|
76
|
+
- detect imports from `numtypes`;
|
|
77
|
+
- resolve `i32`, `u32`, `f32`, and `f64` through the TypeScript checker;
|
|
78
|
+
- reject lookalike local functions or variables with the same names;
|
|
79
|
+
- build a per-source-file symbol table for cast calls and imported types;
|
|
80
|
+
- preserve enough import metadata so the transform phase can remove phantom
|
|
81
|
+
imports later.
|
|
82
|
+
|
|
83
|
+
Tests:
|
|
84
|
+
|
|
85
|
+
- imported `i32` is recognized;
|
|
86
|
+
- local `function i32(...)` is not recognized;
|
|
87
|
+
- aliased imports such as `import { i32 as int }` are recognized;
|
|
88
|
+
- type-only imports are recognized for annotations.
|
|
89
|
+
|
|
90
|
+
## Phase 2: Domain Extraction From Types
|
|
91
|
+
|
|
92
|
+
Implement helpers that map TypeScript types and type nodes to numeric domains.
|
|
93
|
+
|
|
94
|
+
Tasks:
|
|
95
|
+
|
|
96
|
+
- detect branded type aliases from `numtypes`;
|
|
97
|
+
- read annotations on parameters, variables, properties, and return types;
|
|
98
|
+
- read numtypes references inside arrays, tuples, object properties, and generic
|
|
99
|
+
type arguments for type surface preservation/erasure;
|
|
100
|
+
- detect inferred branded types from checked cast calls and unchecked
|
|
101
|
+
TypeScript assertions;
|
|
102
|
+
- classify plain `number` separately from `unknown`;
|
|
103
|
+
- preserve storage unions that contain numtypes domains, and add diagnostics for
|
|
104
|
+
ambiguous intersections.
|
|
105
|
+
|
|
106
|
+
Initial rule:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
i32/u32/f32/f64 annotation -> matching numeric domain
|
|
110
|
+
plain number annotation -> plain-number
|
|
111
|
+
missing annotation -> infer from initializer only if TypeScript already infers a branded type
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This phase must not infer `const x = a + b` as `i32` just because the initializer
|
|
115
|
+
is an `i32` operation. That expression is domain-bound, but the storage is still
|
|
116
|
+
plain `number` unless the user explicitly casts it. For branded storage,
|
|
117
|
+
`const x: i32 = i32(a + b)` is the valid TypeScript form.
|
|
118
|
+
|
|
119
|
+
## Phase 3: Expression Domain Analysis
|
|
120
|
+
|
|
121
|
+
Implement a recursive expression analyzer that returns both the TypeScript type
|
|
122
|
+
and the internal numeric domain for each expression.
|
|
123
|
+
|
|
124
|
+
Suggested result shape:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface ExpressionDomainResult {
|
|
128
|
+
readonly domain: NumericDomain | "plain-number" | "unknown";
|
|
129
|
+
readonly isDomainBound: boolean;
|
|
130
|
+
readonly requiresClosure: boolean;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Rules:
|
|
135
|
+
|
|
136
|
+
- identifier/property access: use the storage domain recorded for the symbol or
|
|
137
|
+
the branded TypeScript type;
|
|
138
|
+
- checked cast call: source expression is analyzed first, then the target
|
|
139
|
+
domain is applied and emitted as a runtime coercion;
|
|
140
|
+
- unchecked TypeScript assertion: the asserted domain is applied for analysis,
|
|
141
|
+
but the assertion itself emits no runtime coercion and is treated as a
|
|
142
|
+
trusted operand for later operations;
|
|
143
|
+
- checked cast call argument: analyze the argument normally; the cast converts
|
|
144
|
+
the final argument value to the target domain, but it does not automatically
|
|
145
|
+
make mixed branded/plain operations inside the argument valid;
|
|
146
|
+
- numeric literal: may be contextually coerced only when a target domain is
|
|
147
|
+
available;
|
|
148
|
+
- unary arithmetic: domain follows the operand when the operator is supported;
|
|
149
|
+
- binary arithmetic: same-domain operands produce a domain-bound expression of
|
|
150
|
+
that domain;
|
|
151
|
+
- integer bitwise: allowed only for `i32` and `u32`;
|
|
152
|
+
- mixed branded domains require explicit casts;
|
|
153
|
+
- branded domain plus plain `number` requires an explicit cast unless the plain
|
|
154
|
+
side is an allowed contextual literal;
|
|
155
|
+
- unsupported expressions return `unknown` and are rejected when a numeric
|
|
156
|
+
domain is required.
|
|
157
|
+
|
|
158
|
+
The analyzer should cache results by `ts.Node` identity within one source file.
|
|
159
|
+
|
|
160
|
+
## Phase 4: Contextual Domain Analysis
|
|
161
|
+
|
|
162
|
+
Implement target-domain discovery for expression positions.
|
|
163
|
+
|
|
164
|
+
Contextual domains:
|
|
165
|
+
|
|
166
|
+
- variable initializer target from declaration annotation or inferred branded
|
|
167
|
+
cast result, only after the source has passed TypeScript's branded boundary;
|
|
168
|
+
- assignment target from the resolved lvalue symbol, only after the source has
|
|
169
|
+
passed TypeScript's branded boundary;
|
|
170
|
+
- return expression target from containing function return annotation, only
|
|
171
|
+
after the source has passed TypeScript's branded boundary;
|
|
172
|
+
- argument target only from known numtypes cast functions;
|
|
173
|
+
- conditional expression branch target from the containing target domain;
|
|
174
|
+
- array/property element target only when its declared type is a numtypes domain.
|
|
175
|
+
|
|
176
|
+
Compile errors:
|
|
177
|
+
|
|
178
|
+
- domain-bound expression assigned to plain `number` storage;
|
|
179
|
+
- domain-bound expression returned from an unannotated function;
|
|
180
|
+
- domain-bound expression passed to a plain `number` parameter without explicit
|
|
181
|
+
escape or cast;
|
|
182
|
+
- same-domain arithmetic stored in an unannotated variable when TypeScript would
|
|
183
|
+
infer plain `number`.
|
|
184
|
+
|
|
185
|
+
Do not treat a branded lvalue annotation as permission to accept TypeScript
|
|
186
|
+
invalid source such as `const x: i32 = a + b` or `return a / b` from a `f64`
|
|
187
|
+
function. Those cases must be written with checked casts.
|
|
188
|
+
|
|
189
|
+
Normal function and method parameters are not implicit domain contexts. A call
|
|
190
|
+
such as `values.push(a + b)` for `values: i32[]` is intentionally rejected by
|
|
191
|
+
TypeScript because `a + b` has TypeScript type `number`; users must write
|
|
192
|
+
`values.push(i32(a + b))` when they want to cross that call boundary. This keeps
|
|
193
|
+
call arguments explicit and avoids hidden contextual lowering through arbitrary
|
|
194
|
+
signatures.
|
|
195
|
+
|
|
196
|
+
Generic type arguments that contain numtypes domains are allowed as type surface,
|
|
197
|
+
but they do not create special runtime semantics. The transformer should preserve
|
|
198
|
+
or erase types such as `Array<i32>`, `[i32, f32]`, `Promise<i32>`, `Map<string,
|
|
199
|
+
i32>`, and `Box<i32>`, but it should not interpret arbitrary generic methods,
|
|
200
|
+
callbacks, or asynchronous resolution. Calls through those APIs rely on
|
|
201
|
+
TypeScript signatures and explicit checked casts at value boundaries.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
function f(a: i32, b: i32) {
|
|
207
|
+
const x = a + b;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
must produce:
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
error: result of i32 operation is assigned to untyped variable 'x'; use a checked cast such as i32(...) at the boundary or explicitly escape to a non-numtypes type
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Allowed forms:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
function f(a: i32, b: i32): i32 {
|
|
221
|
+
const x: i32 = i32(a + b);
|
|
222
|
+
return x;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function g(a: i32, b: i32) {
|
|
226
|
+
const x = i32(a + b);
|
|
227
|
+
return x;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Phase 5: Diagnostics Pass
|
|
232
|
+
|
|
233
|
+
Run a pre-transform analysis pass that reports all errors before rewriting.
|
|
234
|
+
|
|
235
|
+
Tasks:
|
|
236
|
+
|
|
237
|
+
- traverse source files once and collect domain diagnostics;
|
|
238
|
+
- include source spans and stable diagnostic codes;
|
|
239
|
+
- make diagnostics deterministic and fixture-testable;
|
|
240
|
+
- fail transform when diagnostics exist.
|
|
241
|
+
|
|
242
|
+
Important diagnostic categories:
|
|
243
|
+
|
|
244
|
+
- implicit cast from branded domain to plain `number`;
|
|
245
|
+
- implicit cast between different branded domains;
|
|
246
|
+
- mixed branded/plain operands inside an operation, even when the whole
|
|
247
|
+
expression is later wrapped in a checked cast;
|
|
248
|
+
- bitwise operation on `f32` or `f64`;
|
|
249
|
+
- domain-bound result escaping through unannotated storage;
|
|
250
|
+
- unresolved or shadowed numtypes symbols;
|
|
251
|
+
- erased cast marker misuse, such as storing or value-re-exporting `i32`;
|
|
252
|
+
- unsupported expression shape in a domain-required context.
|
|
253
|
+
|
|
254
|
+
## Phase 6: Expression Rewriting
|
|
255
|
+
|
|
256
|
+
After diagnostics pass, rewrite expressions according to the domain analysis.
|
|
257
|
+
|
|
258
|
+
Tasks:
|
|
259
|
+
|
|
260
|
+
- insert operand coercions;
|
|
261
|
+
- insert result closures;
|
|
262
|
+
- lower integer multiplication to `Math.imul`;
|
|
263
|
+
- preserve source evaluation order;
|
|
264
|
+
- preserve prefix/postfix update value semantics;
|
|
265
|
+
- preserve single evaluation for complex assignment targets;
|
|
266
|
+
- do not treat compound assignments such as `a += b` as accepted branded-source
|
|
267
|
+
syntax; TypeScript rejects them for branded lvalues, so users must write
|
|
268
|
+
checked assignments such as `a = i32(a + b)`;
|
|
269
|
+
- erase checked cast calls into coercion idioms;
|
|
270
|
+
- erase unchecked TypeScript assertions without adding coercions;
|
|
271
|
+
- remove phantom imports when no runtime import remains.
|
|
272
|
+
|
|
273
|
+
The rewrite should use the cached domain analysis rather than re-deriving
|
|
274
|
+
domains during emit.
|
|
275
|
+
|
|
276
|
+
## Phase 7: Fixture Test Runner
|
|
277
|
+
|
|
278
|
+
Build the source-to-source fixture runner.
|
|
279
|
+
|
|
280
|
+
Fixture types:
|
|
281
|
+
|
|
282
|
+
- `*.input.ts` and `*.output.ts` for successful transforms;
|
|
283
|
+
- `*.input.ts` and `*.errors.txt` for diagnostics;
|
|
284
|
+
- grouped directories for `i32`, `u32`, `f32`, `f64`, mixed domains,
|
|
285
|
+
assignments, and imports.
|
|
286
|
+
|
|
287
|
+
The runner should normalize line endings and use TypeScript's printer so output
|
|
288
|
+
is stable.
|
|
289
|
+
|
|
290
|
+
## Phase 8: V8 Lowering Probes
|
|
291
|
+
|
|
292
|
+
Add opt-in engine probes after the transform output is stable.
|
|
293
|
+
|
|
294
|
+
Tasks:
|
|
295
|
+
|
|
296
|
+
- generate small JS probes from fixture output;
|
|
297
|
+
- run them under Node with explicit V8 flags;
|
|
298
|
+
- record the Node and V8 versions in test output;
|
|
299
|
+
- assert narrow, version-aware facts only;
|
|
300
|
+
- keep these probes out of the default test command.
|
|
301
|
+
|
|
302
|
+
Candidate probes:
|
|
303
|
+
|
|
304
|
+
- i32 addition closure;
|
|
305
|
+
- i32/u32 `Math.imul` multiplication;
|
|
306
|
+
- u32 logical right shift;
|
|
307
|
+
- f32 `Math.fround` arithmetic;
|
|
308
|
+
- f64 unary-plus arithmetic.
|
|
309
|
+
|
|
310
|
+
## Phase 9: Benchmarks
|
|
311
|
+
|
|
312
|
+
Add opt-in benchmarks after correctness tests are meaningful.
|
|
313
|
+
|
|
314
|
+
Benchmark groups:
|
|
315
|
+
|
|
316
|
+
- transformer throughput on large files;
|
|
317
|
+
- emitted i32/u32/f32/f64 runtime shapes;
|
|
318
|
+
- baseline JavaScript versus transformed JavaScript;
|
|
319
|
+
- compile-time overhead of diagnostics and domain analysis caching.
|
|
320
|
+
|
|
321
|
+
Benchmarks should not gate normal correctness tests until there is a stable
|
|
322
|
+
baseline and variance policy.
|
|
323
|
+
|
|
324
|
+
## Implementation Order
|
|
325
|
+
|
|
326
|
+
1. Symbol resolution.
|
|
327
|
+
2. Type-to-domain extraction.
|
|
328
|
+
3. Expression domain analyzer for identifiers, literals, casts, unary, binary.
|
|
329
|
+
4. Contextual target-domain analyzer for variable declarations and returns.
|
|
330
|
+
5. Diagnostics for implicit plain-number leaks.
|
|
331
|
+
6. Fixture runner and first failing diagnostics fixtures.
|
|
332
|
+
7. Expression rewrite for casts and simple binary arithmetic.
|
|
333
|
+
8. Assignment/update rewrite.
|
|
334
|
+
9. Import erasure.
|
|
335
|
+
10. V8 probes and benchmark expansion.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Lib Implementation
|
|
2
|
+
|
|
3
|
+
The authoritative public symbol surface is `src/lib/index.ts`. This document
|
|
4
|
+
must stay aligned with that file.
|
|
5
|
+
|
|
6
|
+
These symbols are phantom runtime markers: user code imports and calls the
|
|
7
|
+
function symbols before transformation, but the transformer must lower every
|
|
8
|
+
function call to the matching JavaScript coercion idiom and remove the package
|
|
9
|
+
import when no runtime value remains.
|
|
10
|
+
|
|
11
|
+
The function symbols are only valid as direct checked cast calls such as
|
|
12
|
+
`i32(value)` or namespace-qualified direct calls such as `nt.i32(value)`.
|
|
13
|
+
Because the package root has no runtime implementation, users must not store,
|
|
14
|
+
pass, object-shorthand, optional-call, optional-access, spread-call,
|
|
15
|
+
type-argument-call, parenthesized-callee-call, or value-re-export these function
|
|
16
|
+
symbols. Type-only re-exports of the branded type aliases are allowed.
|
|
17
|
+
|
|
18
|
+
The exported type aliases are type-only domain markers. A TypeScript assertion
|
|
19
|
+
such as `value as i32` or `<i32>value` is unchecked and must not insert a runtime
|
|
20
|
+
coercion by itself. It only changes the domain that later numtypes operations
|
|
21
|
+
use for dispatch. Use the function form, such as `i32(value)`, when runtime
|
|
22
|
+
coercion is required at that expression boundary.
|
|
23
|
+
|
|
24
|
+
The package root intentionally has no executable implementation. The emitted
|
|
25
|
+
JavaScript for `src/lib/index.ts` should remain effectively empty.
|
|
26
|
+
|
|
27
|
+
## Public Surface
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
declare const i32Brand: unique symbol;
|
|
31
|
+
declare const u32Brand: unique symbol;
|
|
32
|
+
declare const f32Brand: unique symbol;
|
|
33
|
+
declare const f64Brand: unique symbol;
|
|
34
|
+
|
|
35
|
+
export type i32 = number & {
|
|
36
|
+
readonly [i32Brand]: never;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type u32 = number & {
|
|
40
|
+
readonly [u32Brand]: never;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type f32 = number & {
|
|
44
|
+
readonly [f32Brand]: never;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type f64 = number & {
|
|
48
|
+
readonly [f64Brand]: never;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export declare function i32(value: number): i32;
|
|
52
|
+
|
|
53
|
+
export declare function u32(value: number): u32;
|
|
54
|
+
|
|
55
|
+
export declare function f32(value: number): f32;
|
|
56
|
+
|
|
57
|
+
export declare function f64(value: number): f64;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Transformer Lowering
|
|
61
|
+
|
|
62
|
+
The runtime behavior for checked cast calls is produced by the transformer, not
|
|
63
|
+
by these declarations.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
i32(value) => value | 0
|
|
67
|
+
u32(value) => value >>> 0
|
|
68
|
+
f32(value) => Math.fround(value)
|
|
69
|
+
f64(value) => +value
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Unchecked TypeScript assertions erase like normal TypeScript syntax.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
value as i32 => value
|
|
76
|
+
<i32>value => value
|
|
77
|
+
```
|