@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +13 -12
  3. package/SECURITY.md +1 -1
  4. package/dist/clientgen.js +190 -41
  5. package/dist/compiler/analyze.js +195 -25
  6. package/dist/compiler/emit/base.d.ts +28 -5
  7. package/dist/compiler/emit/base.js +81 -15
  8. package/dist/compiler/emit/calls.d.ts +18 -1
  9. package/dist/compiler/emit/calls.js +159 -7
  10. package/dist/compiler/emit/cpi.d.ts +23 -0
  11. package/dist/compiler/emit/cpi.js +274 -5
  12. package/dist/compiler/emit/expressions.d.ts +9 -1
  13. package/dist/compiler/emit/expressions.js +25 -12
  14. package/dist/compiler/emit/function.js +8 -4
  15. package/dist/compiler/emit/program.js +9 -2
  16. package/dist/compiler/emit/shared.d.ts +43 -3
  17. package/dist/compiler/emit/shared.js +43 -0
  18. package/dist/compiler/emit/statements.d.ts +39 -2
  19. package/dist/compiler/emit/statements.js +346 -87
  20. package/dist/compiler/emit/storage.d.ts +6 -2
  21. package/dist/compiler/emit/storage.js +75 -45
  22. package/dist/compiler/model.d.ts +26 -1
  23. package/dist/idlpublish.d.ts +6 -1
  24. package/dist/idlpublish.js +47 -5
  25. package/dist/portgen.js +1 -1
  26. package/dist/svmrpc.d.ts +7 -1
  27. package/dist/svmrpc.js +133 -19
  28. package/dist/testing.js +23 -3
  29. package/docs/cli.md +2 -2
  30. package/docs/client.md +13 -10
  31. package/docs/errors.md +9 -3
  32. package/docs/language.md +21 -20
  33. package/docs/mainnet.md +1 -1
  34. package/docs/migrate-from-anchor.md +8 -9
  35. package/docs/test-your-program.md +6 -2
  36. package/docs/testing.md +1 -1
  37. package/docs/troubleshooting.md +3 -2
  38. package/lib/typesol.d.ts +127 -20
  39. package/package.json +1 -1
  40. package/runtime/typesol_rt.h +73 -18
@@ -128,7 +128,7 @@ export class ExpressionEmitter extends StorageEmitter {
128
128
  if (ts.isNonNullExpression(node)) {
129
129
  const v = this.expr(node.expression, hint && hint.kind !== "option" ? { kind: "option", inner: hint } : hint);
130
130
  if (v.type.kind !== "option")
131
- this.fail(node, "! works on Option values; check optional accounts with `x !== null`");
131
+ this.fail(node, "! works on Option values and optional accounts (acct!.field); check other values with `x !== null`");
132
132
  return this.unwrap(v, node);
133
133
  }
134
134
  if (ts.isPropertyAccessExpression(node) && node.questionDotToken)
@@ -154,6 +154,8 @@ export class ExpressionEmitter extends StorageEmitter {
154
154
  if (ts.isTemplateExpression(node)) {
155
155
  // `a${x}b${y}c`: one join on the heap (or a literal when every piece is known at compile time).
156
156
  const pieces = this.templatePieces(node);
157
+ if (pieces.only !== undefined)
158
+ return { c: pieces.only, type: STRING };
157
159
  if (pieces.text !== undefined)
158
160
  return { c: `ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})`, type: STRING, stringLiteral: pieces.text };
159
161
  const t = this.tmp("tpl");
@@ -171,9 +173,11 @@ export class ExpressionEmitter extends StorageEmitter {
171
173
  const l = this.lookup(node.text);
172
174
  if (l) {
173
175
  if (l.closure)
174
- this.fail(node, `"${node.text}" is a function: TypeSOL functions are called (${node.text}(…)), not passed around or stored`);
176
+ this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
175
177
  if (l.type.kind === "array")
176
178
  this.fail(node, `"${node.text}" is an array; use .get(i)`);
179
+ if (l.type.kind === "vec")
180
+ this.fail(node, `"${node.text}" is a list; use .get(i) and .length`);
177
181
  if (l.type.kind === "remaining")
178
182
  return { c: "", type: l.type };
179
183
  return l.type.kind === "pubkey" && !l.ptr ? { c: `(&${l.c})`, type: l.type } : { c: l.c, type: l.type };
@@ -202,7 +206,7 @@ export class ExpressionEmitter extends StorageEmitter {
202
206
  if (this.accounts.has(node.text))
203
207
  this.fail(node, `"${node.text}" is an account; use ${node.text}.key for its address`);
204
208
  if (calleeName(node, this.model.helperDecls, this.checker) || this.model.helpers.has(node.text))
205
- this.fail(node, `"${node.text}" is a function: TypeSOL functions are called (${node.text}(…)), not passed around or stored`);
209
+ this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
206
210
  this.fail(node, `unknown name "${node.text}"`);
207
211
  }
208
212
  if (ts.isPropertyAccessExpression(node))
@@ -261,6 +265,13 @@ export class ExpressionEmitter extends StorageEmitter {
261
265
  if (field === "length") {
262
266
  if (this.isRemaining(node.expression))
263
267
  return { c: `((uint64_t)(params->ka_num - ${this.ix.accounts.length}))`, type: U64 };
268
+ // The number of elements a map / filter chain gives: counted, nothing stored.
269
+ if (this.isPipeline(node.expression)) {
270
+ const n = this.tmp("count");
271
+ this.line(`uint64_t ${n} = 0;`);
272
+ this.eachElement(node.expression, node, { index: `__ln${this.temp++}`, ci: this.tmp("ln_i") }, () => this.line(`${n}++;`));
273
+ return { c: n, type: U64 };
274
+ }
264
275
  const arr = this.ref(node.expression);
265
276
  if (arr?.type.kind === "array")
266
277
  return { c: `${arr.type.len}ULL`, type: U64 };
@@ -446,16 +457,20 @@ export class ExpressionEmitter extends StorageEmitter {
446
457
  lit(known);
447
458
  else {
448
459
  const mark = this.lines.length;
449
- const c = this.toText(span.expression);
460
+ const converted = { yes: false };
461
+ const c = this.toText(span.expression, converted);
450
462
  for (const p of parts)
451
463
  if (p.c !== undefined)
452
464
  p.c = this.settle({ c: p.c, type: STRING }, mark).c;
453
- parts.push({ c });
465
+ parts.push({ c, converted: converted.yes });
454
466
  }
455
467
  lit(span.literal.text);
456
468
  }
457
469
  if (parts.every((p) => p.text !== undefined))
458
470
  return { text: parts.map((p) => p.text).join("") };
471
+ // `${n}` alone: the text a conversion just made (or a constant one) is already a string of its own; no join.
472
+ if (parts.length === 1 && parts[0].converted)
473
+ return { only: parts[0].c };
459
474
  const strOf = (text) => `ts_str((const uint8_t *)${cString(text)}, ${Buffer.byteLength(text, "utf8")})`;
460
475
  const arr = this.tmp("parts");
461
476
  this.line(`TS_Str ${arr}[${parts.length}] = {${parts.map((p) => (p.text !== undefined ? strOf(p.text) : p.c)).join(", ")}};`);
@@ -485,10 +500,13 @@ export class ExpressionEmitter extends StorageEmitter {
485
500
  }
486
501
  return undefined;
487
502
  }
488
- toText(node) {
503
+ /** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
504
+ toText(node, converted) {
489
505
  const v = this.expr(node);
490
506
  if (v.type.kind === "string")
491
507
  return v.c;
508
+ if (converted)
509
+ converted.yes = true;
492
510
  if (v.type.kind === "option" && (v.type.inner.kind === "int" || v.type.inner.kind === "bool")) {
493
511
  // `${opt}`: the value, or "null" like JS.
494
512
  const inner = v.type.inner;
@@ -548,12 +566,7 @@ export class ExpressionEmitter extends StorageEmitter {
548
566
  (ts.isBigIntLiteral(node.whenFalse) || ts.isPrefixUnaryExpression(node.whenFalse));
549
567
  if (!type && !bothLiteral && (ts.isBigIntLiteral(node.whenTrue) || node.whenTrue.kind === ts.SyntaxKind.NullKeyword)) {
550
568
  // Type the non-literal branch first, without emitting it, to give the literal its type.
551
- const saved = { lines: this.lines.length, hoisted: this.hoisted.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
552
- const probe = this.reporter.guard(() => this.expr(node.whenFalse));
553
- this.lines.length = saved.lines;
554
- this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
555
- this.temp = saved.temp;
556
- this.reporter.diagnostics.length = saved.diagnostics;
569
+ const probe = this.probe(() => this.expr(node.whenFalse));
557
570
  if (probe && probe.literal === undefined && !probe.isNull)
558
571
  type = probe.type;
559
572
  }
@@ -3,7 +3,7 @@ import { snakeCase } from "../analyze.js";
3
3
  import { decodeAddress } from "../../keys.js";
4
4
  import { INT_INFO, isScalar, isWritable, scalarSize, typeName, } from "../model.js";
5
5
  import { borshIdentical, borshMinSize } from "./borsh.js";
6
- import { MAX_ACCOUNTS, ATA_PROGRAM_BYTES, cBytes, cString, optC, fieldC, storageDecl, helperSignature } from "./shared.js";
6
+ import { MAX_ACCOUNTS, ATA_PROGRAM_BYTES, cBytes, cString, optC, fieldC, storageDecl, helperSignature, emptySizeC, vecLenCheck } from "./shared.js";
7
7
  import { StatementEmitter } from "./statements.js";
8
8
  /**
9
9
  * One instruction or helper as a C function: the instruction prologue (account checks, constraints and
@@ -42,7 +42,7 @@ export class FunctionEmitter extends StatementEmitter {
42
42
  head.push(` TS_TRY(ts_check_writable(${v}));`);
43
43
  // A @layout borsh account only has to hold its smallest Borsh form (unpacking checks the rest).
44
44
  if (k.kind === "account")
45
- head.push(` TS_TRY(ts_check_program_account(${v}, params->program_id, TS_DISC_${k.struct.name}, ${k.struct.layout === "borsh" ? borshMinSize(k.struct, this.model) : `sizeof(TSS_${k.struct.name})`}));`);
45
+ head.push(` TS_TRY(ts_check_program_account(${v}, params->program_id, TS_DISC_${k.struct.name}, ${k.struct.layout === "borsh" ? borshMinSize(k.struct, this.model) : emptySizeC(k.struct)}));`);
46
46
  // A token account created by this instruction is checked where it is created (create/createIfNeeded).
47
47
  if (k.kind === "token" && !k.init)
48
48
  head.push(` TS_TRY(ts_check_token_account(${v}));`);
@@ -106,7 +106,7 @@ export class FunctionEmitter extends StatementEmitter {
106
106
  if (k.struct.vec) {
107
107
  head.push(` uint64_t entry_${a.name} = ${v}->data_len;`, ` (void)entry_${a.name};`);
108
108
  if (k.kind === "account")
109
- head.push(` TS_TRY(ts_check_vec_len(${v}, sizeof(TSS_${k.struct.name}), d_${a.name}->${k.struct.vec.name}__len, TS_ELEM_${k.struct.name}));`);
109
+ head.push(...vecLenCheck(k.struct, v, `d_${a.name}`));
110
110
  }
111
111
  }
112
112
  // realloc(x, …) needs the size the account had when the instruction started (the 10 KiB growth limit).
@@ -150,14 +150,16 @@ export class FunctionEmitter extends StatementEmitter {
150
150
  // The loader passes each account once and marks repeats as duplicates, which share its record (ts_deserialize),
151
151
  // so two parameters name the same account exactly when their pointers are equal.
152
152
  const writable = ix.accounts.filter((a) => isWritable(a.kind));
153
+ const pairs = [];
153
154
  for (let i = 0; i < writable.length; i++)
154
155
  for (let j = i + 1; j < writable.length; j++) {
155
156
  const present = [writable[i], writable[j]]
156
157
  .filter((a) => a.optional)
157
158
  .map((a) => `a_${a.name} && `)
158
159
  .join("");
159
- head.push(` if (${present}a_${writable[i].name} == a_${writable[j].name}) TS_FAIL(TS_ERR_DUPLICATE_MUT, "TypeSOL: ${writable[i].name} and ${writable[j].name} must be different accounts");`);
160
+ pairs.push(`if (${present}a_${writable[i].name} == a_${writable[j].name}) TS_FAIL(TS_ERR_DUPLICATE_MUT, "TypeSOL: ${writable[i].name} and ${writable[j].name} must be different accounts");`);
160
161
  }
162
+ head.push(...pairs.map((p) => ` ${p}`));
161
163
  // Arguments: Borsh, read sequentially (strings make later offsets dynamic).
162
164
  if (ix.args.length)
163
165
  head.push(` uint64_t off = ${ix.discriminator.length};`);
@@ -219,6 +221,8 @@ export class FunctionEmitter extends StatementEmitter {
219
221
  const size = this.fieldBytes(elem);
220
222
  need("4");
221
223
  head.push(` TS_VecArg arg_${a.name};`, ` arg_${a.name}.len = (uint32_t)ts_read_le(${at}, 4);`, ` off += 4;`);
224
+ if (a.type.cap !== undefined)
225
+ head.push(` if (arg_${a.name}.len > ${a.type.cap}) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: more elements than the FixedVec argument holds");`);
222
226
  need(`(uint64_t)arg_${a.name}.len * ${size}`);
223
227
  head.push(` arg_${a.name}.ptr = ${at};`, ` off += (uint64_t)arg_${a.name}.len * ${size};`);
224
228
  // Struct elements are read in place, so check them all now; scalars are checked when read.
@@ -1,4 +1,4 @@
1
- import { MAX_ACCOUNTS, cBytes, scalarStorageC, optC, storageDecl, helperSignature, fixedVecC } from "./shared.js";
1
+ import { MAX_ACCOUNTS, cBytes, scalarStorageC, optC, storageDecl, helperSignature, fixedVecC, laterVecC } from "./shared.js";
2
2
  import { FunctionEmitter } from "./function.js";
3
3
  import { emitBorshCodecs } from "./borsh.js";
4
4
  /**
@@ -96,9 +96,14 @@ export function emitProgram(analysis, opts = {}) {
96
96
  return;
97
97
  emitted.add(s.name);
98
98
  s.fields.forEach((f) => deps(f.type));
99
+ // Growable Vecs after the first live past the C struct (see StructDef.vecs): a header type each.
100
+ const later = new Set((s.vecs ?? []).slice(1).map((v) => v.name));
101
+ for (const v of (s.vecs ?? []).slice(1))
102
+ out.push(`typedef struct __attribute__((packed)) {`, ` uint32_t len;`, ` ${storageDecl(v.elem, "items[]")};`, `} ${laterVecC(s.name, v.name)};`);
99
103
  out.push(`typedef struct __attribute__((packed)) {`);
100
104
  for (const f of s.fields)
101
- out.push(` ${storageDecl(f.type, f.name)};`);
105
+ if (!later.has(f.name))
106
+ out.push(` ${storageDecl(f.type, f.name)};`);
102
107
  if (!s.fields.length)
103
108
  out.push(` uint8_t __empty[0];`); // a marker account (Anchor `pub struct NftAuthority {}`): discriminator only
104
109
  out.push(`} TSS_${s.name};`);
@@ -113,6 +118,8 @@ export function emitProgram(analysis, opts = {}) {
113
118
  emitStruct(s);
114
119
  for (const e of model.dataEnums.values())
115
120
  deps({ kind: "dataEnum", name: e.name });
121
+ for (const t of model.localVecs.values())
122
+ deps(t);
116
123
  for (const k of model.constants.values())
117
124
  if (typeof k.value === "object")
118
125
  out.push(`static const SolPubkey TSK_${k.name} = {${cBytes(k.value.pubkey)}};`);
@@ -1,5 +1,5 @@
1
1
  import ts from "typescript";
2
- import { type AccountParam, type FieldType, type Helper, type IntName, type ParamKind, type ScalarType, type ValueType } from "../model.js";
2
+ import { type AccountParam, type FieldType, type Helper, type IntName, type ParamKind, type ScalarType, type StructDef, type ValueType } from "../model.js";
3
3
  /** Maximum accounts the generated entrypoint deserializes. */
4
4
  export declare const MAX_ACCOUNTS = 32;
5
5
  export declare const MAX_SEEDS = 15;
@@ -46,16 +46,27 @@ export interface Ref {
46
46
  vec?: {
47
47
  len: string;
48
48
  items: string;
49
+ /** The account's size without this Vec's elements (so with `n` of them: 8 + fixed + n × elemSize). */
49
50
  fixed: string;
50
51
  elemSize: number;
51
52
  resizable: boolean;
52
53
  cap?: number;
54
+ /** Growable Vecs stored after this one move when it grows or shrinks: where its elements end, and their paths. */
55
+ tail?: {
56
+ end: string;
57
+ dataEnd: string;
58
+ paths: string[];
59
+ };
53
60
  };
54
61
  }
55
62
  export interface Local {
56
63
  c: string;
64
+ /** A value, or a local FixedArray / FixedVec (`fixedArray<T, N>(…)`, `const ys = xs.map(…)`) held in place. */
57
65
  type: ValueType | (FieldType & {
58
66
  kind: "array";
67
+ }) | (FieldType & {
68
+ kind: "vec";
69
+ cap: number;
59
70
  });
60
71
  mutable: boolean;
61
72
  /** Pubkey held as a `const SolPubkey *` (helper parameters) rather than a value. */
@@ -64,15 +75,26 @@ export interface Local {
64
75
  readonly?: boolean;
65
76
  /** For struct / data-enum references: the storage paths they alias (e.g. "book.asks[]"), and the declaration. */
66
77
  paths?: string[];
67
- decl?: ts.VariableDeclaration;
78
+ decl?: ts.VariableDeclaration | ts.ParameterDeclaration;
68
79
  /** A local closure (`const f = (…) => …` reading the enclosing function's variables), expanded where it is called. */
69
80
  closure?: Closure;
70
81
  }
71
82
  /** A closure and the scopes it was declared in (its free names resolve there, wherever it is called). */
72
83
  export interface Closure {
73
- node: ts.ArrowFunction | ts.FunctionExpression;
84
+ /** The function expanded: a closure, or a helper expanded where it is called because it was given one. */
85
+ node: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration;
74
86
  name: string;
75
87
  scopes: Map<string, Local>[];
88
+ /**
89
+ * The instruction arguments and accounts its body may name: those where it was written for a closure, none for a
90
+ * helper or a function a helper made (they see only their parameters and the module).
91
+ */
92
+ env: {
93
+ args: Map<string, Local>;
94
+ accounts: Map<string, AccountParam>;
95
+ };
96
+ /** The `typesol` imports of the file it is written in: its type names mean what they mean there. */
97
+ imports: Map<string, string>;
76
98
  }
77
99
  export declare const cBytes: (bytes: number[]) => string;
78
100
  /** C expressions that nothing in a TypeSOL expression can change: temporaries, locals, arguments, account keys, constants. */
@@ -102,6 +124,24 @@ export interface EmitOptions {
102
124
  /** Log a marker and the remaining compute units before every statement (`typesol build --profile`). */
103
125
  profile?: boolean;
104
126
  }
127
+ /** C type of a growable Vec stored after the first one of struct `s`: `{ uint32_t len; T items[]; }`. */
128
+ export declare function laterVecC(s: string, field: string): string;
129
+ /** The account size of struct `s` with every growable Vec empty, as C (8-byte discriminator not included). */
130
+ export declare function emptySizeC(s: StructDef): string;
131
+ /**
132
+ * Where growable Vec `k` of a struct at `base` (a C lvalue of type TSS_<S>) starts, as C: its length and elements.
133
+ * The first is a member of the struct; each further one starts right after the previous one's elements.
134
+ */
135
+ export declare function vecPlace(s: StructDef, base: string, k: number): {
136
+ len: string;
137
+ items: string;
138
+ end: string;
139
+ };
140
+ /**
141
+ * C lines checking that an account's size matches its growable Vecs' lengths (`d`: its TSS_<S> pointer, `a`: its
142
+ * TsAccount). One Vec: ts_check_vec_len; more: each length read only once the bytes before it are known to exist.
143
+ */
144
+ export declare function vecLenCheck(s: StructDef, a: string, d: string, pad?: string): string[];
105
145
  /** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
106
146
  export declare function fixedVecC(t: FieldType & {
107
147
  kind: "vec";
@@ -202,6 +202,49 @@ export function helperSignature(h) {
202
202
  params.push(h.returns.kind === "struct" ? `TSS_${h.returns.name} *ret` : h.returns.kind === "string" ? `TS_Str *ret` : `${scalarC(h.returns)} *ret`);
203
203
  return `static uint64_t fn_${h.name}(${params.join(", ")})`;
204
204
  }
205
+ /** C type of a growable Vec stored after the first one of struct `s`: `{ uint32_t len; T items[]; }`. */
206
+ export function laterVecC(s, field) {
207
+ return `TSW_${s}__${field}`;
208
+ }
209
+ /** The account size of struct `s` with every growable Vec empty, as C (8-byte discriminator not included). */
210
+ export function emptySizeC(s) {
211
+ const n = s.vecs?.length ?? 0;
212
+ return n > 1 ? `(sizeof(TSS_${s.name}) + ${4 * (n - 1)})` : `sizeof(TSS_${s.name})`;
213
+ }
214
+ /**
215
+ * Where growable Vec `k` of a struct at `base` (a C lvalue of type TSS_<S>) starts, as C: its length and elements.
216
+ * The first is a member of the struct; each further one starts right after the previous one's elements.
217
+ */
218
+ export function vecPlace(s, base, k) {
219
+ const vs = s.vecs;
220
+ const first = vs[0];
221
+ let end = `((uint8_t *)${base}.${first.name} + (uint64_t)${base}.${first.name}__len * ${first.elemSize})`;
222
+ let place = { len: `${base}.${first.name}__len`, items: `${base}.${first.name}`, end };
223
+ for (let j = 1; j <= k; j++) {
224
+ const v = vs[j];
225
+ const h = `((${laterVecC(s.name, v.name)} *)${end})`;
226
+ end = `((uint8_t *)${h}->items + (uint64_t)${h}->len * ${v.elemSize})`;
227
+ place = { len: `${h}->len`, items: `${h}->items`, end };
228
+ }
229
+ return place;
230
+ }
231
+ /**
232
+ * C lines checking that an account's size matches its growable Vecs' lengths (`d`: its TSS_<S> pointer, `a`: its
233
+ * TsAccount). One Vec: ts_check_vec_len; more: each length read only once the bytes before it are known to exist.
234
+ */
235
+ export function vecLenCheck(s, a, d, pad = " ") {
236
+ const vs = s.vecs ?? [];
237
+ if (vs.length === 1)
238
+ return [`${pad}TS_TRY(ts_check_vec_len(${a}, sizeof(TSS_${s.name}), ${d}->${vs[0].name}__len, TS_ELEM_${s.name}));`];
239
+ const o = `${d.replace(/\W/g, "")}__vo`;
240
+ const lines = [`${pad}{`, `${pad} uint64_t ${o} = sizeof(TSS_${s.name}) + (uint64_t)${d}->${vs[0].name}__len * ${vs[0].elemSize};`];
241
+ for (const v of vs.slice(1)) {
242
+ lines.push(`${pad} if (${a}->data_len < 8 + ${o} + 4) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account size does not match its Vec lengths");`);
243
+ lines.push(`${pad} ${o} += 4 + (uint64_t)((${laterVecC(s.name, v.name)} *)(${a}->data + 8 + ${o}))->len * ${v.elemSize};`);
244
+ }
245
+ lines.push(`${pad} if (${a}->data_len != 8 + ${o}) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account size does not match its Vec lengths");`, `${pad}}`);
246
+ return lines;
247
+ }
205
248
  /** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
206
249
  export function fixedVecC(t) {
207
250
  return `TSF_${fixedVecName(t)}`;
@@ -1,5 +1,6 @@
1
1
  import ts from "typescript";
2
- import { type Value, type Closure } from "./shared.js";
2
+ import { type FieldType } from "../model.js";
3
+ import { type Value, type Closure, type Ref } from "./shared.js";
3
4
  import { CpiEmitter } from "./cpi.js";
4
5
  /**
5
6
  * Statements: variables, assignment, control flow and return.
@@ -17,6 +18,42 @@ export declare abstract class StatementEmitter extends CpiEmitter {
17
18
  * As in JS, the length is read again before every step, so a body that pushes or pops sees the new length.
18
19
  */
19
20
  protected forOf(stmt: ts.ForOfStatement): void;
21
+ /** The function an identifier names (a closure, a local function or a helper), as something to expand; else undefined. */
22
+ protected functionNamed(id: ts.Identifier): Closure | undefined;
23
+ /** `xs.map(…)` / `xs.filter(…)` (or a chain of them): a list built element by element, not stored anywhere yet. */
24
+ protected isPipeline(e: ts.Expression): e is ts.CallExpression;
25
+ /** The list under a chain of map / filter calls, and the calls in the order they run. */
26
+ protected pipelineStages(e: ts.Expression): {
27
+ base: ts.Expression;
28
+ stages: {
29
+ kind: "map" | "filter";
30
+ fn: ts.ArrowFunction | ts.FunctionExpression;
31
+ }[];
32
+ };
33
+ /**
34
+ * Run `body` for each element of a list, or of a chain of `map` / `filter` calls on one (`xs.filter(f).map(g)`):
35
+ * one index loop over the list (its length read every step, as `for … of`), each callback expanded in it. A
36
+ * filter skips to the next element, a map binds its result, so a chain builds no list. `body` gets the element (as
37
+ * `list.get(i)` gives it, structs by reference, or the mapped value) and its index in the chain's output (after a
38
+ * filter, the count of elements it let through, as JS gives callbacks).
39
+ */
40
+ protected eachElement(list: ts.Expression, at: ts.Node, names: {
41
+ index: string;
42
+ ci: string;
43
+ next?: string;
44
+ alwaysNext?: boolean;
45
+ }, body: (element: ts.Expression, index: ts.Expression) => void): void;
46
+ /**
47
+ * `const ys = xs.map(…)` / `xs.filter(…)`: the chain's elements in a local FixedVec, as big as the list it starts
48
+ * from (a FixedArray's length, a FixedVec's capacity) or as annotated (`const ys: FixedVec<u64, 16> = …`, needed
49
+ * 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.
50
+ */
51
+ protected localList(d: ts.VariableDeclaration, init: ts.CallExpression, c: string): FieldType & {
52
+ kind: "vec";
53
+ cap: number;
54
+ };
55
+ /** The element type of a Vec, FixedArray, FixedVec or Vec argument. */
56
+ protected listElem(list: ts.Expression): FieldType;
20
57
  /** Whether `e` is the remaining accounts (or a const bound to them). */
21
58
  protected isRemaining(e: ts.Expression): boolean;
22
59
  /**
@@ -30,7 +67,7 @@ export declare abstract class StatementEmitter extends CpiEmitter {
30
67
  * was declared with, so a block around the call that declares the same name does not change what it reads, and
31
68
  * writes to captured `let`s are seen by the enclosing function. Returns the result (undefined for no value).
32
69
  */
33
- protected expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node): Value | undefined;
70
+ protected expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node, into?: Ref): Value | undefined;
34
71
  /**
35
72
  * `xs.forEach / some / every / find / findIndex / reduce / includes / indexOf` on a Vec, FixedArray, FixedVec or Vec
36
73
  * argument: an index loop (the length read each step, as `for … of`), the callback (an arrow written at the call)