@typesol/cli 0.22.0 → 0.24.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 +82 -0
- package/README.md +13 -12
- package/SECURITY.md +1 -1
- package/dist/clientgen.js +190 -41
- package/dist/compiler/analyze.js +195 -25
- package/dist/compiler/emit/base.d.ts +28 -5
- package/dist/compiler/emit/base.js +81 -15
- package/dist/compiler/emit/calls.d.ts +18 -1
- package/dist/compiler/emit/calls.js +159 -7
- package/dist/compiler/emit/cpi.d.ts +23 -0
- package/dist/compiler/emit/cpi.js +274 -5
- package/dist/compiler/emit/expressions.d.ts +9 -1
- package/dist/compiler/emit/expressions.js +25 -12
- package/dist/compiler/emit/function.js +8 -4
- package/dist/compiler/emit/program.js +9 -2
- package/dist/compiler/emit/shared.d.ts +43 -3
- package/dist/compiler/emit/shared.js +43 -0
- package/dist/compiler/emit/statements.d.ts +39 -2
- package/dist/compiler/emit/statements.js +346 -87
- package/dist/compiler/emit/storage.d.ts +6 -2
- package/dist/compiler/emit/storage.js +75 -45
- package/dist/compiler/model.d.ts +26 -1
- package/dist/idlpublish.d.ts +6 -1
- package/dist/idlpublish.js +47 -5
- package/dist/portgen.js +1 -1
- package/dist/svmrpc.d.ts +7 -1
- package/dist/svmrpc.js +133 -19
- package/dist/testing.js +23 -3
- package/docs/cli.md +2 -2
- package/docs/client.md +13 -10
- package/docs/errors.md +9 -3
- package/docs/language.md +21 -20
- package/docs/mainnet.md +1 -1
- package/docs/migrate-from-anchor.md +8 -9
- package/docs/test-your-program.md +6 -2
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +3 -2
- package/lib/typesol.d.ts +127 -20
- package/package.json +1 -1
- package/runtime/typesol_rt.h +73 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { MAX_RETURN_DATA, resolveValueType, structNameOf } from "../analyze.js";
|
|
3
|
-
import { INT_INFO, isWritable, optionKey, scalarSize, typeName } from "../model.js";
|
|
4
|
-
import { U64, BOOL, cString, optC, scalarC, scalarStorageC, valueC } from "./shared.js";
|
|
2
|
+
import { MAX_RETURN_DATA, calleeName, resolveScalarType, resolveValueType, structNameOf, typesolName } from "../analyze.js";
|
|
3
|
+
import { INT_INFO, isScalar, isWritable, optionKey, scalarSize, typeName, } from "../model.js";
|
|
4
|
+
import { U64, BOOL, cString, fixedVecC, optC, scalarC, scalarStorageC, valueC } from "./shared.js";
|
|
5
5
|
import { CpiEmitter } from "./cpi.js";
|
|
6
6
|
/**
|
|
7
7
|
* Statements: variables, assignment, control flow and return.
|
|
@@ -259,37 +259,234 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
259
259
|
n.parent = stmt;
|
|
260
260
|
return n;
|
|
261
261
|
};
|
|
262
|
-
|
|
263
|
-
const length = place(f.createPropertyAccessExpression(stmt.expression, "length"));
|
|
264
|
-
const get = place(f.createCallExpression(place(f.createPropertyAccessExpression(stmt.expression, "get")), undefined, [indexId]));
|
|
265
|
-
// The element's declaration: the source's own name (so aliasing checks find its uses) bound to list.get(i).
|
|
266
|
-
const decl = f.createVariableDeclaration(d.name, undefined, undefined, get);
|
|
267
|
-
ts.setTextRange(decl, d);
|
|
268
|
-
decl.parent = list;
|
|
269
|
-
const declList = { ...list, flags: ts.NodeFlags.Const, declarations: [decl] };
|
|
262
|
+
void place;
|
|
270
263
|
const ci = this.tmp("of_i");
|
|
271
264
|
const label = this.tmp("ts_continue_");
|
|
265
|
+
this.eachElement(stmt.expression, stmt, { index, ci, next: label, alwaysNext: true }, (element) => {
|
|
266
|
+
// The element's declaration: the source's own name (so aliasing checks find its uses) bound to the element.
|
|
267
|
+
const decl = f.createVariableDeclaration(d.name, undefined, undefined, element);
|
|
268
|
+
ts.setTextRange(decl, d);
|
|
269
|
+
decl.parent = list;
|
|
270
|
+
const declList = { ...list, flags: ts.NodeFlags.Const, declarations: [decl] };
|
|
271
|
+
this.variable(declList);
|
|
272
|
+
this.loops.push(label);
|
|
273
|
+
this.line(`{`);
|
|
274
|
+
this.block(() => this.body(stmt.statement));
|
|
275
|
+
this.line(`}`);
|
|
276
|
+
this.loops.pop();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/** The function an identifier names (a closure, a local function or a helper), as something to expand; else undefined. */
|
|
280
|
+
functionNamed(id) {
|
|
281
|
+
const l = this.lookup(id.text);
|
|
282
|
+
if (l)
|
|
283
|
+
return l.closure;
|
|
284
|
+
const name = calleeName(id, this.model.helperDecls, this.checker);
|
|
285
|
+
const h = name ? (this.model.helpers.get(name) ?? this.model.generics.get(name)) : undefined;
|
|
286
|
+
return h ? { node: h.node, name: h.display, scopes: [], env: this.noEnv(), imports: h.imports } : undefined;
|
|
287
|
+
}
|
|
288
|
+
/** `xs.map(…)` / `xs.filter(…)` (or a chain of them): a list built element by element, not stored anywhere yet. */
|
|
289
|
+
isPipeline(e) {
|
|
290
|
+
return ts.isCallExpression(e) && ts.isPropertyAccessExpression(e.expression) && (e.expression.name.text === "map" || e.expression.name.text === "filter");
|
|
291
|
+
}
|
|
292
|
+
/** The list under a chain of map / filter calls, and the calls in the order they run. */
|
|
293
|
+
pipelineStages(e) {
|
|
294
|
+
const stages = [];
|
|
295
|
+
let base = e;
|
|
296
|
+
while (this.isPipeline(base)) {
|
|
297
|
+
const access = base.expression;
|
|
298
|
+
const kind = access.name.text;
|
|
299
|
+
if (base.arguments.length !== 1)
|
|
300
|
+
this.fail(base, `${kind}() takes one argument: the callback`);
|
|
301
|
+
const fn = base.arguments[0];
|
|
302
|
+
if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
|
|
303
|
+
this.fail(fn, `pass ${kind} an arrow function written here: xs.${kind}((x) => …)`);
|
|
304
|
+
if (fn.parameters.length > 2)
|
|
305
|
+
this.fail(fn.parameters[2], `${kind}'s callback takes the element and its index`);
|
|
306
|
+
stages.unshift({ kind, fn });
|
|
307
|
+
base = access.expression;
|
|
308
|
+
}
|
|
309
|
+
if (stages.length) {
|
|
310
|
+
const l = ts.isIdentifier(base) ? this.lookup(base.text) : undefined;
|
|
311
|
+
if (!(l?.type.kind === "vecArg" || l?.type.kind === "array" || this.peekArray(base)))
|
|
312
|
+
this.fail(base, "map and filter work on a Vec, FixedArray or FixedVec");
|
|
313
|
+
}
|
|
314
|
+
return { base, stages };
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Run `body` for each element of a list, or of a chain of `map` / `filter` calls on one (`xs.filter(f).map(g)`):
|
|
318
|
+
* one index loop over the list (its length read every step, as `for … of`), each callback expanded in it. A
|
|
319
|
+
* filter skips to the next element, a map binds its result, so a chain builds no list. `body` gets the element (as
|
|
320
|
+
* `list.get(i)` gives it, structs by reference, or the mapped value) and its index in the chain's output (after a
|
|
321
|
+
* filter, the count of elements it let through, as JS gives callbacks).
|
|
322
|
+
*/
|
|
323
|
+
eachElement(list, at, names, body) {
|
|
324
|
+
const f = ts.factory;
|
|
325
|
+
const place = (n) => {
|
|
326
|
+
ts.setTextRange(n, at);
|
|
327
|
+
n.parent = at;
|
|
328
|
+
return n;
|
|
329
|
+
};
|
|
330
|
+
const { base, stages } = this.pipelineStages(list);
|
|
331
|
+
const { index, ci } = names;
|
|
332
|
+
const indexId = place(f.createIdentifier(index));
|
|
333
|
+
const length = place(f.createPropertyAccessExpression(base, "length"));
|
|
334
|
+
const get = place(f.createCallExpression(place(f.createPropertyAccessExpression(base, "get")), undefined, [indexId]));
|
|
335
|
+
const next = names.next ?? (stages.some((s) => s.kind === "filter") ? this.tmp("ts_next_") : undefined);
|
|
272
336
|
this.line(`{`);
|
|
273
337
|
this.block(() => {
|
|
274
338
|
this.line(`uint64_t ${ci} = 0;`);
|
|
275
339
|
this.scopes.at(-1).set(index, { c: ci, type: U64, mutable: false });
|
|
340
|
+
const counters = stages
|
|
341
|
+
.filter((st) => st.kind === "filter")
|
|
342
|
+
.map(() => {
|
|
343
|
+
const k = this.tmp("lm_k");
|
|
344
|
+
this.line(`uint64_t ${k} = 0;`);
|
|
345
|
+
return k;
|
|
346
|
+
});
|
|
276
347
|
this.line(`while (1) {`);
|
|
277
348
|
this.block(() => {
|
|
278
|
-
const n = this.expect(this.expr(length, U64), U64,
|
|
349
|
+
const n = this.expect(this.expr(length, U64), U64, base);
|
|
279
350
|
this.line(`if (!(${ci} < ${n.c})) break;`);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
351
|
+
let element = get;
|
|
352
|
+
let idx = indexId;
|
|
353
|
+
let k = 0;
|
|
354
|
+
for (const st of stages) {
|
|
355
|
+
if (st.kind === "map") {
|
|
356
|
+
const ret = st.fn.body && !ts.isBlock(st.fn.body) ? st.fn.body : undefined;
|
|
357
|
+
const bare = ret && ts.isParenthesizedExpression(ret) ? ret.expression : ret;
|
|
358
|
+
if (bare && ts.isObjectLiteralExpression(bare))
|
|
359
|
+
this.fail(st.fn, "a map to structs is stored, with the struct type: const ys: FixedVec<MyStruct, N> = xs.map((x) => ({ … }))");
|
|
360
|
+
}
|
|
361
|
+
const closure = { node: st.fn, name: `${st.kind}'s callback`, scopes: [...this.scopes], env: this.envNow(), imports: this.scope.imports };
|
|
362
|
+
const v = this.expandClosure(closure, [element, idx].slice(0, st.fn.parameters.length), st.fn);
|
|
363
|
+
if (st.kind === "filter") {
|
|
364
|
+
if (!v)
|
|
365
|
+
this.fail(st.fn, "filter's callback returns a boolean");
|
|
366
|
+
const b = this.expect(v, BOOL, st.fn);
|
|
367
|
+
this.line(`if (!(${b.c})) goto ${next};`);
|
|
368
|
+
const id = `__lk${this.temp++}`;
|
|
369
|
+
const ic = this.tmp("lm_ki");
|
|
370
|
+
this.line(`uint64_t ${ic} = ${counters[k++]}++;`);
|
|
371
|
+
this.scopes.at(-1).set(id, { c: ic, type: U64, mutable: false });
|
|
372
|
+
idx = place(f.createIdentifier(id));
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
if (!v)
|
|
376
|
+
this.fail(st.fn, "map's callback returns the new element");
|
|
377
|
+
const mv = v.literal !== undefined ? this.literal(v.literal, U64, st.fn) : v;
|
|
378
|
+
if (!["int", "bool", "pubkey", "enum"].includes(mv.type.kind))
|
|
379
|
+
this.fail(st.fn, `map's callback returns integers, booleans, Pubkeys or enums (the new list's elements), not ${typeName(mv.type)}`);
|
|
380
|
+
const id = `__lv${this.temp++}`;
|
|
381
|
+
const mc = this.tmp("lm_v");
|
|
382
|
+
this.line(mv.type.kind === "pubkey" ? `SolPubkey ${mc} = *(${mv.c});` : `${valueC(mv.type)} ${mc} = ${mv.c};`);
|
|
383
|
+
this.scopes.at(-1).set(id, { c: mc, type: mv.type, mutable: false });
|
|
384
|
+
element = place(f.createIdentifier(id));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
body(element, idx);
|
|
388
|
+
if (next && (names.alwaysNext || counters.length))
|
|
389
|
+
this.line(`${next}:;`);
|
|
287
390
|
this.line(`${ci}++;`);
|
|
288
391
|
});
|
|
289
392
|
this.line(`}`);
|
|
290
393
|
});
|
|
291
394
|
this.line(`}`);
|
|
292
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* `const ys = xs.map(…)` / `xs.filter(…)`: the chain's elements in a local FixedVec, as big as the list it starts
|
|
398
|
+
* from (a FixedArray's length, a FixedVec's capacity) or as annotated (`const ys: FixedVec<u64, 16> = …`, needed
|
|
399
|
+
* when it starts from a Vec). It lives on the stack, so it holds at most 2 KiB; one more element than it holds aborts.
|
|
400
|
+
*/
|
|
401
|
+
localList(d, init, c) {
|
|
402
|
+
const { base, stages } = this.pipelineStages(init);
|
|
403
|
+
let cap;
|
|
404
|
+
let annotatedElem;
|
|
405
|
+
if (d.type) {
|
|
406
|
+
const t = d.type;
|
|
407
|
+
const args = ts.isTypeReferenceNode(t) ? (t.typeArguments ?? []) : [];
|
|
408
|
+
const n = args[1] && ts.isLiteralTypeNode(args[1]) && ts.isNumericLiteral(args[1].literal) ? Number(args[1].literal.text) : NaN;
|
|
409
|
+
const elemNode = args[0];
|
|
410
|
+
const sn = elemNode && structNameOf(elemNode, this.scope);
|
|
411
|
+
const fs = elemNode && typesolName(elemNode, this.scope.imports) === "FixedString" ? elemNode.typeArguments?.[0] : undefined;
|
|
412
|
+
const fcap = fs && ts.isLiteralTypeNode(fs) && ts.isNumericLiteral(fs.literal) ? Number(fs.literal.text) : NaN;
|
|
413
|
+
if (Number.isInteger(fcap) && fcap >= 1)
|
|
414
|
+
this.model.fstrings.add(fcap);
|
|
415
|
+
annotatedElem = elemNode
|
|
416
|
+
? (resolveScalarType(elemNode, this.scope) ??
|
|
417
|
+
(sn && this.model.structs.has(sn) ? { kind: "struct", name: sn } : Number.isInteger(fcap) && fcap >= 1 ? { kind: "fstring", cap: fcap } : undefined))
|
|
418
|
+
: undefined;
|
|
419
|
+
if (!ts.isTypeReferenceNode(t) || typesolName(t, this.scope.imports) !== "FixedVec" || !annotatedElem || !Number.isInteger(n) || n < 1)
|
|
420
|
+
this.fail(t, "annotate the new list as FixedVec<T, N> (N a literal)");
|
|
421
|
+
cap = n;
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
const r = this.reporter.guard(() => this.ref(base));
|
|
425
|
+
const l = ts.isIdentifier(base) ? this.lookup(base.text) : undefined;
|
|
426
|
+
if (r?.type.kind === "array")
|
|
427
|
+
cap = r.type.len;
|
|
428
|
+
else if (r?.type.kind === "vec" && r.type.cap !== undefined)
|
|
429
|
+
cap = r.type.cap;
|
|
430
|
+
if (cap === undefined || l?.type.kind === "vecArg")
|
|
431
|
+
this.fail(init, "this list has no fixed size: annotate the new one with its capacity, const ys: FixedVec<T, N> = …");
|
|
432
|
+
}
|
|
433
|
+
// The element type: the last map's result, else the list's own elements (known once the loop is emitted).
|
|
434
|
+
const declAt = this.lines.length;
|
|
435
|
+
this.line(`/* ${c} */`);
|
|
436
|
+
let elem = stages.some((st) => st.kind === "map") ? undefined : this.listElem(base);
|
|
437
|
+
const cap0 = cap;
|
|
438
|
+
// A last map to structs or FixedStrings (annotated) writes each result straight into its slot.
|
|
439
|
+
const last = stages.at(-1);
|
|
440
|
+
if (last?.kind === "map" && annotatedElem && !isScalar(annotatedElem)) {
|
|
441
|
+
const target = annotatedElem;
|
|
442
|
+
const inner = init.expression.expression;
|
|
443
|
+
this.eachElement(inner, init, { index: `__ml${this.temp++}`, ci: this.tmp("ml_i") }, (element, index) => {
|
|
444
|
+
this.line(`if (${c}.len >= ${cap0}) TS_FAIL(TS_ERR_INDEX, "TypeSOL: the new list is full");`);
|
|
445
|
+
const closure = { node: last.fn, name: "map's callback", scopes: [...this.scopes], env: this.envNow(), imports: this.scope.imports };
|
|
446
|
+
this.expandClosure(closure, [element, index].slice(0, last.fn.parameters.length), last.fn, {
|
|
447
|
+
lv: `${c}.items[${c}.len]`,
|
|
448
|
+
type: target,
|
|
449
|
+
writable: true,
|
|
450
|
+
});
|
|
451
|
+
this.line(`${c}.len++;`);
|
|
452
|
+
});
|
|
453
|
+
elem = target;
|
|
454
|
+
}
|
|
455
|
+
else
|
|
456
|
+
this.eachElement(init, init, { index: `__ml${this.temp++}`, ci: this.tmp("ml_i") }, (element) => {
|
|
457
|
+
if (!elem) {
|
|
458
|
+
const v = this.expr(element);
|
|
459
|
+
elem = v.type;
|
|
460
|
+
}
|
|
461
|
+
const target = annotatedElem ?? elem;
|
|
462
|
+
const sub = { lv: `${c}.items[${c}.len]`, type: target, writable: true };
|
|
463
|
+
this.line(`if (${c}.len >= ${cap0}) TS_FAIL(TS_ERR_INDEX, "TypeSOL: the new list is full");`);
|
|
464
|
+
const vt = this.storageValueType(target);
|
|
465
|
+
if (vt)
|
|
466
|
+
this.line(this.writeStorage(sub, this.ownedString(this.expect(this.expr(element, vt), vt, element)).c) + ";");
|
|
467
|
+
else
|
|
468
|
+
this.writeAggregate(sub, element);
|
|
469
|
+
this.line(`${c}.len++;`);
|
|
470
|
+
});
|
|
471
|
+
const t = { kind: "vec", elem: annotatedElem ?? elem, cap: cap0 };
|
|
472
|
+
const bytes = 4 + this.fieldBytes(t.elem) * cap0;
|
|
473
|
+
if (bytes > 2048)
|
|
474
|
+
this.fail(init, `the new list takes ${bytes} bytes; locals live on the 4 KiB stack, so keep it under 2 KiB (a smaller FixedVec<T, N>)`);
|
|
475
|
+
this.model.localVecs.set(fixedVecC(t), t);
|
|
476
|
+
const pad = " ".repeat(this.indent);
|
|
477
|
+
this.lines[declAt] = `${pad}${fixedVecC(t)} ${c};\n${pad}ts_memzero(&${c}, sizeof(${c}));`;
|
|
478
|
+
return t;
|
|
479
|
+
}
|
|
480
|
+
/** The element type of a Vec, FixedArray, FixedVec or Vec argument. */
|
|
481
|
+
listElem(list) {
|
|
482
|
+
const l = ts.isIdentifier(list) ? this.lookup(list.text) : undefined;
|
|
483
|
+
if (l?.type.kind === "vecArg")
|
|
484
|
+
return l.type.elem;
|
|
485
|
+
const r = this.ref(list);
|
|
486
|
+
if (r && (r.type.kind === "array" || r.type.kind === "vec"))
|
|
487
|
+
return r.type.elem;
|
|
488
|
+
this.fail(list, "expected a Vec, FixedArray or FixedVec");
|
|
489
|
+
}
|
|
293
490
|
/** Whether `e` is the remaining accounts (or a const bound to them). */
|
|
294
491
|
isRemaining(e) {
|
|
295
492
|
if (ts.isCallExpression(e) && this.exportName(e.expression) === "remainingAccounts")
|
|
@@ -325,24 +522,43 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
325
522
|
* was declared with, so a block around the call that declares the same name does not change what it reads, and
|
|
326
523
|
* writes to captured `let`s are seen by the enclosing function. Returns the result (undefined for no value).
|
|
327
524
|
*/
|
|
328
|
-
expandClosure(c, args, node) {
|
|
525
|
+
expandClosure(c, args, node, into) {
|
|
329
526
|
const fn = c.node;
|
|
330
527
|
if (this.inlining.has(fn))
|
|
331
528
|
this.fail(node, `${c.name} calls itself: closures cannot be recursive`);
|
|
529
|
+
// Its types are written in its own file: they mean what that file's imports say (the arguments are the caller's).
|
|
530
|
+
const calleeScope = c.imports === this.scope.imports ? this.scope : { ...this.scope, imports: c.imports };
|
|
332
531
|
if (args.length !== fn.parameters.length)
|
|
333
532
|
this.fail(node, `${c.name}() takes ${fn.parameters.length} argument(s)`);
|
|
334
533
|
const params = new Map();
|
|
534
|
+
// Account parameters (of a helper expanded here) name the caller's accounts while the body runs.
|
|
535
|
+
const aliases = new Map();
|
|
335
536
|
fn.parameters.forEach((p, i) => {
|
|
336
537
|
if (!ts.isIdentifier(p.name) || p.initializer || p.dotDotDotToken)
|
|
337
538
|
this.fail(p, "closure parameters are plain names");
|
|
338
539
|
const arg = args[i];
|
|
339
540
|
const name = p.name.text;
|
|
541
|
+
// A function parameter (of a helper expanded here): the function given, expanded where the body calls it.
|
|
542
|
+
if (p.type && ts.isFunctionTypeNode(p.type)) {
|
|
543
|
+
params.set(name, { c: "", type: BOOL, mutable: false, closure: this.closureOf(arg) });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (p.type && ts.isIdentifier(arg) && !this.lookup(arg.text) && this.accounts.has(arg.text) && !resolveValueType(p.type, calleeScope)) {
|
|
547
|
+
aliases.set(name, this.accounts.get(arg.text));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
340
550
|
const c2 = `l_${name}_${this.temp++}`;
|
|
341
|
-
const annotated = p.type ? resolveValueType(p.type,
|
|
551
|
+
const annotated = p.type ? resolveValueType(p.type, calleeScope) : undefined;
|
|
342
552
|
if (!annotated && this.looksLikeStructPlace(arg)) {
|
|
343
553
|
const place = this.structPlace(arg);
|
|
344
554
|
this.line(`TSS_${place.struct} *${c2} = ${place.ptr};`);
|
|
345
|
-
params.set(name, {
|
|
555
|
+
params.set(name, {
|
|
556
|
+
c: c2,
|
|
557
|
+
type: { kind: "structRef", struct: place.struct, writable: place.writable },
|
|
558
|
+
mutable: false,
|
|
559
|
+
paths: this.pathsOf(arg),
|
|
560
|
+
decl: p,
|
|
561
|
+
});
|
|
346
562
|
return;
|
|
347
563
|
}
|
|
348
564
|
let v = this.expr(arg, annotated);
|
|
@@ -358,7 +574,7 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
358
574
|
this.line(`${valueC(type)}${type.kind === "account" ? "" : " "}${c2} = ${this.ownedString(v).c};`);
|
|
359
575
|
params.set(name, { c: c2, type, mutable: true });
|
|
360
576
|
});
|
|
361
|
-
const declared = fn.type && fn.type.kind !== ts.SyntaxKind.VoidKeyword ? resolveValueType(fn.type,
|
|
577
|
+
const declared = fn.type && fn.type.kind !== ts.SyntaxKind.VoidKeyword ? resolveValueType(fn.type, calleeScope) : undefined;
|
|
362
578
|
if (fn.type && fn.type.kind !== ts.SyntaxKind.VoidKeyword && !declared)
|
|
363
579
|
this.fail(fn.type, "closures return integers, booleans, Pubkeys, strings, enums or Options");
|
|
364
580
|
const frame = {
|
|
@@ -368,16 +584,42 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
368
584
|
declAt: this.lines.length,
|
|
369
585
|
pad: " ".repeat(this.indent),
|
|
370
586
|
node: fn,
|
|
587
|
+
into,
|
|
371
588
|
};
|
|
372
589
|
this.line(declared ? `${declared.kind === "pubkey" ? "SolPubkey" : valueC(declared)} ${frame.result};` : `/* ${frame.result} */`);
|
|
373
|
-
|
|
590
|
+
// The body names what it could where it was written: a closure the caller's arguments and accounts, a helper
|
|
591
|
+
// only its own parameters (its account parameters name the caller's accounts) and the module.
|
|
592
|
+
const swap = (m, to) => {
|
|
593
|
+
const before = new Map(m);
|
|
594
|
+
m.clear();
|
|
595
|
+
for (const [k, v] of to)
|
|
596
|
+
m.set(k, v);
|
|
597
|
+
return before;
|
|
598
|
+
};
|
|
599
|
+
const bodyAccounts = new Map(c.env.accounts);
|
|
600
|
+
for (const [n, a] of aliases)
|
|
601
|
+
bodyAccounts.set(n, a);
|
|
602
|
+
// Accounts are found by name before locals: a parameter named like one the body can see would read the account.
|
|
603
|
+
for (const n of params.keys())
|
|
604
|
+
if (bodyAccounts.has(n))
|
|
605
|
+
this.fail(fn.parameters.find((p) => ts.isIdentifier(p.name) && p.name.text === n) ?? node, `"${n}" shadows the account "${n}"; use another name`);
|
|
606
|
+
const saved = {
|
|
607
|
+
scope: this.scope,
|
|
608
|
+
scopes: this.scopes,
|
|
609
|
+
loops: this.loops.splice(0),
|
|
610
|
+
args: swap(this.args, c.env.args),
|
|
611
|
+
accounts: swap(this.accounts, bodyAccounts),
|
|
612
|
+
};
|
|
374
613
|
this.scopes = [...c.scopes, params];
|
|
614
|
+
this.scope = calleeScope;
|
|
375
615
|
this.inlining.add(fn);
|
|
376
616
|
this.inlineFrames.push(frame);
|
|
377
617
|
try {
|
|
378
618
|
this.line(`{`);
|
|
379
619
|
this.block(() => {
|
|
380
620
|
const body = fn.body;
|
|
621
|
+
if (!body)
|
|
622
|
+
this.fail(fn, `${c.name} has no body`);
|
|
381
623
|
if (ts.isBlock(body))
|
|
382
624
|
body.statements.forEach((s) => this.reporter.guard(() => this.statement(s)));
|
|
383
625
|
else {
|
|
@@ -385,7 +627,7 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
385
627
|
const ret = ts.factory.createReturnStatement(body);
|
|
386
628
|
ts.setTextRange(ret, body);
|
|
387
629
|
ret.parent = fn;
|
|
388
|
-
if (ts.isCallExpression(body) && !frame.type) {
|
|
630
|
+
if (ts.isCallExpression(body) && !frame.type && !into) {
|
|
389
631
|
const v = this.call(body, false);
|
|
390
632
|
if (v)
|
|
391
633
|
this.closureResult(v, body, frame);
|
|
@@ -400,7 +642,10 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
400
642
|
finally {
|
|
401
643
|
this.inlineFrames.pop();
|
|
402
644
|
this.inlining.delete(fn);
|
|
645
|
+
swap(this.args, saved.args);
|
|
646
|
+
swap(this.accounts, saved.accounts);
|
|
403
647
|
this.scopes = saved.scopes;
|
|
648
|
+
this.scope = saved.scope;
|
|
404
649
|
this.loops.push(...saved.loops);
|
|
405
650
|
}
|
|
406
651
|
if (!frame.type) {
|
|
@@ -434,9 +679,7 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
434
679
|
this.fail(cb, `pass ${method} an arrow function written here: xs.${method}((x) => …)`);
|
|
435
680
|
const fn = cb;
|
|
436
681
|
const index = `__lm${this.temp++}`;
|
|
437
|
-
|
|
438
|
-
const length = place(f.createPropertyAccessExpression(list, "length"));
|
|
439
|
-
const element = place(f.createCallExpression(place(f.createPropertyAccessExpression(list, "get")), undefined, [indexId]));
|
|
682
|
+
void place;
|
|
440
683
|
const I64 = { kind: "int", name: "i64" };
|
|
441
684
|
// The result, declared before the loop.
|
|
442
685
|
const res = this.tmp("lm");
|
|
@@ -482,69 +725,58 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
482
725
|
}
|
|
483
726
|
const findDecl = method === "find" ? this.lines.length - 1 : -1;
|
|
484
727
|
const ci = this.tmp("lm_i");
|
|
485
|
-
this.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
this.line(`
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
this.
|
|
499
|
-
|
|
728
|
+
this.eachElement(list, node, { index, ci }, (element, indexId) => {
|
|
729
|
+
if (valueForm) {
|
|
730
|
+
const eq = place(f.createBinaryExpression(element, ts.SyntaxKind.EqualsEqualsEqualsToken, place(f.createIdentifier(needle))));
|
|
731
|
+
const hit = this.expect(this.expr(eq, BOOL), BOOL, node);
|
|
732
|
+
this.line(`if (${hit.c}) {`);
|
|
733
|
+
this.line(method === "includes" ? ` ${res} = true;` : ` ${res} = (int64_t)${this.expr(indexId, U64).c};`);
|
|
734
|
+
this.line(` break;`);
|
|
735
|
+
this.line(`}`);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
const closure = { node: fn, name: `${method}'s callback`, scopes: [...this.scopes], env: this.envNow(), imports: this.scope.imports };
|
|
739
|
+
const accId = method === "reduce" ? place(f.createIdentifier(`__acc${this.temp++}`)) : undefined;
|
|
740
|
+
if (accId)
|
|
741
|
+
this.scopes.at(-1).set(accId.text, { c: res, type: accType, mutable: true });
|
|
742
|
+
const all = method === "reduce" ? [accId, element, indexId] : [element, indexId];
|
|
743
|
+
const v = this.expandClosure(closure, all.slice(0, fn.parameters.length), cb);
|
|
744
|
+
if (method === "forEach") {
|
|
745
|
+
if (v)
|
|
746
|
+
this.fail(cb, "forEach's callback returns nothing");
|
|
747
|
+
}
|
|
748
|
+
else if (method === "reduce") {
|
|
749
|
+
if (!v)
|
|
750
|
+
this.fail(cb, "reduce's callback returns the new accumulator");
|
|
751
|
+
const nv = this.expect(v, accType, cb);
|
|
752
|
+
this.line(accType.kind === "pubkey" ? `${res} = *(${nv.c});` : `${res} = ${nv.c};`);
|
|
500
753
|
}
|
|
501
754
|
else {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
this.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
this.line(`if (${b.c}) { ${res} = true; break; }`);
|
|
524
|
-
if (method === "every")
|
|
525
|
-
this.line(`if (!(${b.c})) { ${res} = false; break; }`);
|
|
526
|
-
if (method === "findIndex")
|
|
527
|
-
this.line(`if (${b.c}) { ${res} = (int64_t)${ci}; break; }`);
|
|
528
|
-
if (method === "find") {
|
|
529
|
-
this.line(`if (${b.c}) {`);
|
|
530
|
-
this.block(() => {
|
|
531
|
-
const e = this.expr(element);
|
|
532
|
-
elemType = e.type;
|
|
533
|
-
if (!["int", "bool", "pubkey", "enum"].includes(e.type.kind))
|
|
534
|
-
this.fail(node, "find returns an Option of the element, so the elements must be integers, booleans, Pubkeys or enums; use findIndex");
|
|
535
|
-
this.line(`${res}.some = 1;`);
|
|
536
|
-
this.line(e.type.kind === "pubkey" ? `${res}.v = *(${e.c});` : `${res}.v = ${e.c};`);
|
|
537
|
-
this.line(`break;`);
|
|
538
|
-
});
|
|
539
|
-
this.line(`}`);
|
|
540
|
-
}
|
|
755
|
+
if (!v)
|
|
756
|
+
this.fail(cb, `${method}'s callback returns a boolean`);
|
|
757
|
+
const b = this.expect(v, BOOL, cb);
|
|
758
|
+
if (method === "some")
|
|
759
|
+
this.line(`if (${b.c}) { ${res} = true; break; }`);
|
|
760
|
+
if (method === "every")
|
|
761
|
+
this.line(`if (!(${b.c})) { ${res} = false; break; }`);
|
|
762
|
+
if (method === "findIndex")
|
|
763
|
+
this.line(`if (${b.c}) { ${res} = (int64_t)${this.expr(indexId, U64).c}; break; }`);
|
|
764
|
+
if (method === "find") {
|
|
765
|
+
this.line(`if (${b.c}) {`);
|
|
766
|
+
this.block(() => {
|
|
767
|
+
const e = this.expr(element);
|
|
768
|
+
elemType = e.type;
|
|
769
|
+
if (!["int", "bool", "pubkey", "enum"].includes(e.type.kind))
|
|
770
|
+
this.fail(node, "find returns an Option of the element, so the elements must be integers, booleans, Pubkeys or enums; use findIndex");
|
|
771
|
+
this.line(`${res}.some = 1;`);
|
|
772
|
+
this.line(e.type.kind === "pubkey" ? `${res}.v = *(${e.c});` : `${res}.v = ${e.c};`);
|
|
773
|
+
this.line(`break;`);
|
|
774
|
+
});
|
|
775
|
+
this.line(`}`);
|
|
541
776
|
}
|
|
542
777
|
}
|
|
543
|
-
|
|
544
|
-
});
|
|
545
|
-
this.line(`}`);
|
|
778
|
+
}
|
|
546
779
|
});
|
|
547
|
-
this.line(`}`);
|
|
548
780
|
if (method === "find") {
|
|
549
781
|
const inner = elemType;
|
|
550
782
|
this.model.options.set(optionKey(inner), inner);
|
|
@@ -555,6 +787,17 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
555
787
|
}
|
|
556
788
|
/** `return` inside an expanded closure: the value goes to the closure's result, then past its body. */
|
|
557
789
|
closureReturn(stmt, frame) {
|
|
790
|
+
if (frame.into && stmt.expression) {
|
|
791
|
+
// Written straight into its place (an object literal for a struct, text for a FixedString, or a value).
|
|
792
|
+
const target = frame.into;
|
|
793
|
+
const vt = this.storageValueType(target.type);
|
|
794
|
+
if (vt)
|
|
795
|
+
this.line(this.writeStorage(target, this.ownedString(this.expect(this.expr(stmt.expression, vt), vt, stmt.expression)).c) + ";");
|
|
796
|
+
else
|
|
797
|
+
this.writeAggregate(target, stmt.expression);
|
|
798
|
+
this.line(`goto ${frame.label};`);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
558
801
|
if (!stmt.expression) {
|
|
559
802
|
if (frame.type)
|
|
560
803
|
this.fail(stmt, "this function returns a value");
|
|
@@ -628,7 +871,7 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
628
871
|
c: "",
|
|
629
872
|
type: BOOL,
|
|
630
873
|
mutable: false,
|
|
631
|
-
closure: { node: d.initializer, name: d.name.text, scopes: [...this.scopes] },
|
|
874
|
+
closure: { node: d.initializer, name: d.name.text, scopes: [...this.scopes], env: this.envNow(), imports: this.scope.imports },
|
|
632
875
|
});
|
|
633
876
|
continue;
|
|
634
877
|
}
|
|
@@ -644,12 +887,26 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
644
887
|
this.fail(d.name, `"${name}" shadows the parameter "${name}"; use another name`);
|
|
645
888
|
const c = `l_${name}_${this.temp++}`;
|
|
646
889
|
const init = d.initializer;
|
|
890
|
+
// A function as a value: another name for one (`const h = g`), or the one a function returns (`adder(5n)`).
|
|
891
|
+
const made = ts.isCallExpression(init) ? this.madeFunction(init) : undefined;
|
|
892
|
+
const alias = ts.isIdentifier(init) ? this.functionNamed(init) : undefined;
|
|
893
|
+
if (made || alias) {
|
|
894
|
+
if (mutable)
|
|
895
|
+
this.fail(d, "bind functions with const");
|
|
896
|
+
this.scopes.at(-1).set(name, { c: "", type: BOOL, mutable: false, closure: (made ?? alias) });
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
647
899
|
const fn = ts.isCallExpression(init) ? this.exportName(init.expression) : undefined;
|
|
648
900
|
if (fn === "fixedArray") {
|
|
649
901
|
const t = this.localArray(init, c);
|
|
650
902
|
this.scopes.at(-1).set(name, { c, type: t, mutable });
|
|
651
903
|
continue;
|
|
652
904
|
}
|
|
905
|
+
if (this.isPipeline(init)) {
|
|
906
|
+
const t = this.localList(d, init, c);
|
|
907
|
+
this.scopes.at(-1).set(name, { c, type: t, mutable });
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
653
910
|
if (fn === "loadAccount" || fn === "loadAccountMut" || fn === "loadForeign") {
|
|
654
911
|
if (mutable)
|
|
655
912
|
this.fail(d, "bind loaded accounts with const");
|
|
@@ -851,6 +1108,8 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
851
1108
|
const pieces = this.templatePieces(rhsNode);
|
|
852
1109
|
if (pieces.text !== undefined)
|
|
853
1110
|
this.line(`TS_TRY(ts_str_append(&${cur}, ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})));`);
|
|
1111
|
+
else if (pieces.only !== undefined)
|
|
1112
|
+
this.line(`TS_TRY(ts_str_append(&${cur}, ${pieces.only}));`);
|
|
854
1113
|
else
|
|
855
1114
|
this.line(`TS_TRY(ts_str_append_parts(&${cur}, ${pieces.arr}, ${pieces.n}));`);
|
|
856
1115
|
return;
|
|
@@ -27,8 +27,12 @@ export declare abstract class StorageEmitter extends EmitterBase {
|
|
|
27
27
|
* value. Refuse when a live reference into the replaced place is still used afterwards.
|
|
28
28
|
*/
|
|
29
29
|
protected assertNotAliased(replaced: string[] | undefined, at: ts.Node, helper?: string): void;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Whether the variable declared by `decl` is read after `at` (later in its block, or anywhere in a loop around `at`).
|
|
32
|
+
* A parameter (of a closure or helper expanded here) is read after `at` when its function reads it after `at`; when
|
|
33
|
+
* `at` is in another function (a closure or helper it calls), any read of it in its own function counts.
|
|
34
|
+
*/
|
|
35
|
+
protected usedAfter(decl: ts.VariableDeclaration | ts.ParameterDeclaration, at: ts.Node): boolean;
|
|
32
36
|
/**
|
|
33
37
|
* A pointer to a struct in storage, from `place` or `cond ? placeA : placeB` (same struct type).
|
|
34
38
|
* Returns undefined when the expression is not such a place.
|