ferrovue 0.9.0 → 0.11.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/README.md +54 -32
- package/dist/args.js +1 -1
- package/dist/attrs.d.ts +2 -0
- package/dist/attrs.js +19 -12
- package/dist/blocks.d.ts +40 -0
- package/dist/blocks.js +768 -0
- package/dist/calls.js +46 -25
- package/dist/children.d.ts +1 -1
- package/dist/children.js +141 -19
- package/dist/classes.js +49 -14
- package/dist/cli.js +2 -6
- package/dist/client-render.d.ts +4 -3
- package/dist/client-render.js +17 -4
- package/dist/client.d.ts +23 -4
- package/dist/client.js +37 -30
- package/dist/compiler.d.ts +14 -4
- package/dist/compiler.js +189 -37
- package/dist/component.js +142 -9
- package/dist/conformance.d.ts +5 -2
- package/dist/conformance.js +16 -2
- package/dist/constants.d.ts +50 -2
- package/dist/constants.js +310 -29
- package/dist/context.d.ts +21 -1
- package/dist/context.js +11 -2
- package/dist/diagnostics.d.ts +11 -1
- package/dist/diagnostics.js +33 -4
- package/dist/dynamic.js +18 -7
- package/dist/editor.d.ts +2 -2
- package/dist/editor.js +14 -9
- package/dist/emitter.d.ts +5 -0
- package/dist/emitter.js +25 -1
- package/dist/enums.d.ts +35 -0
- package/dist/enums.js +244 -0
- package/dist/errors.d.ts +535 -12
- package/dist/errors.js +1511 -339
- package/dist/expr.d.ts +19 -2
- package/dist/expr.js +411 -65
- package/dist/fallthrough.js +15 -0
- package/dist/fixture.d.ts +14 -1
- package/dist/fixture.js +20 -3
- package/dist/functions.d.ts +14 -0
- package/dist/functions.js +186 -0
- package/dist/hydration.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inputs.d.ts +2 -2
- package/dist/inputs.js +1 -1
- package/dist/lists.d.ts +10 -1
- package/dist/lists.js +279 -56
- package/dist/loads.d.ts +28 -0
- package/dist/loads.js +97 -0
- package/dist/loops.js +27 -18
- package/dist/model.d.ts +154 -6
- package/dist/model.js +274 -7
- package/dist/names.d.ts +10 -0
- package/dist/names.js +98 -0
- package/dist/narrowing.d.ts +13 -0
- package/dist/narrowing.js +82 -6
- package/dist/numbers.d.ts +9 -3
- package/dist/numbers.js +21 -9
- package/dist/objects.d.ts +18 -0
- package/dist/objects.js +201 -0
- package/dist/page-json.d.ts +1 -0
- package/dist/page-json.js +14 -0
- package/dist/parens.d.ts +11 -0
- package/dist/parens.js +16 -2
- package/dist/plugin.d.ts +2 -1
- package/dist/plugins/client-only.js +1 -1
- package/dist/plugins/dates.js +11 -9
- package/dist/plugins/head.js +4 -3
- package/dist/plugins/index.js +2 -1
- package/dist/plugins/loaders.d.ts +35 -0
- package/dist/plugins/loaders.js +367 -0
- package/dist/plugins/provide.js +13 -7
- package/dist/plugins/router-link.js +6 -5
- package/dist/plugins/scoped.js +2 -2
- package/dist/plugins/shared-types.js +2 -1
- package/dist/plugins/stores.js +14 -9
- package/dist/plugins/twins.js +4 -3
- package/dist/resolve.d.ts +32 -0
- package/dist/resolve.js +246 -0
- package/dist/revive.d.ts +5 -0
- package/dist/revive.js +38 -0
- package/dist/rust.d.ts +1 -1
- package/dist/rust.js +98 -34
- package/dist/schema.d.ts +1 -1
- package/dist/script.d.ts +1 -1
- package/dist/script.js +97 -58
- package/dist/settle.js +8 -0
- package/dist/slots.js +9 -5
- package/dist/statements.d.ts +11 -0
- package/dist/statements.js +436 -0
- package/dist/strings.d.ts +4 -1
- package/dist/strings.js +43 -26
- package/dist/styles.js +5 -5
- package/dist/template.js +197 -139
- package/dist/typescript.d.ts +17 -11
- package/dist/typescript.js +126 -57
- package/dist/vite.js +3 -3
- package/dist/volar.js +1 -1
- package/package.json +6 -6
package/dist/blocks.js
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
import { fail, GenError, nothing } from "./model.js";
|
|
2
|
+
import { ctx } from "./context.js";
|
|
3
|
+
import { fresh, jsHint } from "./names.js";
|
|
4
|
+
import { expr, typedBy } from "./expr.js";
|
|
5
|
+
import { collected, heldList } from "./lists.js";
|
|
6
|
+
import { atom, bare, occurrences, operand, UNARY } from "./parens.js";
|
|
7
|
+
import { presence } from "./narrowing.js";
|
|
8
|
+
import { asCow, heldStr, isTemporary } from "./strings.js";
|
|
9
|
+
import { intFromF64, isCast } from "./numbers.js";
|
|
10
|
+
import { ownedStruct, ownedStructs } from "./objects.js";
|
|
11
|
+
import { mutableLet } from "./statements.js";
|
|
12
|
+
const DESTRUCTURED = "the destructured value";
|
|
13
|
+
const COW = "std::borrow::Cow::<str>";
|
|
14
|
+
/** Runtime calls that refuse where JavaScript throws or a value cannot be held, or user code that
|
|
15
|
+
* may panic. */
|
|
16
|
+
export const PANICS = /\bfv_refusals\.(?:keep|date)\(|\bfv_i18n\.t\(/;
|
|
17
|
+
/** Whether `code` calls a runtime routine that may refuse the render. */
|
|
18
|
+
export function refuses(code) {
|
|
19
|
+
return /\bfv_refusals\.(?:keep|date)\(/.test(code);
|
|
20
|
+
}
|
|
21
|
+
export function panicky(code) {
|
|
22
|
+
return PANICS.test(code) || Object.values(ctx.helpers).some((h) => code.includes(`${h.rust}(`));
|
|
23
|
+
}
|
|
24
|
+
/** How a value is held in a Rust `let`: what the `let` is given, how the name reads back, and
|
|
25
|
+
* whether the `let` owns what a read borrows (a `String`, a `Cow`, a `Vec`). */
|
|
26
|
+
export function bindingOf(v) {
|
|
27
|
+
const lone = v.lone ? { lone: true } : {};
|
|
28
|
+
if (v.variant !== undefined) {
|
|
29
|
+
const optional = v.ty.k === "opt";
|
|
30
|
+
return { init: bare(v.variant), read: (at) => ({ code: optional ? `${at}.map(|v| v.as_str())` : `${at}.as_str()`, ty: v.ty, variant: at, ...lone }), owned: false };
|
|
31
|
+
}
|
|
32
|
+
if (v.owned)
|
|
33
|
+
return { init: bare(v.code), read: (at) => ({ code: v.ty.k === "opt" ? `${at}.as_ref()` : at, ty: v.ty, ...lone }), owned: true };
|
|
34
|
+
if (v.slice)
|
|
35
|
+
return { init: bare(v.code), read: (at) => ({ code: at, ty: v.ty, slice: true, ...lone }), owned: false };
|
|
36
|
+
if (v.ty.k === "list" && v.iter !== undefined)
|
|
37
|
+
return { init: collected(v), read: (at) => heldList(at, v.ty.k === "list" ? v.ty.of : v.ty, v.lone), owned: true };
|
|
38
|
+
if (v.held !== undefined)
|
|
39
|
+
return { init: v.held, read: (at) => ({ code: `${at}.as_deref()`, ty: v.ty, ...lone }), owned: true };
|
|
40
|
+
if (isCast(v) || (v.ty.k === "int" && v.f64 !== undefined && Object.is(v.num, -0))) {
|
|
41
|
+
return {
|
|
42
|
+
init: bare(v.f64),
|
|
43
|
+
read: (at) => ({ ...intFromF64(at, v.at ?? null), ...lone, ...(v.num !== undefined ? { num: v.num } : {}), ...(v.konst !== undefined ? { konst: v.konst } : {}) }),
|
|
44
|
+
owned: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (isTemporary(v))
|
|
48
|
+
return { init: heldStr(v), read: (at) => ({ code: `&*${at}`, ty: v.ty, ...lone }), owned: true };
|
|
49
|
+
const place = v.ty.k === "list" || v.ty.k === "record" || v.ty.k === "struct" || v.ty.k === "child";
|
|
50
|
+
return {
|
|
51
|
+
init: place ? `&${operand(v.code, UNARY)}` : bare(v.code),
|
|
52
|
+
read: (at) => ({ code: at, ty: v.ty, ...lone, ...(v.konst !== undefined ? { konst: v.konst } : {}) }),
|
|
53
|
+
owned: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const SKIP = new Set(["loc", "start", "end", "extra", "range", "typeAnnotation", "typeParameters", "returnType", "leadingComments", "trailingComments", "innerComments", "__fv", "label"]);
|
|
57
|
+
let renames = 0;
|
|
58
|
+
function spot(from) {
|
|
59
|
+
return { loc: from?.loc, start: from?.start, end: from?.end, __fv: from?.__fv };
|
|
60
|
+
}
|
|
61
|
+
function declared(st) {
|
|
62
|
+
if (st?.type === "VariableDeclaration")
|
|
63
|
+
return st.declarations.flatMap((d) => patternNames(d.id));
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
function patternNames(p) {
|
|
67
|
+
switch (p?.type) {
|
|
68
|
+
case "Identifier":
|
|
69
|
+
return [p.name];
|
|
70
|
+
case "ObjectPattern":
|
|
71
|
+
return p.properties.flatMap((q) => patternNames(q.type === "RestElement" ? q.argument : q.value));
|
|
72
|
+
case "ArrayPattern":
|
|
73
|
+
return p.elements.flatMap((q) => (q ? patternNames(q) : []));
|
|
74
|
+
case "AssignmentPattern":
|
|
75
|
+
return patternNames(p.left);
|
|
76
|
+
case "RestElement":
|
|
77
|
+
return patternNames(p.argument);
|
|
78
|
+
default:
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
class Renamer {
|
|
83
|
+
comp;
|
|
84
|
+
constructor(comp) {
|
|
85
|
+
this.comp = comp;
|
|
86
|
+
}
|
|
87
|
+
statements(sts, outer) {
|
|
88
|
+
const env = new Map(outer);
|
|
89
|
+
for (const st of sts)
|
|
90
|
+
for (const name of declared(st))
|
|
91
|
+
env.set(name, null);
|
|
92
|
+
return sts.map((st) => {
|
|
93
|
+
if (st.type !== "VariableDeclaration")
|
|
94
|
+
return this.node(st, env);
|
|
95
|
+
const declarations = st.declarations.map((d) => {
|
|
96
|
+
if (d.id.type === "Identifier" && (d.init?.type === "ArrowFunctionExpression" || d.init?.type === "FunctionExpression")) {
|
|
97
|
+
const id = this.pattern(d.id, env);
|
|
98
|
+
return { ...d, id, init: this.node(d.init, env) };
|
|
99
|
+
}
|
|
100
|
+
const init = this.node(d.init, env);
|
|
101
|
+
return { ...d, init, id: this.pattern(d.id, env) };
|
|
102
|
+
});
|
|
103
|
+
return { ...st, declarations };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
pattern(p, env) {
|
|
107
|
+
switch (p?.type) {
|
|
108
|
+
case "Identifier": {
|
|
109
|
+
const to = `${p.name}#${++renames}`;
|
|
110
|
+
env.set(p.name, to);
|
|
111
|
+
return { ...p, name: to, fvName: p.name };
|
|
112
|
+
}
|
|
113
|
+
case "ObjectPattern":
|
|
114
|
+
return {
|
|
115
|
+
...p,
|
|
116
|
+
properties: p.properties.map((q) => q.type === "RestElement" ? { ...q, argument: this.pattern(q.argument, env) } : { ...q, key: q.computed ? this.node(q.key, env) : q.key, value: this.pattern(q.value, env), shorthand: false }),
|
|
117
|
+
};
|
|
118
|
+
case "ArrayPattern":
|
|
119
|
+
return { ...p, elements: p.elements.map((q) => (q ? this.pattern(q, env) : q)) };
|
|
120
|
+
case "AssignmentPattern":
|
|
121
|
+
return { ...p, right: this.node(p.right, env), left: this.pattern(p.left, env) };
|
|
122
|
+
case "RestElement":
|
|
123
|
+
return { ...p, argument: this.pattern(p.argument, env) };
|
|
124
|
+
default:
|
|
125
|
+
return p;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
node(n, env) {
|
|
129
|
+
if (!n || typeof n !== "object")
|
|
130
|
+
return n;
|
|
131
|
+
if (Array.isArray(n))
|
|
132
|
+
return n.map((x) => this.node(x, env));
|
|
133
|
+
switch (n.type) {
|
|
134
|
+
case "Identifier": {
|
|
135
|
+
const to = env.get(n.name);
|
|
136
|
+
if (to === null)
|
|
137
|
+
fail(this.comp, "FV1901", `\`${n.name}\` is read before its declaration, where JavaScript throws a \`ReferenceError\``, n);
|
|
138
|
+
return to === undefined ? n : { ...n, name: to, fvName: n.name };
|
|
139
|
+
}
|
|
140
|
+
case "MemberExpression":
|
|
141
|
+
case "OptionalMemberExpression":
|
|
142
|
+
return { ...n, object: this.node(n.object, env), property: n.computed ? this.node(n.property, env) : n.property };
|
|
143
|
+
case "ObjectProperty":
|
|
144
|
+
return { ...n, key: n.computed ? this.node(n.key, env) : n.key, value: this.node(n.value, env), shorthand: false };
|
|
145
|
+
case "ArrowFunctionExpression":
|
|
146
|
+
case "FunctionExpression":
|
|
147
|
+
case "FunctionDeclaration":
|
|
148
|
+
case "ObjectMethod": {
|
|
149
|
+
const inner = new Map(env);
|
|
150
|
+
for (const p of n.params)
|
|
151
|
+
for (const name of patternNames(p))
|
|
152
|
+
inner.delete(name);
|
|
153
|
+
if (n.type === "FunctionExpression" && n.id)
|
|
154
|
+
inner.delete(n.id.name);
|
|
155
|
+
const body = n.body.type === "BlockStatement" ? { ...n.body, body: this.statements(n.body.body, inner) } : this.node(n.body, inner);
|
|
156
|
+
return { ...n, body };
|
|
157
|
+
}
|
|
158
|
+
case "BlockStatement":
|
|
159
|
+
return { ...n, body: this.statements(n.body, env) };
|
|
160
|
+
case "SwitchStatement":
|
|
161
|
+
return { ...n, discriminant: this.node(n.discriminant, env), cases: n.cases.map((c) => ({ ...c, test: this.node(c.test, env), consequent: this.statements(c.consequent, env) })) };
|
|
162
|
+
case "ForOfStatement":
|
|
163
|
+
case "ForInStatement": {
|
|
164
|
+
const right = this.node(n.right, env);
|
|
165
|
+
const inner = new Map(env);
|
|
166
|
+
const left = n.left.type === "VariableDeclaration" ? { ...n.left, declarations: n.left.declarations.map((d) => ({ ...d, id: this.pattern(d.id, inner) })) } : this.node(n.left, env);
|
|
167
|
+
return { ...n, left, right, body: this.node(n.body, inner) };
|
|
168
|
+
}
|
|
169
|
+
case "ForStatement": {
|
|
170
|
+
const inner = new Map(env);
|
|
171
|
+
let init = n.init;
|
|
172
|
+
if (init?.type === "VariableDeclaration") {
|
|
173
|
+
for (const name of declared(init))
|
|
174
|
+
inner.set(name, null);
|
|
175
|
+
init = { ...init, declarations: init.declarations.map((d) => ({ ...d, init: this.node(d.init, inner), id: this.pattern(d.id, inner) })) };
|
|
176
|
+
}
|
|
177
|
+
else
|
|
178
|
+
init = this.node(init, inner);
|
|
179
|
+
return { ...n, init, test: this.node(n.test, inner), update: this.node(n.update, inner), body: this.node(n.body, inner) };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const copy = { ...n };
|
|
183
|
+
for (const key of Object.keys(n)) {
|
|
184
|
+
if (SKIP.has(key))
|
|
185
|
+
continue;
|
|
186
|
+
const v = n[key];
|
|
187
|
+
if (v && typeof v === "object")
|
|
188
|
+
copy[key] = this.node(v, env);
|
|
189
|
+
}
|
|
190
|
+
return copy;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const COUNT = "count with `for (let i = start; i < end; i++)` to an integer bound, or iterate with `for (const x of list)`";
|
|
194
|
+
const LOOPS = {
|
|
195
|
+
ForInStatement: "a `for…in` loop, which walks an object's keys: iterate `Object.keys(…)` with `for (const key of …)`",
|
|
196
|
+
WhileStatement: `a \`while\` loop, which the compiler cannot bound: ${COUNT}`,
|
|
197
|
+
DoWhileStatement: `a \`do…while\` loop, which the compiler cannot bound: ${COUNT}`,
|
|
198
|
+
};
|
|
199
|
+
const OTHERS = {
|
|
200
|
+
TryStatement: "`try`, which only catches what the body throws",
|
|
201
|
+
ThrowStatement: "`throw`, which ends the render with an error",
|
|
202
|
+
LabeledStatement: "a labelled statement",
|
|
203
|
+
FunctionDeclaration: "a function declared inside it with `function`, which is hoisted: declare it with `const f = (…) => …` before it is called",
|
|
204
|
+
ClassDeclaration: "a class declared inside it",
|
|
205
|
+
DebuggerStatement: "`debugger`",
|
|
206
|
+
WithStatement: "`with`",
|
|
207
|
+
};
|
|
208
|
+
const SUBSET = "it holds `const` and `let`, assignments to its `let`s, `if` / `else`, `switch`, `for…of` and counting `for` loops, and `return`";
|
|
209
|
+
const FUNCTIONS = new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration", "ObjectMethod", "ClassMethod", "ClassPrivateMethod"]);
|
|
210
|
+
function walk(n, visit, parent = null) {
|
|
211
|
+
if (!n || typeof n !== "object")
|
|
212
|
+
return;
|
|
213
|
+
if (Array.isArray(n)) {
|
|
214
|
+
for (const x of n)
|
|
215
|
+
walk(x, visit, parent);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!visit(n, parent))
|
|
219
|
+
return;
|
|
220
|
+
for (const key of Object.keys(n))
|
|
221
|
+
if (!SKIP.has(key) && n[key] && typeof n[key] === "object")
|
|
222
|
+
walk(n[key], visit, n);
|
|
223
|
+
}
|
|
224
|
+
function ownNames(body) {
|
|
225
|
+
const lets = new Set();
|
|
226
|
+
const consts = new Set();
|
|
227
|
+
walk(body, (n) => {
|
|
228
|
+
if (FUNCTIONS.has(n.type))
|
|
229
|
+
return false;
|
|
230
|
+
if (n.type === "VariableDeclaration")
|
|
231
|
+
for (const d of n.declarations)
|
|
232
|
+
for (const name of patternNames(d.id))
|
|
233
|
+
(n.kind === "let" ? lets : consts).add(name);
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
return { lets, consts };
|
|
237
|
+
}
|
|
238
|
+
function targetOf(n) {
|
|
239
|
+
return n.type === "UpdateExpression" ? n.argument : n.left;
|
|
240
|
+
}
|
|
241
|
+
function assignedIn(n) {
|
|
242
|
+
const names = new Set();
|
|
243
|
+
walk(n, (x) => {
|
|
244
|
+
if (FUNCTIONS.has(x.type))
|
|
245
|
+
return false;
|
|
246
|
+
if ((x.type === "AssignmentExpression" || x.type === "UpdateExpression") && targetOf(x).type === "Identifier")
|
|
247
|
+
names.add(targetOf(x).name);
|
|
248
|
+
return true;
|
|
249
|
+
});
|
|
250
|
+
return names;
|
|
251
|
+
}
|
|
252
|
+
function reads(n, names) {
|
|
253
|
+
let found = null;
|
|
254
|
+
walk(n, (x) => {
|
|
255
|
+
if (found)
|
|
256
|
+
return false;
|
|
257
|
+
if (x.type === "Identifier" && names.has(x.name))
|
|
258
|
+
found = x;
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
return found;
|
|
262
|
+
}
|
|
263
|
+
/** The `let`s of a body that it assigns again, each assignment checked: a statement of its own that
|
|
264
|
+
* assigns a `let` the body itself declares. */
|
|
265
|
+
function assignments(comp, body, whose) {
|
|
266
|
+
const top = ownNames(body);
|
|
267
|
+
const assigned = new Set();
|
|
268
|
+
const check = (n, parent, own, outer, nested) => {
|
|
269
|
+
const target = targetOf(n);
|
|
270
|
+
const op = `\`${n.operator}\``;
|
|
271
|
+
if (target.type !== "Identifier") {
|
|
272
|
+
const what = target.type === "MemberExpression" || target.type === "OptionalMemberExpression" ? "a field or an item" : "a pattern";
|
|
273
|
+
fail(comp, "FV1902", `an assignment (${op}) to ${what}, which changes an object or a list in place, and ${whose} does not translate that: build the new value and give it a \`const\``, n);
|
|
274
|
+
}
|
|
275
|
+
const name = target.fvName ?? target.name;
|
|
276
|
+
if (!own.lets.has(target.name)) {
|
|
277
|
+
if (own.consts.has(target.name))
|
|
278
|
+
fail(comp, "FV1902", `\`${name}\` is a \`const\`, which JavaScript does not let a body assign: declare it with \`let\``, n);
|
|
279
|
+
if (nested && outer.has(target.name)) {
|
|
280
|
+
fail(comp, "FV1902", `\`${name}\` is a \`let\` of the body around this function, which a function inside it may not assign (${op}): return the value from the function, and assign it in the body`, n);
|
|
281
|
+
}
|
|
282
|
+
fail(comp, "FV1902", `\`${name}\` is assigned (${op}), and it is no \`let\` of this body: ${whose} assigns only the \`let\`s it declares, and leaves a prop, a ref, a \`computed\`, a parameter and a name from outside it as they are; copy the value into a \`let\` of the body`, n);
|
|
283
|
+
}
|
|
284
|
+
const statement = (parent?.type === "ExpressionStatement" && parent.expression === n) || (parent?.type === "ForStatement" && parent.update === n);
|
|
285
|
+
if (!statement)
|
|
286
|
+
fail(comp, "FV1902", `an assignment (${op}) inside an expression, whose value JavaScript then reads: write the assignment as a statement of its own`, n);
|
|
287
|
+
};
|
|
288
|
+
const scan = (from, own, outer, nested) => {
|
|
289
|
+
walk(from, (n, parent) => {
|
|
290
|
+
if (FUNCTIONS.has(n.type)) {
|
|
291
|
+
const mine = n.body?.type === "BlockStatement" ? ownNames(n.body.body) : { lets: new Set(), consts: new Set() };
|
|
292
|
+
scan(n.body, mine, new Set([...outer, ...own.lets]), true);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (n.type === "AssignmentExpression" || n.type === "UpdateExpression") {
|
|
296
|
+
check(n, parent, own, outer, nested);
|
|
297
|
+
if (!nested)
|
|
298
|
+
assigned.add(targetOf(n).name);
|
|
299
|
+
}
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
scan(body, top, new Set(), false);
|
|
304
|
+
if (assigned.size) {
|
|
305
|
+
walk(body, (n) => {
|
|
306
|
+
if (FUNCTIONS.has(n.type))
|
|
307
|
+
return false;
|
|
308
|
+
if (n.type !== "VariableDeclarator" || !FUNCTIONS.has(n.init?.type))
|
|
309
|
+
return true;
|
|
310
|
+
const read = reads(n.init, assigned);
|
|
311
|
+
if (read) {
|
|
312
|
+
const fn = n.id.fvName ?? n.id.name;
|
|
313
|
+
fail(comp, "FV1902", `\`${fn}\` reads \`${read.fvName ?? read.name}\`, a \`let\` the body assigns again: the server gives a function the values the body holds where the function is declared, and JavaScript reads them where it is called; pass the value to \`${fn}\` as a parameter`, read);
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return assigned;
|
|
319
|
+
}
|
|
320
|
+
function stepOf(update, name) {
|
|
321
|
+
if (update?.type === "UpdateExpression" && update.argument.type === "Identifier" && update.argument.name === name)
|
|
322
|
+
return update.operator === "++" ? 1 : -1;
|
|
323
|
+
if (update?.type !== "AssignmentExpression" || update.left.type !== "Identifier" || update.left.name !== name)
|
|
324
|
+
return 0;
|
|
325
|
+
const by = update.right;
|
|
326
|
+
if (by.type !== "NumericLiteral" || !Number.isSafeInteger(by.value) || by.value <= 0)
|
|
327
|
+
return 0;
|
|
328
|
+
return update.operator === "+=" ? by.value : update.operator === "-=" ? -by.value : 0;
|
|
329
|
+
}
|
|
330
|
+
/** How many times a body's statements may be translated again after an `if` or a `case` that
|
|
331
|
+
* falls off its end: each such path continues with the statements after it, translated on its own,
|
|
332
|
+
* so sequential `switch`es of `break`ing cases multiply them. */
|
|
333
|
+
export const PATH_BUDGET = 4096;
|
|
334
|
+
class Desugar {
|
|
335
|
+
comp;
|
|
336
|
+
whose;
|
|
337
|
+
assigned;
|
|
338
|
+
loops = [];
|
|
339
|
+
paths = 0;
|
|
340
|
+
constructor(comp, whose = "a function's body", assigned = new Set()) {
|
|
341
|
+
this.comp = comp;
|
|
342
|
+
this.whose = whose;
|
|
343
|
+
this.assigned = assigned;
|
|
344
|
+
}
|
|
345
|
+
run(sts, k, brk, from) {
|
|
346
|
+
for (let i = 0; i < sts.length; i++) {
|
|
347
|
+
const st = sts[i];
|
|
348
|
+
const rest = sts.slice(i + 1);
|
|
349
|
+
const after = rest.length ? { sts: rest, next: k, brk } : k;
|
|
350
|
+
switch (st.type) {
|
|
351
|
+
case "EmptyStatement":
|
|
352
|
+
continue;
|
|
353
|
+
case "ReturnStatement":
|
|
354
|
+
if (!st.argument)
|
|
355
|
+
fail(this.comp, "FV1903", "`return` without a value returns `undefined`: return the value the function gives on this path", st);
|
|
356
|
+
this.dead(rest, "`return`");
|
|
357
|
+
return this.loops.length ? { type: "FvStep", kind: "return", argument: st.argument, ...spot(st) } : st.argument;
|
|
358
|
+
case "BreakStatement":
|
|
359
|
+
this.dead(rest, "`break`");
|
|
360
|
+
return this.go(brk, st);
|
|
361
|
+
case "ContinueStatement":
|
|
362
|
+
this.dead(rest, "`continue`");
|
|
363
|
+
return this.go(this.loops.at(-1), st);
|
|
364
|
+
case "ForOfStatement":
|
|
365
|
+
return this.forOf(st, after);
|
|
366
|
+
case "ForStatement":
|
|
367
|
+
return this.forCount(st, after);
|
|
368
|
+
case "BlockStatement":
|
|
369
|
+
return this.run(st.body, after, brk, st);
|
|
370
|
+
case "IfStatement": {
|
|
371
|
+
const yes = this.run(this.branch(st.consequent), after, brk, st);
|
|
372
|
+
const no = st.alternate ? this.run(this.branch(st.alternate), after, brk, st) : this.go(after, st);
|
|
373
|
+
return { type: "ConditionalExpression", test: st.test, consequent: yes, alternate: no, fvIf: true, ...spot(st) };
|
|
374
|
+
}
|
|
375
|
+
case "SwitchStatement":
|
|
376
|
+
return this.switched(st, after);
|
|
377
|
+
case "VariableDeclaration": {
|
|
378
|
+
if (st.kind !== "const" && st.kind !== "let")
|
|
379
|
+
fail(this.comp, "FV1901", `${this.whose} may not hold \`${st.kind}\`: declare the value with \`const\``, st);
|
|
380
|
+
const body = this.run(rest, k, brk, st);
|
|
381
|
+
return st.declarations.reduceRight((inner, d) => this.bind(d, inner), body);
|
|
382
|
+
}
|
|
383
|
+
case "ExpressionStatement":
|
|
384
|
+
if (st.expression.type === "AssignmentExpression" || st.expression.type === "UpdateExpression")
|
|
385
|
+
return this.assign(st.expression, this.run(rest, k, brk, st));
|
|
386
|
+
return fail(this.comp, "FV1901", `${this.whose} may not hold an expression statement (\`${st.expression.type}\`), whose value is dropped and which could only change something: ${SUBSET}`, st);
|
|
387
|
+
default: {
|
|
388
|
+
if (LOOPS[st.type])
|
|
389
|
+
return fail(this.comp, "FV1901", `${this.whose} may not hold ${LOOPS[st.type]}`, st);
|
|
390
|
+
return fail(this.comp, "FV1901", `${this.whose} may not hold ${OTHERS[st.type] ?? `\`${st.type}\``}: ${SUBSET}`, st);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return this.go(k, from);
|
|
395
|
+
}
|
|
396
|
+
assign(e, body) {
|
|
397
|
+
const target = targetOf(e);
|
|
398
|
+
const at = spot(e);
|
|
399
|
+
let value;
|
|
400
|
+
if (e.type === "UpdateExpression") {
|
|
401
|
+
value = { type: "BinaryExpression", operator: e.operator[0], left: { ...target }, right: { type: "NumericLiteral", value: 1, ...at }, ...at };
|
|
402
|
+
}
|
|
403
|
+
else if (e.operator === "=") {
|
|
404
|
+
value = e.right;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
const op = e.operator.slice(0, -1);
|
|
408
|
+
value = { type: op === "&&" || op === "||" || op === "??" ? "LogicalExpression" : "BinaryExpression", operator: op, left: { ...target }, right: e.right, ...at };
|
|
409
|
+
}
|
|
410
|
+
return { type: "FvSet", name: target.name, js: target.fvName ?? target.name, value, count: e.type === "UpdateExpression" ? e.operator : undefined, body, ...at };
|
|
411
|
+
}
|
|
412
|
+
loopBody(sts, update, from) {
|
|
413
|
+
const next = { sts: [], next: null, brk: undefined, leaf: { type: "FvStep", kind: "next" } };
|
|
414
|
+
const cont = update ? { sts: [{ type: "ExpressionStatement", expression: update, ...spot(update) }], next, brk: undefined } : next;
|
|
415
|
+
const brk = { sts: [], next: null, brk: undefined, leaf: { type: "FvStep", kind: "break" } };
|
|
416
|
+
this.loops.push(cont);
|
|
417
|
+
try {
|
|
418
|
+
return this.run(sts, cont, brk, from);
|
|
419
|
+
}
|
|
420
|
+
finally {
|
|
421
|
+
this.loops.pop();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
carried(st, head) {
|
|
425
|
+
const local = new Set(head);
|
|
426
|
+
walk(st.body, (n) => {
|
|
427
|
+
if (FUNCTIONS.has(n.type))
|
|
428
|
+
return false;
|
|
429
|
+
if (n.type === "VariableDeclaration")
|
|
430
|
+
for (const d of n.declarations)
|
|
431
|
+
for (const name of patternNames(d.id))
|
|
432
|
+
local.add(name);
|
|
433
|
+
return true;
|
|
434
|
+
});
|
|
435
|
+
const assigned = assignedIn(st.body);
|
|
436
|
+
if (st.update)
|
|
437
|
+
for (const name of assignedIn(st.update))
|
|
438
|
+
assigned.add(name);
|
|
439
|
+
return [...assigned].filter((name) => !local.has(name));
|
|
440
|
+
}
|
|
441
|
+
forOf(st, after) {
|
|
442
|
+
if (st.await)
|
|
443
|
+
fail(this.comp, "FV1909", "`for await`, which waits on promises a render cannot wait on: iterate the values themselves", st);
|
|
444
|
+
const decl = st.left;
|
|
445
|
+
const d = decl.type === "VariableDeclaration" && (decl.kind === "const" || decl.kind === "let") && decl.declarations.length === 1 ? decl.declarations[0] : null;
|
|
446
|
+
if (!d || d.init)
|
|
447
|
+
fail(this.comp, "FV1909", "a `for…of` loop declares its item in its head, as `for (const x of list)`", decl);
|
|
448
|
+
const id = d.id;
|
|
449
|
+
const right = st.right;
|
|
450
|
+
const entries = id.type === "ArrayPattern" &&
|
|
451
|
+
right.type === "CallExpression" &&
|
|
452
|
+
right.arguments.length === 0 &&
|
|
453
|
+
right.callee.type === "MemberExpression" &&
|
|
454
|
+
!right.callee.computed &&
|
|
455
|
+
right.callee.property.name === "entries";
|
|
456
|
+
let index = null;
|
|
457
|
+
let pattern = id;
|
|
458
|
+
if (entries) {
|
|
459
|
+
const [i, x] = id.elements;
|
|
460
|
+
if (id.elements.length > 2 || (i && i.type !== "Identifier") || x?.type === "RestElement") {
|
|
461
|
+
fail(this.comp, "FV1909", "`for (const [i, x] of list.entries())` names the index and the item: `[i, x]`, `[i]` or `[, x]`", id);
|
|
462
|
+
}
|
|
463
|
+
index = i?.name ?? null;
|
|
464
|
+
pattern = x ?? null;
|
|
465
|
+
}
|
|
466
|
+
const item = `item#${++renames}`;
|
|
467
|
+
const states = this.carried(st, patternNames(id));
|
|
468
|
+
const nested = this.loops.length > 0;
|
|
469
|
+
const inner = this.loopBody(this.branch(st.body), null, st);
|
|
470
|
+
const body = pattern ? this.pattern(pattern, { type: "Identifier", name: item, ...spot(right) }, inner, d) : inner;
|
|
471
|
+
const over = entries ? right.callee.object : right;
|
|
472
|
+
const itemHint = pattern?.type === "Identifier" ? (pattern.fvName ?? pattern.name) : "item";
|
|
473
|
+
return { type: "FvLoop", kind: "of", over, entries, item, itemHint, index, body, after: this.go(after, st), states, nested, ...spot(st) };
|
|
474
|
+
}
|
|
475
|
+
forCount(st, after) {
|
|
476
|
+
const init = st.init;
|
|
477
|
+
const d = init?.type === "VariableDeclaration" && init.kind === "let" && init.declarations.length === 1 ? init.declarations[0] : null;
|
|
478
|
+
const counter = d?.id.type === "Identifier" && d.init ? d.id : null;
|
|
479
|
+
const by = counter ? stepOf(st.update, counter.name) : 0;
|
|
480
|
+
const test = st.test;
|
|
481
|
+
const order = test?.type === "BinaryExpression" && test.left.type === "Identifier" && test.left.name === counter?.name ? test.operator : "";
|
|
482
|
+
const rising = order === "<" || order === "<=";
|
|
483
|
+
const falling = order === ">" || order === ">=";
|
|
484
|
+
if (!counter || !((rising && by > 0) || (falling && by < 0))) {
|
|
485
|
+
fail(this.comp, "FV1909", `a \`for\` loop that is no counting loop, which the compiler cannot bound: ${COUNT}`, st);
|
|
486
|
+
}
|
|
487
|
+
const name = counter.fvName ?? counter.name;
|
|
488
|
+
if (assignedIn(st.body).has(counter.name))
|
|
489
|
+
fail(this.comp, "FV1909", `the body of a counting loop assigns \`${name}\`, which the loop's head alone counts: ${COUNT}`, st.body);
|
|
490
|
+
const states = this.carried(st, []);
|
|
491
|
+
const limit = test.right;
|
|
492
|
+
const changed = reads(limit, new Set(states));
|
|
493
|
+
if (changed)
|
|
494
|
+
fail(this.comp, "FV1909", `the bound of a counting loop reads \`${changed.fvName ?? changed.name}\`, which the loop changes, so the compiler cannot bound it: ${COUNT}`, changed);
|
|
495
|
+
const nested = this.loops.length > 0;
|
|
496
|
+
const body = this.loopBody(this.branch(st.body), st.update, st);
|
|
497
|
+
const loop = { type: "FvLoop", kind: "count", counter: counter.name, test, bound: limit, body, after: this.go(after, st), states, nested, ...spot(st) };
|
|
498
|
+
return this.pattern(counter, d.init, loop, d);
|
|
499
|
+
}
|
|
500
|
+
branch(st) {
|
|
501
|
+
return st.type === "BlockStatement" ? st.body : [st];
|
|
502
|
+
}
|
|
503
|
+
dead(rest, after) {
|
|
504
|
+
const first = rest.find((r) => r.type !== "EmptyStatement");
|
|
505
|
+
if (first)
|
|
506
|
+
fail(this.comp, "FV1904", `code after ${after}, which never runs: remove it`, first);
|
|
507
|
+
}
|
|
508
|
+
go(k, from) {
|
|
509
|
+
if (k === null) {
|
|
510
|
+
return fail(this.comp, "FV1903", "the function can reach its end here without a `return`, and JavaScript then returns `undefined`: return a value on every path", from);
|
|
511
|
+
}
|
|
512
|
+
if (k.leaf)
|
|
513
|
+
return { ...k.leaf, ...spot(from) };
|
|
514
|
+
if (k.falls)
|
|
515
|
+
fail(this.comp, "FV1904", "this case falls through into the next one, which runs its statements too: end it with `return` or `break`", k.falls);
|
|
516
|
+
if (k.sts.length && ++this.paths > PATH_BUDGET) {
|
|
517
|
+
fail(this.comp, "FV1905", `the body's \`if\`s and \`switch\`es continue with the statements after them on more than ${PATH_BUDGET} paths, each translated on its own: end the cases in \`return\`, or split the body into \`computed\`s`, from);
|
|
518
|
+
}
|
|
519
|
+
return this.run(k.sts, k.next, k.brk, from);
|
|
520
|
+
}
|
|
521
|
+
bind(d, body) {
|
|
522
|
+
if (!d.init) {
|
|
523
|
+
const name = d.id.fvName ?? d.id.name ?? "a pattern";
|
|
524
|
+
const none = this.assigned.has(d.id.name) ? `, and JavaScript gives it \`undefined\` until it is assigned: give it a value where it is declared, \`let ${name}: T | undefined = undefined\` for none` : ": give it one where it is declared";
|
|
525
|
+
fail(this.comp, "FV1901", `\`${name}\` is declared without a value${none}`, d);
|
|
526
|
+
}
|
|
527
|
+
const typed = d.id.typeAnnotation?.typeAnnotation;
|
|
528
|
+
const init = typed ? typedBy(d.init, typed) : d.init;
|
|
529
|
+
return this.pattern(d.id, init, body, d);
|
|
530
|
+
}
|
|
531
|
+
pattern(p, value, body, d) {
|
|
532
|
+
switch (p.type) {
|
|
533
|
+
case "Identifier":
|
|
534
|
+
if (this.assigned.has(p.name))
|
|
535
|
+
return { type: "FvLet", name: p.name, js: p.fvName ?? p.name, init: value, body, mutable: true, declared: p.typeAnnotation?.typeAnnotation, ...spot(d) };
|
|
536
|
+
return { type: "FvLet", name: p.name, js: p.fvName ?? p.name, init: value, body, ...spot(d) };
|
|
537
|
+
case "ObjectPattern":
|
|
538
|
+
case "ArrayPattern": {
|
|
539
|
+
const tmp = `fv#${++renames}`;
|
|
540
|
+
const ref = () => ({ type: "Identifier", name: tmp, ...spot(value) });
|
|
541
|
+
const parts = [];
|
|
542
|
+
if (p.type === "ObjectPattern") {
|
|
543
|
+
for (const q of p.properties) {
|
|
544
|
+
if (q.type === "RestElement" || q.computed)
|
|
545
|
+
fail(this.comp, "FV1901", "a destructuring pattern names each field it reads, without `...rest` or a computed key", q);
|
|
546
|
+
const key = q.key.type === "Identifier" ? q.key.name : String(q.key.value);
|
|
547
|
+
parts.push([q.value, { type: "MemberExpression", computed: false, object: ref(), property: { type: "Identifier", name: key, ...spot(q.key) }, ...spot(q) }]);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
p.elements.forEach((q, i) => {
|
|
552
|
+
if (!q)
|
|
553
|
+
return;
|
|
554
|
+
if (q.type === "RestElement")
|
|
555
|
+
fail(this.comp, "FV1901", "a destructuring pattern names each item it reads, without `...rest`", q);
|
|
556
|
+
parts.push([q, { type: "MemberExpression", computed: true, object: ref(), property: { type: "NumericLiteral", value: i, ...spot(q) }, ...spot(q) }]);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
for (const [q] of parts)
|
|
560
|
+
if (q.type === "AssignmentPattern")
|
|
561
|
+
fail(this.comp, "FV1901", "a default in a destructuring pattern, which applies to `undefined` alone: write `?? fallback` where it is read", q);
|
|
562
|
+
const inner = parts.reduceRight((acc, [q, v]) => this.pattern(q, v, acc, d), body);
|
|
563
|
+
return { type: "FvLet", name: tmp, js: DESTRUCTURED, init: value, body: inner, ...spot(d) };
|
|
564
|
+
}
|
|
565
|
+
default:
|
|
566
|
+
return fail(this.comp, "FV1901", "a declaration binds a name, or an object or array pattern of names", p);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
switched(st, after) {
|
|
570
|
+
const d = `switch#${++renames}`;
|
|
571
|
+
const subject = () => ({ type: "Identifier", name: d, ...spot(st.discriminant) });
|
|
572
|
+
const groups = [];
|
|
573
|
+
let open = null;
|
|
574
|
+
for (const c of st.cases) {
|
|
575
|
+
if (!open) {
|
|
576
|
+
open = { tests: [], body: [], dflt: false, at: c };
|
|
577
|
+
groups.push(open);
|
|
578
|
+
}
|
|
579
|
+
if (c.test)
|
|
580
|
+
open.tests.push(c.test);
|
|
581
|
+
else
|
|
582
|
+
open.dflt = true;
|
|
583
|
+
const lexical = c.consequent.find((x) => x.type === "VariableDeclaration" || x.type === "FunctionDeclaration" || x.type === "ClassDeclaration");
|
|
584
|
+
if (lexical)
|
|
585
|
+
fail(this.comp, "FV1904", "a declaration directly in a `case`, which the whole `switch` shares: wrap the case's statements in `{ … }`", lexical);
|
|
586
|
+
if (c.consequent.some((x) => x.type !== "EmptyStatement")) {
|
|
587
|
+
open.body = c.consequent;
|
|
588
|
+
open = null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const brk = after;
|
|
592
|
+
const bodyOf = (g, i) => {
|
|
593
|
+
const last = i === groups.length - 1;
|
|
594
|
+
const k = last ? after : { sts: [], next: null, brk: undefined, falls: g.body.at(-1) ?? g.at };
|
|
595
|
+
if (!g.body.length)
|
|
596
|
+
return last ? this.go(after, g.at) : this.go(k, g.at);
|
|
597
|
+
return this.run(g.body, k, brk, g.body.at(-1));
|
|
598
|
+
};
|
|
599
|
+
const fallback = groups.findIndex((g) => g.dflt);
|
|
600
|
+
let chain = fallback >= 0 ? bodyOf(groups[fallback], fallback) : this.go(after, st);
|
|
601
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
602
|
+
const g = groups[i];
|
|
603
|
+
if (i === fallback)
|
|
604
|
+
continue;
|
|
605
|
+
const test = g.tests
|
|
606
|
+
.map((t) => ({ type: "BinaryExpression", operator: "===", left: subject(), right: t, ...spot(t) }))
|
|
607
|
+
.reduce((a, b) => ({ type: "LogicalExpression", operator: "||", left: a, right: b, ...spot(b) }));
|
|
608
|
+
chain = { type: "ConditionalExpression", test, consequent: bodyOf(g, i), alternate: chain, ...spot(g.at) };
|
|
609
|
+
}
|
|
610
|
+
return { type: "FvLet", name: d, js: "the `switch`'s value", init: st.discriminant, body: chain, ...spot(st) };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const desugared = new WeakMap();
|
|
614
|
+
/** The expression a function's body computes: the expression of an arrow function, or a block of
|
|
615
|
+
* statements in the subset ferrovue translates, written as one expression. */
|
|
616
|
+
export function bodyExpr(comp, fn, whose = "a function's body") {
|
|
617
|
+
if (fn.body.type !== "BlockStatement")
|
|
618
|
+
return fn.body;
|
|
619
|
+
const done = desugared.get(fn);
|
|
620
|
+
if (done)
|
|
621
|
+
return done;
|
|
622
|
+
const shadowed = new Map();
|
|
623
|
+
const renamed = new Renamer(comp).statements(fn.body.body, shadowed);
|
|
624
|
+
const assigned = assignments(comp, renamed, whose);
|
|
625
|
+
const out = new Desugar(comp, whose, assigned).run(renamed, null, undefined, fn.body.body.at(-1) ?? fn);
|
|
626
|
+
desugared.set(fn, out);
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
629
|
+
/** `p` (a name, or an object or array pattern of names) bound to `value` around `body`. */
|
|
630
|
+
export function destructured(comp, p, value, body) {
|
|
631
|
+
return new Desugar(comp).pattern(p, value, body, p);
|
|
632
|
+
}
|
|
633
|
+
/** `fn` with an expression for its body, where it is an arrow or function expression with a block. */
|
|
634
|
+
export function expressionBodied(comp, fn) {
|
|
635
|
+
if ((fn?.type !== "ArrowFunctionExpression" && fn?.type !== "FunctionExpression") || fn.body.type !== "BlockStatement")
|
|
636
|
+
return fn;
|
|
637
|
+
return { ...fn, body: bodyExpr(comp, fn) };
|
|
638
|
+
}
|
|
639
|
+
function simple(v) {
|
|
640
|
+
if (v.iter !== undefined || v.held !== undefined || v.format !== undefined)
|
|
641
|
+
return false;
|
|
642
|
+
if (nothing(v.ty))
|
|
643
|
+
return true;
|
|
644
|
+
if (v.variant !== undefined)
|
|
645
|
+
return /^[A-Za-z_]\w*(?:\.\w+)*$/.test(v.variant);
|
|
646
|
+
return /^(?:&\*?|\*)?[A-Za-z_]\w*(?:\.\w+)*$/.test(v.code) || /^"(?:[^"\\]|\\.)*"$/.test(v.code) || /^-?\d[\w.]*$/.test(v.code);
|
|
647
|
+
}
|
|
648
|
+
export function reps(v) {
|
|
649
|
+
return [v.code, v.iter, v.held, v.f64, ...(v.format?.args ?? [])].filter((r) => r !== undefined);
|
|
650
|
+
}
|
|
651
|
+
function ownedCow(v) {
|
|
652
|
+
if (/^&\*fv_m\d+$/.test(v.code))
|
|
653
|
+
return `${COW}::Owned(${v.code.slice(2)})`;
|
|
654
|
+
if (!isTemporary(v))
|
|
655
|
+
return /^&\*\w+(?:\.[\w#]+)+$/.test(v.code) ? `${COW}::Owned(str::to_owned(${v.code}))` : `${COW}::Owned(${atom(v.code).replace(/^&\*?/, "")}.to_owned())`;
|
|
656
|
+
const cow = asCow(v);
|
|
657
|
+
return cow.startsWith(`${COW}::Owned(`) ? cow : `${COW}::Owned(${atom(cow)}.into_owned())`;
|
|
658
|
+
}
|
|
659
|
+
/** `const name = init` around `body`: the value `body` computes with `name` bound once, as a Rust
|
|
660
|
+
* `let` in a block that hands out nothing borrowed from it. */
|
|
661
|
+
export function letIn(s, n) {
|
|
662
|
+
let init = n.init;
|
|
663
|
+
while (init?.type === "ParenthesizedExpression" || init?.type === "TSAsExpression" || init?.type === "TSSatisfiesExpression")
|
|
664
|
+
init = init.expression;
|
|
665
|
+
if (n.mutable)
|
|
666
|
+
return mutableLet(s, n, init);
|
|
667
|
+
if (init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression") {
|
|
668
|
+
const fn = { name: n.js, node: init, home: s.comp, file: null };
|
|
669
|
+
const inner = {
|
|
670
|
+
...s,
|
|
671
|
+
functions: new Map(s.functions).set(n.name, fn),
|
|
672
|
+
clientOnly: new Map(s.clientOnly).set(n.name, "it is a function declared in a body, which that body may call, and not use as a value"),
|
|
673
|
+
};
|
|
674
|
+
fn.captured = inner;
|
|
675
|
+
return expr(inner, n.body);
|
|
676
|
+
}
|
|
677
|
+
return bound(s, n.name, expr(s, n.init), (inner) => expr(inner, n.body), n.js === DESTRUCTURED ? "destructured" : jsHint(n.js));
|
|
678
|
+
}
|
|
679
|
+
/** `v` bound once to `name` for `body`, which is given the scope that reads it. */
|
|
680
|
+
export function bound(s, local, v, then, hint = jsHint(local)) {
|
|
681
|
+
if (simple(v))
|
|
682
|
+
return then({ ...s, locals: new Map(s.locals).set(local, v) });
|
|
683
|
+
const name = fresh("c", hint);
|
|
684
|
+
const { init, read, owned } = bindingOf(v);
|
|
685
|
+
const held = read(name);
|
|
686
|
+
const body = then({ ...s, locals: new Map(s.locals).set(local, held) });
|
|
687
|
+
if (reps(body).join("\0") === reps(held).join("\0"))
|
|
688
|
+
return v;
|
|
689
|
+
const uses = reps(body).some((r) => occurrences(r, name) > 0);
|
|
690
|
+
if (!uses && !panicky(init))
|
|
691
|
+
return body;
|
|
692
|
+
return around(`let ${uses ? name : "_"} = ${init};`, body, owned, !!v.owned && uses);
|
|
693
|
+
}
|
|
694
|
+
/** `body` after the Rust statements `lead`, as a block that hands out nothing borrowed from what
|
|
695
|
+
* they declare: `owned` when a statement owns what `body` may borrow (a `String`, a `Cow`, a
|
|
696
|
+
* `Vec`), `ownedUses` when it holds a struct or a list `body` reads. */
|
|
697
|
+
export function around(lead, body, owned, ownedUses = false) {
|
|
698
|
+
const block = (code) => `{ ${lead} ${bare(code)} }`;
|
|
699
|
+
const lone = body.lone ? { lone: true } : {};
|
|
700
|
+
if (body.ty.k === "step")
|
|
701
|
+
return { code: body.code === "{}" ? `{ ${lead} }` : block(body.code), ty: body.ty };
|
|
702
|
+
if (body.owned)
|
|
703
|
+
return { code: `(${block(body.code)})`, ty: body.ty, owned: true, ...lone };
|
|
704
|
+
if (ownedUses) {
|
|
705
|
+
const of = body.ty.k === "opt" || body.ty.k === "list" ? body.ty.of : body.ty;
|
|
706
|
+
if (of.k === "struct" && body.ty.k === "struct")
|
|
707
|
+
return { code: `(${block(ownedStruct(body))})`, ty: body.ty, owned: true, ...lone };
|
|
708
|
+
if (of.k === "struct" && body.ty.k === "opt")
|
|
709
|
+
return { code: `(${block(`${atom(body.code)}.cloned()`)})`, ty: body.ty, owned: true, ...lone };
|
|
710
|
+
if (of.k === "struct" && body.ty.k === "list")
|
|
711
|
+
return { code: `(${block(ownedStructs(body))})`, ty: body.ty, owned: true, ...lone };
|
|
712
|
+
if (body.ty.k === "list" && body.iter === undefined)
|
|
713
|
+
return { code: `(${block(`${atom(body.code)}.to_vec()`)})`, ty: body.ty, owned: true, ...lone };
|
|
714
|
+
}
|
|
715
|
+
switch (body.ty.k) {
|
|
716
|
+
case "int":
|
|
717
|
+
case "float":
|
|
718
|
+
case "bool":
|
|
719
|
+
case "undef":
|
|
720
|
+
case "null":
|
|
721
|
+
return { code: block(body.code), ty: body.ty, ...lone, ...(body.f64 !== undefined ? { f64: block(body.f64) } : {}), ...(body.at !== undefined ? { at: body.at } : {}) };
|
|
722
|
+
case "str":
|
|
723
|
+
if (!owned && !isTemporary(body))
|
|
724
|
+
return { code: block(body.code), ty: body.ty, ...lone };
|
|
725
|
+
return { code: `&*${block(owned ? ownedCow(body) : asCow(body))}`, ty: body.ty, ...lone };
|
|
726
|
+
case "list": {
|
|
727
|
+
if (!owned && body.iter === undefined)
|
|
728
|
+
return { code: block(body.code), ty: body.ty, ...(body.slice ? { slice: true } : {}), ...lone };
|
|
729
|
+
const all = block(collected(body));
|
|
730
|
+
return { code: all, ty: body.ty, iter: `${all}.into_iter()`, ...lone };
|
|
731
|
+
}
|
|
732
|
+
case "opt":
|
|
733
|
+
if (body.ty.of.k === "str" && (owned || body.held !== undefined)) {
|
|
734
|
+
const kept = block(owned ? `${atom(body.code)}.map(|v| ${COW}::Owned(v.to_owned()))` : body.held);
|
|
735
|
+
return { code: `${kept}.as_deref()`, ty: body.ty, held: kept, ...lone };
|
|
736
|
+
}
|
|
737
|
+
return { code: block(body.code), ty: body.ty, ...lone };
|
|
738
|
+
default: {
|
|
739
|
+
const ref = /^\w+(?:\.[\w#]+)+$/.test(body.code) ? `&${body.code}` : body.code;
|
|
740
|
+
return { code: `(${block(ref)})`, ty: body.ty, ...lone };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/** Whether a desugared `if` whose test is `a && b` or `a || b` should be split, so that a side
|
|
745
|
+
* that narrows a value narrows what follows it, as TypeScript narrows it. */
|
|
746
|
+
export function splits(s, test) {
|
|
747
|
+
if (test.type !== "LogicalExpression" || (test.operator !== "&&" && test.operator !== "||"))
|
|
748
|
+
return false;
|
|
749
|
+
const narrows = (side) => {
|
|
750
|
+
if (side.type === "LogicalExpression" && side.operator === test.operator)
|
|
751
|
+
return splits(s, side);
|
|
752
|
+
try {
|
|
753
|
+
return presence(s, side) !== null;
|
|
754
|
+
}
|
|
755
|
+
catch (e) {
|
|
756
|
+
if (e instanceof GenError)
|
|
757
|
+
return false;
|
|
758
|
+
throw e;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
return narrows(test.left) || narrows(test.right);
|
|
762
|
+
}
|
|
763
|
+
/** `a && b ? x : y` as `a ? (b ? x : y) : y`, and `a || b ? x : y` as `a ? x : (b ? x : y)`. */
|
|
764
|
+
export function split(n) {
|
|
765
|
+
const { left, right, operator } = n.test;
|
|
766
|
+
const inner = { ...n, test: right };
|
|
767
|
+
return operator === "&&" ? { ...n, test: left, consequent: inner, alternate: n.alternate } : { ...n, test: left, consequent: n.consequent, alternate: inner };
|
|
768
|
+
}
|