@typesol/cli 0.15.0 → 0.17.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 +107 -0
- package/README.md +11 -7
- package/dist/cli.js +147 -20
- package/dist/clientgen.js +37 -0
- package/dist/compiler/analyze.d.ts +11 -1
- package/dist/compiler/analyze.js +307 -37
- package/dist/compiler/emit/base.d.ts +7 -0
- package/dist/compiler/emit/base.js +62 -2
- package/dist/compiler/emit/borsh.d.ts +13 -0
- package/dist/compiler/emit/borsh.js +232 -0
- package/dist/compiler/emit/calls.js +50 -15
- package/dist/compiler/emit/cpi.d.ts +32 -8
- package/dist/compiler/emit/cpi.js +706 -25
- package/dist/compiler/emit/expressions.d.ts +11 -0
- package/dist/compiler/emit/expressions.js +126 -4
- package/dist/compiler/emit/function.js +45 -12
- package/dist/compiler/emit/program.js +24 -6
- package/dist/compiler/emit/shared.d.ts +12 -0
- package/dist/compiler/emit/shared.js +22 -4
- package/dist/compiler/emit/statements.d.ts +13 -0
- package/dist/compiler/emit/statements.js +141 -7
- package/dist/compiler/emit/storage.d.ts +6 -1
- package/dist/compiler/emit/storage.js +82 -17
- package/dist/compiler/idl.js +61 -18
- package/dist/compiler/model.d.ts +56 -4
- package/dist/compiler/model.js +8 -2
- package/dist/compiler/toolchain.d.ts +2 -0
- package/dist/compiler/toolchain.js +27 -1
- package/dist/onchain.d.ts +30 -0
- package/dist/onchain.js +53 -0
- package/dist/portgen.d.ts +87 -0
- package/dist/portgen.js +367 -0
- package/dist/templates.js +49 -154
- package/dist/testing.d.ts +66 -0
- package/dist/testing.js +337 -0
- package/dist/workspace.js +11 -1
- package/docs/architecture.md +3 -0
- package/docs/cli.md +16 -2
- package/docs/errors.md +3 -2
- package/docs/language.md +19 -14
- package/docs/mainnet.md +5 -4
- package/docs/migrate-from-anchor.md +113 -0
- package/docs/test-your-program.md +52 -0
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +7 -0
- package/lib/typesol.d.ts +325 -13
- package/package.json +19 -1
- package/runtime/typesol_rt.h +54 -9
- package/ts-plugin/index.js +112 -0
- package/ts-plugin/package.json +4 -0
|
@@ -50,6 +50,8 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
50
50
|
return this.switchStatement(stmt);
|
|
51
51
|
if (ts.isForStatement(stmt))
|
|
52
52
|
return this.forLoop(stmt);
|
|
53
|
+
if (ts.isForOfStatement(stmt))
|
|
54
|
+
return this.forOf(stmt);
|
|
53
55
|
if (ts.isWhileStatement(stmt))
|
|
54
56
|
return this.loop(stmt.expression, stmt.statement, undefined);
|
|
55
57
|
if (ts.isBreakStatement(stmt) || ts.isContinueStatement(stmt)) {
|
|
@@ -63,8 +65,8 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
63
65
|
this.line(ts.isBreakStatement(stmt) ? `break;` : `goto ${this.loops.at(-1)};`);
|
|
64
66
|
return;
|
|
65
67
|
}
|
|
66
|
-
if (ts.isDoStatement(stmt) || ts.
|
|
67
|
-
this.fail(stmt, "only for (init; cond; step) and while loops are supported");
|
|
68
|
+
if (ts.isDoStatement(stmt) || ts.isForInStatement(stmt))
|
|
69
|
+
this.fail(stmt, "only for (init; cond; step), for (const x of list) and while loops are supported");
|
|
68
70
|
this.fail(stmt, `${ts.SyntaxKind[stmt.kind]} is not supported in TypeSOL`);
|
|
69
71
|
}
|
|
70
72
|
returnStatement(stmt) {
|
|
@@ -77,6 +79,25 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
77
79
|
this.line(`return SUCCESS;`);
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
82
|
+
if (returns.kind === "struct") {
|
|
83
|
+
if (!stmt.expression)
|
|
84
|
+
this.fail(stmt, `return a ${returns.name}`);
|
|
85
|
+
if (this.helper) {
|
|
86
|
+
// Write the struct through the caller's out pointer (a literal, a place or another helper's result).
|
|
87
|
+
this.writeAggregate({ lv: "(*ret)", type: returns, writable: true }, stmt.expression);
|
|
88
|
+
this.line(`return SUCCESS;`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Return data: the struct's fixed layout, which the IDL describes field by field.
|
|
92
|
+
const t = this.tmp("ret");
|
|
93
|
+
this.line(`TSS_${returns.name} ${t};`);
|
|
94
|
+
this.writeAggregate({ lv: t, type: returns, writable: true }, stmt.expression);
|
|
95
|
+
this.line(`sol_set_return_data((const uint8_t *)&${t}, sizeof(${t}));`);
|
|
96
|
+
for (const l of this.epilogue())
|
|
97
|
+
this.line(l);
|
|
98
|
+
this.line(`return SUCCESS;`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
80
101
|
if (!stmt.expression)
|
|
81
102
|
this.fail(stmt, `return a ${typeName(returns)}`);
|
|
82
103
|
const v = this.expect(this.expr(stmt.expression, returns), returns, stmt.expression);
|
|
@@ -170,6 +191,65 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
170
191
|
});
|
|
171
192
|
this.line(`}`);
|
|
172
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* `for (const x of list)` over a Vec or FixedArray: an index loop that binds `x` as `const x = list.get(i)` would,
|
|
196
|
+
* so elements are values (scalars, strings) or references into the data (structs), with the same aliasing rules.
|
|
197
|
+
* As in JS, the length is read again before every step, so a body that pushes or pops sees the new length.
|
|
198
|
+
*/
|
|
199
|
+
forOf(stmt) {
|
|
200
|
+
if (stmt.awaitModifier)
|
|
201
|
+
this.fail(stmt, "for await is not supported");
|
|
202
|
+
const list = stmt.initializer;
|
|
203
|
+
const d = ts.isVariableDeclarationList(list) && list.declarations.length === 1 ? list.declarations[0] : undefined;
|
|
204
|
+
if (!d || !(list.flags & ts.NodeFlags.Const) || !ts.isIdentifier(d.name) || d.type)
|
|
205
|
+
this.fail(stmt.initializer, "write for (const x of list): one const name, no type annotation");
|
|
206
|
+
if (this.isRemaining(stmt.expression))
|
|
207
|
+
this.fail(stmt.expression, "iterate remaining accounts with for (let i = 0n; i < rest.length; i++) and rest.get(i)");
|
|
208
|
+
// Synthetic `list.length` / `list.get(i)` nodes placed at the loop, so errors and checks point at the source.
|
|
209
|
+
const f = ts.factory;
|
|
210
|
+
const index = `__of${this.temp++}`;
|
|
211
|
+
const place = (n) => {
|
|
212
|
+
ts.setTextRange(n, stmt.expression);
|
|
213
|
+
n.parent = stmt;
|
|
214
|
+
return n;
|
|
215
|
+
};
|
|
216
|
+
const indexId = place(f.createIdentifier(index));
|
|
217
|
+
const length = place(f.createPropertyAccessExpression(stmt.expression, "length"));
|
|
218
|
+
const get = place(f.createCallExpression(place(f.createPropertyAccessExpression(stmt.expression, "get")), undefined, [indexId]));
|
|
219
|
+
// The element's declaration: the source's own name (so aliasing checks find its uses) bound to list.get(i).
|
|
220
|
+
const decl = f.createVariableDeclaration(d.name, undefined, undefined, get);
|
|
221
|
+
ts.setTextRange(decl, d);
|
|
222
|
+
decl.parent = list;
|
|
223
|
+
const declList = { ...list, flags: ts.NodeFlags.Const, declarations: [decl] };
|
|
224
|
+
const ci = this.tmp("of_i");
|
|
225
|
+
const label = this.tmp("ts_continue_");
|
|
226
|
+
this.line(`{`);
|
|
227
|
+
this.block(() => {
|
|
228
|
+
this.line(`uint64_t ${ci} = 0;`);
|
|
229
|
+
this.scopes.at(-1).set(index, { c: ci, type: U64, mutable: false });
|
|
230
|
+
this.line(`while (1) {`);
|
|
231
|
+
this.block(() => {
|
|
232
|
+
const n = this.expect(this.expr(length, U64), U64, stmt.expression);
|
|
233
|
+
this.line(`if (!(${ci} < ${n.c})) break;`);
|
|
234
|
+
this.variable(declList);
|
|
235
|
+
this.loops.push(label);
|
|
236
|
+
this.line(`{`);
|
|
237
|
+
this.block(() => this.body(stmt.statement));
|
|
238
|
+
this.line(`}`);
|
|
239
|
+
this.loops.pop();
|
|
240
|
+
this.line(`${label}:;`);
|
|
241
|
+
this.line(`${ci}++;`);
|
|
242
|
+
});
|
|
243
|
+
this.line(`}`);
|
|
244
|
+
});
|
|
245
|
+
this.line(`}`);
|
|
246
|
+
}
|
|
247
|
+
/** Whether `e` is the remaining accounts (or a const bound to them). */
|
|
248
|
+
isRemaining(e) {
|
|
249
|
+
if (ts.isCallExpression(e) && this.exportName(e.expression) === "remainingAccounts")
|
|
250
|
+
return true;
|
|
251
|
+
return ts.isIdentifier(e) && this.lookup(e.text)?.type.kind === "remaining";
|
|
252
|
+
}
|
|
173
253
|
/**
|
|
174
254
|
* `while (1) { cond; if (!cond) break; body; continue_label: step }`. The condition is lowered
|
|
175
255
|
* inside the loop so its checked-arithmetic temporaries are recomputed every iteration.
|
|
@@ -204,8 +284,18 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
204
284
|
if (!(list.flags & (ts.NodeFlags.Const | ts.NodeFlags.Let)))
|
|
205
285
|
this.fail(list, "use const or let, not var");
|
|
206
286
|
for (const d of list.declarations) {
|
|
287
|
+
if (ts.isObjectBindingPattern(d.name) && d.initializer) {
|
|
288
|
+
this.destructure(list, d, d.name, d.initializer, topLevel);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
207
291
|
if (!ts.isIdentifier(d.name) || !d.initializer)
|
|
208
292
|
this.fail(d, "declare one plain name with an initializer");
|
|
293
|
+
// A local function is a helper of its own (lifted when the program was analyzed): nothing to run here.
|
|
294
|
+
if (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)) {
|
|
295
|
+
if (!this.model.helperDecls.has(d))
|
|
296
|
+
this.fail(d, "declare local functions with const: const f = (…) => …");
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
209
299
|
const name = d.name.text;
|
|
210
300
|
if (this.scopes.at(-1).has(name))
|
|
211
301
|
this.fail(d.name, `"${name}" is already declared`);
|
|
@@ -312,6 +402,39 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
312
402
|
this.scopes.at(-1).set(name, { c, type, mutable });
|
|
313
403
|
}
|
|
314
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* `const { a, b: c } = s`: one declaration per name, `const a = s.a`, in source order. The source must be a name or
|
|
407
|
+
* a field path (no calls), so reading it once per name reads the same thing JS reads once.
|
|
408
|
+
*/
|
|
409
|
+
destructure(list, d, pattern, init, topLevel) {
|
|
410
|
+
if (d.type)
|
|
411
|
+
this.fail(d.type, "destructuring takes the types from the source; drop the annotation");
|
|
412
|
+
const plain = (e) => ts.isIdentifier(e) ||
|
|
413
|
+
(ts.isPropertyAccessExpression(e) && !e.questionDotToken && plain(e.expression)) ||
|
|
414
|
+
(ts.isParenthesizedExpression(e) && plain(e.expression));
|
|
415
|
+
if (!plain(init))
|
|
416
|
+
this.fail(init, "destructure a variable or a field (a.b.c); bind a call to a const first");
|
|
417
|
+
const f = ts.factory;
|
|
418
|
+
for (const el of pattern.elements) {
|
|
419
|
+
if (el.dotDotDotToken)
|
|
420
|
+
this.fail(el, "...rest is not supported in destructuring");
|
|
421
|
+
if (el.initializer)
|
|
422
|
+
this.fail(el, "default values are not supported in destructuring");
|
|
423
|
+
if (!ts.isIdentifier(el.name))
|
|
424
|
+
this.fail(el, "nested destructuring is not supported; destructure in two steps");
|
|
425
|
+
const prop = el.propertyName ?? el.name;
|
|
426
|
+
if (!ts.isIdentifier(prop))
|
|
427
|
+
this.fail(el, "use plain field names");
|
|
428
|
+
const access = f.createPropertyAccessExpression(init, prop.text);
|
|
429
|
+
ts.setTextRange(access, el);
|
|
430
|
+
access.parent = d;
|
|
431
|
+
// The element's own name node, so aliasing checks and the type checker find its uses.
|
|
432
|
+
const decl = f.createVariableDeclaration(el.name, undefined, undefined, access);
|
|
433
|
+
ts.setTextRange(decl, el);
|
|
434
|
+
decl.parent = list;
|
|
435
|
+
this.variable({ ...list, declarations: [decl] }, topLevel);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
315
438
|
expressionStatement(e, topLevel) {
|
|
316
439
|
if (ts.isBinaryExpression(e)) {
|
|
317
440
|
const op = e.operatorToken.kind;
|
|
@@ -332,6 +455,8 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
332
455
|
const base = compound[op];
|
|
333
456
|
if (base !== undefined)
|
|
334
457
|
return this.assign(e.left, e.right, base);
|
|
458
|
+
if (op === ts.SyntaxKind.AsteriskAsteriskEqualsToken)
|
|
459
|
+
this.fail(e.operatorToken, "write x = x ** n instead of **=");
|
|
335
460
|
}
|
|
336
461
|
if ((ts.isPostfixUnaryExpression(e) || ts.isPrefixUnaryExpression(e)) &&
|
|
337
462
|
(e.operator === ts.SyntaxKind.PlusPlusToken || e.operator === ts.SyntaxKind.MinusMinusToken)) {
|
|
@@ -379,17 +504,25 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
379
504
|
}
|
|
380
505
|
// JS evaluates the target (`a.get(i).x`: which element) before the right side. The exception is
|
|
381
506
|
// `acct.field = acct.create(...)`, which must create the account before the field can be touched.
|
|
382
|
-
|
|
507
|
+
// `fixedVec([…])` is a value written in place, not a call.
|
|
508
|
+
const isCall = ts.isCallExpression(valueNode) && !this.isStorageCall(valueNode) && this.exportName(valueNode.expression) !== "fixedVec";
|
|
383
509
|
const createsFirst = isCall && ts.isPropertyAccessExpression(valueNode.expression) && ["create", "createIfNeeded"].includes(valueNode.expression.name.text);
|
|
384
510
|
let raw = createsFirst ? this.call(valueNode, topLevel) : undefined;
|
|
385
511
|
if (createsFirst && !raw)
|
|
386
512
|
this.fail(valueNode, "this call does not produce a value");
|
|
513
|
+
// A helper's struct result is copied like any struct (the target first, as JS evaluates it first).
|
|
514
|
+
const structCall = isCall && ts.isIdentifier(valueNode.expression)
|
|
515
|
+
? this.helperOf(valueNode)?.returns?.kind === "struct"
|
|
516
|
+
: false;
|
|
517
|
+
if (structCall && this.writesThroughVec(target) && this.helperTakesMutAccounts(valueNode))
|
|
518
|
+
this.fail(target, "the right side calls a helper that may change this Vec; store its result in a local first");
|
|
387
519
|
// Whole arrays / structs: copy between identical layouts.
|
|
388
|
-
const targetRef = isCall ? undefined : this.ref(target);
|
|
520
|
+
const targetRef = isCall && !structCall ? undefined : this.ref(target);
|
|
389
521
|
if (targetRef &&
|
|
390
522
|
(targetRef.type.kind === "array" ||
|
|
391
523
|
targetRef.type.kind === "struct" ||
|
|
392
524
|
targetRef.type.kind === "dataEnum" ||
|
|
525
|
+
(targetRef.type.kind === "vec" && targetRef.type.cap !== undefined) ||
|
|
393
526
|
(targetRef.type.kind === "option" && targetRef.type.inner.kind === "struct"))) {
|
|
394
527
|
if (!targetRef.writable)
|
|
395
528
|
this.fail(target, this.readOnly(targetRef));
|
|
@@ -407,8 +540,9 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
407
540
|
this.fail(valueNode, "this call does not produce a value");
|
|
408
541
|
}
|
|
409
542
|
let value = this.expect(raw ?? this.expr(valueNode, place.type), place.type, valueNode);
|
|
410
|
-
|
|
411
|
-
|
|
543
|
+
// A local, or a string field of a variable enum (a view), keeps its own copy of account text.
|
|
544
|
+
if (ts.isIdentifier(target) || (value.type.kind === "string" && this.peekKind(target) === "string"))
|
|
545
|
+
value = this.ownedString(value);
|
|
412
546
|
this.line(place.write(value.c) + ";");
|
|
413
547
|
}
|
|
414
548
|
/** Whether a place goes through a Vec element (`v.get(i).x`, `v.get(i)`). */
|
|
@@ -428,7 +562,7 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
428
562
|
}
|
|
429
563
|
}
|
|
430
564
|
helperTakesMutAccounts(node) {
|
|
431
|
-
const h =
|
|
565
|
+
const h = this.helperOf(node);
|
|
432
566
|
return !!h?.params.some((p) => p.account && isWritable(p.account));
|
|
433
567
|
}
|
|
434
568
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { type FieldType, type ValueType } from "../model.js";
|
|
2
|
+
import { type FieldType, type Helper, type ValueType } from "../model.js";
|
|
3
3
|
import { type Value, type Ref } from "./shared.js";
|
|
4
4
|
import { EmitterBase } from "./base.js";
|
|
5
5
|
/**
|
|
@@ -7,6 +7,7 @@ import { EmitterBase } from "./base.js";
|
|
|
7
7
|
* argument validation.
|
|
8
8
|
*/
|
|
9
9
|
export declare abstract class StorageEmitter extends EmitterBase {
|
|
10
|
+
protected abstract callHelper(node: ts.CallExpression, h: Helper): Value | undefined;
|
|
10
11
|
/** C lines rejecting malformed fixed-layout data at `lv`: bools, enums, option tags, string lengths, enum tags. */
|
|
11
12
|
protected validation(lv: string, t: FieldType, pad: string, fail?: string): string[];
|
|
12
13
|
/** The variants TypeScript has narrowed a data-enum expression to (e.g. inside `if (a.kind === "X")`). */
|
|
@@ -66,6 +67,10 @@ export declare abstract class StorageEmitter extends EmitterBase {
|
|
|
66
67
|
* required) or by copying another location of the same type.
|
|
67
68
|
*/
|
|
68
69
|
protected writeAggregate(target: Ref, valueNode: ts.Expression): void;
|
|
70
|
+
/** The Vec view of a FixedVec stored at `lv` (`{ len, items[cap] }`). */
|
|
71
|
+
protected fixedVec(lv: string, elem: FieldType, cap: number): NonNullable<Ref["vec"]>;
|
|
72
|
+
/** A place whose type is a FixedVec gets its Vec view. */
|
|
73
|
+
protected withFixedVec(r: Ref): Ref;
|
|
69
74
|
/** `arr.get(i)` reads storage rather than calling anything. */
|
|
70
75
|
protected isStorageCall(node: ts.CallExpression): boolean;
|
|
71
76
|
/** Whether an expression names an array, without emitting code or failing. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveScalarType } from "../analyze.js";
|
|
3
3
|
import { INT_INFO, isScalar, isWritable, sameType, scalarSize, typeName } from "../model.js";
|
|
4
|
-
import { U64, BOOL, PUBKEY, STRING, optC, fieldC, storageDecl } from "./shared.js";
|
|
4
|
+
import { U64, BOOL, PUBKEY, STRING, optC, fieldC, fixedVecC, storageDecl, vecCount } from "./shared.js";
|
|
5
5
|
import { EmitterBase } from "./base.js";
|
|
6
6
|
/**
|
|
7
7
|
* Places in account data and locals: references, reads and writes, Vec/array indexing, aliasing rules and
|
|
@@ -73,12 +73,13 @@ export class StorageEmitter extends EmitterBase {
|
|
|
73
73
|
return r?.type.kind === "dataEnum" ? r : undefined;
|
|
74
74
|
}
|
|
75
75
|
looksLikeDataEnum(node) {
|
|
76
|
-
const saved = { lines: this.lines.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
|
|
76
|
+
const saved = { lines: this.lines.length, hoisted: this.hoisted.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
|
|
77
77
|
try {
|
|
78
78
|
return this.reporter.guard(() => this.ref(node))?.type.kind === "dataEnum";
|
|
79
79
|
}
|
|
80
80
|
finally {
|
|
81
81
|
this.lines.length = saved.lines;
|
|
82
|
+
this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
|
|
82
83
|
this.temp = saved.temp;
|
|
83
84
|
this.reporter.diagnostics.length = saved.diagnostics;
|
|
84
85
|
}
|
|
@@ -144,7 +145,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
144
145
|
// Loops around `at` that do not also contain the declaration run their earlier statements again later.
|
|
145
146
|
let loop;
|
|
146
147
|
for (let n = at.parent; n && n !== decl.parent; n = n.parent)
|
|
147
|
-
if ((ts.isForStatement(n) || ts.isWhileStatement(n) || ts.isDoStatement(n)) && !(decl.pos >= n.pos && decl.end <= n.end))
|
|
148
|
+
if ((ts.isForStatement(n) || ts.isForOfStatement(n) || ts.isWhileStatement(n) || ts.isDoStatement(n)) && !(decl.pos >= n.pos && decl.end <= n.end))
|
|
148
149
|
loop = n;
|
|
149
150
|
let found = false;
|
|
150
151
|
const scope = decl.parent.parent.parent; // VariableDeclaration -> List -> Statement -> enclosing block
|
|
@@ -207,13 +208,14 @@ export class StorageEmitter extends EmitterBase {
|
|
|
207
208
|
return this.looksLikeStructPlace(node.expression);
|
|
208
209
|
if (ts.isConditionalExpression(node))
|
|
209
210
|
return this.looksLikeStructPlace(node.whenTrue) && this.looksLikeStructPlace(node.whenFalse);
|
|
210
|
-
const saved = { lines: this.lines.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
|
|
211
|
+
const saved = { lines: this.lines.length, hoisted: this.hoisted.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
|
|
211
212
|
try {
|
|
212
213
|
const r = this.reporter.guard(() => this.ref(node));
|
|
213
214
|
return r?.type.kind === "struct";
|
|
214
215
|
}
|
|
215
216
|
finally {
|
|
216
217
|
this.lines.length = saved.lines;
|
|
218
|
+
this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
|
|
217
219
|
this.temp = saved.temp;
|
|
218
220
|
this.reporter.diagnostics.length = saved.diagnostics;
|
|
219
221
|
}
|
|
@@ -241,6 +243,14 @@ export class StorageEmitter extends EmitterBase {
|
|
|
241
243
|
if (ts.isParenthesizedExpression(node))
|
|
242
244
|
return this.ref(node.expression);
|
|
243
245
|
this.noOptionalChain(node);
|
|
246
|
+
// A helper's struct result: a local of the caller's, read and copied like any struct.
|
|
247
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
248
|
+
const h = this.helperOf(node);
|
|
249
|
+
if (h?.returns?.kind === "struct") {
|
|
250
|
+
const v = this.callHelper(node, h);
|
|
251
|
+
return { lv: `(*${v.c})`, type: { kind: "struct", name: h.returns.name }, writable: true };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
244
254
|
if (ts.isIdentifier(node)) {
|
|
245
255
|
const l = this.lookup(node.text);
|
|
246
256
|
if (l && l.type.kind === "array")
|
|
@@ -263,10 +273,13 @@ export class StorageEmitter extends EmitterBase {
|
|
|
263
273
|
return undefined;
|
|
264
274
|
this.assertUsable(a, node);
|
|
265
275
|
const r = { lv: `d_${a.name}->${f.name}`, type: f.type, writable: isWritable(a.kind), account: a, path: [f.name] };
|
|
266
|
-
if (f.type.kind === "vec")
|
|
276
|
+
if (f.type.kind === "vec" && f.type.cap !== undefined)
|
|
277
|
+
r.vec = this.fixedVec(r.lv, f.type.elem, f.type.cap);
|
|
278
|
+
else if (f.type.kind === "vec")
|
|
267
279
|
// Growing needs the account's length at instruction entry: only instructions know it.
|
|
268
280
|
r.vec = {
|
|
269
281
|
len: `d_${a.name}->${f.name}__len`,
|
|
282
|
+
items: r.lv,
|
|
270
283
|
fixed: `sizeof(TSS_${a.kind.struct.name})`,
|
|
271
284
|
elemSize: a.kind.struct.vec.elemSize,
|
|
272
285
|
resizable: isWritable(a.kind) && !!this.ix,
|
|
@@ -284,7 +297,9 @@ export class StorageEmitter extends EmitterBase {
|
|
|
284
297
|
const vi = e.variants.findIndex((v) => v.name === n);
|
|
285
298
|
const fields = e.variants[vi]?.fields ?? [];
|
|
286
299
|
const at = fields.findIndex((x) => x.name === field);
|
|
287
|
-
|
|
300
|
+
// In the C payload a string is a TS_Str view (16 bytes); everything else has its stored size.
|
|
301
|
+
const cBytes = (t) => (t.kind === "string" ? 16 : this.fieldBytes(t));
|
|
302
|
+
const offset = fields.slice(0, Math.max(at, 0)).reduce((sum, x) => sum + cBytes(x.type), 0);
|
|
288
303
|
return { vi, name: n, f: fields[at], offset };
|
|
289
304
|
});
|
|
290
305
|
if (located.some((l) => !l.f))
|
|
@@ -295,7 +310,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
295
310
|
// TypeScript proved the variant set; this runtime check is the backstop.
|
|
296
311
|
const cond = located.map((l) => `${base.lv}.tag != ${l.vi}`).join(" && ");
|
|
297
312
|
this.line(`if (${cond}) TS_FAIL(TS_ERR_ENUM_VARIANT, "TypeSOL: ${e.name} is not ${names.join("/")}");`);
|
|
298
|
-
return { ...base, lv: `${base.lv}.v.${first.name}.${first.f.name}`, type: first.f.type, path: undefined, vec: undefined };
|
|
313
|
+
return this.withFixedVec({ ...base, lv: `${base.lv}.v.${first.name}.${first.f.name}`, type: first.f.type, path: undefined, vec: undefined });
|
|
299
314
|
}
|
|
300
315
|
if (base && base.type.kind === "option" && base.type.inner.kind === "struct") {
|
|
301
316
|
const s = this.model.structs.get(base.type.inner.name);
|
|
@@ -303,7 +318,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
303
318
|
if (!f)
|
|
304
319
|
this.fail(node.name, `${s.name} has no field "${field}"`);
|
|
305
320
|
this.line(`if (!${base.lv}.some) TS_FAIL(TS_ERR_UNWRAP, "TypeSOL: option is empty");`);
|
|
306
|
-
return { ...base, lv: `${base.lv}.v.${f.name}`, type: f.type, path: undefined, vec: undefined };
|
|
321
|
+
return this.withFixedVec({ ...base, lv: `${base.lv}.v.${f.name}`, type: f.type, path: undefined, vec: undefined });
|
|
307
322
|
}
|
|
308
323
|
if (base && base.type.kind === "struct") {
|
|
309
324
|
const s = this.model.structs.get(base.type.name);
|
|
@@ -312,9 +327,9 @@ export class StorageEmitter extends EmitterBase {
|
|
|
312
327
|
this.fail(node.name, `${s.name} has no field "${field}"`);
|
|
313
328
|
const r = { ...base, lv: `${base.lv}.${f.name}`, type: f.type, path: base.path && [...base.path, f.name], vec: undefined };
|
|
314
329
|
// Vecs of loaded accounts can be read and updated in place but not resized (no entry length known).
|
|
315
|
-
if (f.type.kind === "vec")
|
|
316
|
-
r.vec = { len: `${base.lv}.${f.name}__len`, fixed: `sizeof(TSS_${s.name})`, elemSize: s.vec.elemSize, resizable: false };
|
|
317
|
-
return r;
|
|
330
|
+
if (f.type.kind === "vec" && f.type.cap === undefined)
|
|
331
|
+
r.vec = { len: `${base.lv}.${f.name}__len`, items: r.lv, fixed: `sizeof(TSS_${s.name})`, elemSize: s.vec.elemSize, resizable: false };
|
|
332
|
+
return this.withFixedVec(r);
|
|
318
333
|
}
|
|
319
334
|
return undefined;
|
|
320
335
|
}
|
|
@@ -333,8 +348,9 @@ export class StorageEmitter extends EmitterBase {
|
|
|
333
348
|
if (base && (base.type.kind === "array" || base.type.kind === "vec")) {
|
|
334
349
|
if (node.arguments.length !== 1)
|
|
335
350
|
this.fail(node, "get(index) takes one argument");
|
|
336
|
-
const idx = this.index(node.arguments[0], base.type.kind === "array" ? `${base.type.len}ULL` :
|
|
337
|
-
|
|
351
|
+
const idx = this.index(node.arguments[0], base.type.kind === "array" ? `${base.type.len}ULL` : vecCount(base.vec));
|
|
352
|
+
const lv = base.type.kind === "array" ? `${base.lv}[${idx}]` : `${base.vec.items}[${idx}]`;
|
|
353
|
+
return this.withFixedVec({ ...base, lv, type: base.type.elem, path: undefined, vec: undefined });
|
|
338
354
|
}
|
|
339
355
|
}
|
|
340
356
|
return undefined;
|
|
@@ -412,6 +428,8 @@ export class StorageEmitter extends EmitterBase {
|
|
|
412
428
|
return { c: `(&${r.lv})`, type: r.type };
|
|
413
429
|
case "fstring":
|
|
414
430
|
return { c: `ts_fstr_view((const uint8_t *)&${r.lv}, ${r.type.cap})`, type: STRING };
|
|
431
|
+
case "string":
|
|
432
|
+
return { c: r.lv, type: STRING }; // a variable enum's text: already a view
|
|
415
433
|
case "enum":
|
|
416
434
|
return { c: r.lv, type: r.type };
|
|
417
435
|
case "option":
|
|
@@ -439,6 +457,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
439
457
|
return `${r.lv} = *(${c})`;
|
|
440
458
|
case "fstring":
|
|
441
459
|
return `TS_TRY(ts_fstr_set((uint8_t *)&${r.lv}, ${r.type.cap}, ${c}))`;
|
|
460
|
+
case "string":
|
|
442
461
|
case "enum":
|
|
443
462
|
case "option":
|
|
444
463
|
return `${r.lv} = ${c}`;
|
|
@@ -449,7 +468,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
449
468
|
storageValueType(t) {
|
|
450
469
|
if (isScalar(t))
|
|
451
470
|
return t;
|
|
452
|
-
if (t.kind === "fstring")
|
|
471
|
+
if (t.kind === "fstring" || t.kind === "string")
|
|
453
472
|
return STRING;
|
|
454
473
|
if (t.kind === "option" && t.inner.kind !== "struct")
|
|
455
474
|
return t;
|
|
@@ -460,6 +479,8 @@ export class StorageEmitter extends EmitterBase {
|
|
|
460
479
|
* required) or by copying another location of the same type.
|
|
461
480
|
*/
|
|
462
481
|
writeAggregate(target, valueNode) {
|
|
482
|
+
if (ts.isParenthesizedExpression(valueNode))
|
|
483
|
+
return this.writeAggregate(target, valueNode.expression);
|
|
463
484
|
if (target.type.kind === "option" && target.type.inner.kind === "struct") {
|
|
464
485
|
if (valueNode.kind === ts.SyntaxKind.NullKeyword) {
|
|
465
486
|
this.line(`${target.lv}.some = 0;`);
|
|
@@ -510,14 +531,44 @@ export class StorageEmitter extends EmitterBase {
|
|
|
510
531
|
const f = variant.fields.find((x) => x.name === name);
|
|
511
532
|
const sub = { lv: `${tmp}.v.${variant.name}.${f.name}`, type: f.type, writable: true };
|
|
512
533
|
const vt = this.storageValueType(f.type);
|
|
534
|
+
// A string kept in a variable enum holds its own copy of account text (the account may change later).
|
|
513
535
|
if (vt)
|
|
514
|
-
this.line(this.writeStorage(sub, this.expect(this.expr(ex, vt), vt, ex).c) + ";");
|
|
536
|
+
this.line(this.writeStorage(sub, this.ownedString(this.expect(this.expr(ex, vt), vt, ex)).c) + ";");
|
|
515
537
|
else
|
|
516
538
|
this.writeAggregate(sub, ex);
|
|
517
539
|
}
|
|
518
540
|
this.line(`ts_memcpy(&${target.lv}, &${tmp}, sizeof(${tmp}));`);
|
|
519
541
|
return;
|
|
520
542
|
}
|
|
543
|
+
if (target.type.kind === "vec" && target.type.cap !== undefined && ts.isCallExpression(valueNode) && this.exportName(valueNode.expression) === "fixedVec") {
|
|
544
|
+
const list = valueNode.arguments[0];
|
|
545
|
+
if (valueNode.arguments.length !== 1 || !ts.isArrayLiteralExpression(list))
|
|
546
|
+
this.fail(valueNode, "fixedVec([…]) takes an array literal");
|
|
547
|
+
return this.writeAggregate(target, list);
|
|
548
|
+
}
|
|
549
|
+
if (target.type.kind === "vec" && target.type.cap !== undefined && ts.isArrayLiteralExpression(valueNode)) {
|
|
550
|
+
// `list = [a, b]`: built in a temporary (the elements may read the old list), the unused slots zeroed.
|
|
551
|
+
const t = target.type;
|
|
552
|
+
const els = valueNode.elements;
|
|
553
|
+
if (els.length > t.cap)
|
|
554
|
+
this.fail(valueNode, `${els.length} elements do not fit in ${typeName(t)}`);
|
|
555
|
+
const tmp = this.tmp("fv");
|
|
556
|
+
this.line(`${fixedVecC(t)} ${tmp};`);
|
|
557
|
+
this.line(`ts_memzero(&${tmp}, sizeof(${tmp}));`);
|
|
558
|
+
this.line(`${tmp}.len = ${els.length};`);
|
|
559
|
+
els.forEach((e, i) => {
|
|
560
|
+
if (ts.isSpreadElement(e))
|
|
561
|
+
this.fail(e, "list the elements one by one");
|
|
562
|
+
const sub = { lv: `${tmp}.items[${i}]`, type: t.elem, writable: true };
|
|
563
|
+
const vt = this.storageValueType(t.elem);
|
|
564
|
+
if (vt)
|
|
565
|
+
this.line(this.writeStorage(sub, this.ownedString(this.expect(this.expr(e, vt), vt, e)).c) + ";");
|
|
566
|
+
else
|
|
567
|
+
this.writeAggregate(sub, e);
|
|
568
|
+
});
|
|
569
|
+
this.line(`ts_memcpy(&${target.lv}, &${tmp}, sizeof(${tmp}));`);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
521
572
|
if (ts.isObjectLiteralExpression(valueNode)) {
|
|
522
573
|
if (target.type.kind !== "struct")
|
|
523
574
|
this.fail(valueNode, `expected a ${typeName(target.type)}, not an object literal`);
|
|
@@ -559,6 +610,16 @@ export class StorageEmitter extends EmitterBase {
|
|
|
559
610
|
// Same-typed places are either distinct or identical (`a.p = a.p`); ts_copy skips the latter.
|
|
560
611
|
this.line(`ts_copy(&${target.lv}, &${src.lv}, sizeof(${target.lv}));`);
|
|
561
612
|
}
|
|
613
|
+
/** The Vec view of a FixedVec stored at `lv` (`{ len, items[cap] }`). */
|
|
614
|
+
fixedVec(lv, elem, cap) {
|
|
615
|
+
return { len: `${lv}.len`, items: `${lv}.items`, fixed: "", elemSize: this.fieldBytes(elem), resizable: false, cap };
|
|
616
|
+
}
|
|
617
|
+
/** A place whose type is a FixedVec gets its Vec view. */
|
|
618
|
+
withFixedVec(r) {
|
|
619
|
+
if (r.type.kind === "vec" && r.type.cap !== undefined)
|
|
620
|
+
r.vec = this.fixedVec(r.lv, r.type.elem, r.type.cap);
|
|
621
|
+
return r;
|
|
622
|
+
}
|
|
562
623
|
/** `arr.get(i)` reads storage rather than calling anything. */
|
|
563
624
|
isStorageCall(node) {
|
|
564
625
|
return ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "get" && !!this.peekArray(node.expression.expression);
|
|
@@ -570,12 +631,13 @@ export class StorageEmitter extends EmitterBase {
|
|
|
570
631
|
}
|
|
571
632
|
/** The kind of storage an expression names (struct, array, dataEnum…), without emitting code or failing. */
|
|
572
633
|
peekKind(node) {
|
|
573
|
-
const saved = { lines: this.lines.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
|
|
634
|
+
const saved = { lines: this.lines.length, hoisted: this.hoisted.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
|
|
574
635
|
try {
|
|
575
636
|
return this.reporter.guard(() => this.ref(node))?.type.kind;
|
|
576
637
|
}
|
|
577
638
|
finally {
|
|
578
639
|
this.lines.length = saved.lines;
|
|
640
|
+
this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
|
|
579
641
|
this.temp = saved.temp;
|
|
580
642
|
this.reporter.diagnostics.length = saved.diagnostics;
|
|
581
643
|
}
|
|
@@ -641,8 +703,11 @@ export class StorageEmitter extends EmitterBase {
|
|
|
641
703
|
return this.fieldBytes(t.elem) * t.len;
|
|
642
704
|
case "struct":
|
|
643
705
|
return this.model.structs.get(t.name).size;
|
|
644
|
-
case "string":
|
|
645
706
|
case "vec":
|
|
707
|
+
if (t.cap !== undefined)
|
|
708
|
+
return 4 + this.fieldBytes(t.elem) * t.cap;
|
|
709
|
+
throw new Error("unreachable: variable-size field has no fixed size");
|
|
710
|
+
case "string":
|
|
646
711
|
throw new Error("unreachable: variable-size field has no fixed size");
|
|
647
712
|
}
|
|
648
713
|
}
|
package/dist/compiler/idl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { snakeCase } from "./analyze.js";
|
|
3
3
|
import { decodeAddress } from "../keys.js";
|
|
4
|
-
import { isWritable, optionKey } from "./model.js";
|
|
4
|
+
import { fixedVecName, isWritable, optionKey, typeName, } from "./model.js";
|
|
5
5
|
const SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
6
6
|
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
7
7
|
const TOKEN_2022_PROGRAM = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
@@ -37,6 +37,16 @@ const fixedEnumName = (name) => `FixedEnum_${name}`;
|
|
|
37
37
|
* IDL type of a field or value. `borshOption` picks the encoding of Option: Borsh `option` for
|
|
38
38
|
* instruction arguments and events, the fixed { is_some, value } struct for account data.
|
|
39
39
|
*/
|
|
40
|
+
/** IDL type of a @layout borsh account's field: Anchor's own (`string`, `{ vec }`, `{ option }`). */
|
|
41
|
+
function borshIdlType(t) {
|
|
42
|
+
if (t.kind === "fstring")
|
|
43
|
+
return "string";
|
|
44
|
+
if (t.kind === "vec")
|
|
45
|
+
return { vec: borshIdlType(t.elem) };
|
|
46
|
+
if (t.kind === "option")
|
|
47
|
+
return { option: idlType(t.inner) };
|
|
48
|
+
return idlType(t);
|
|
49
|
+
}
|
|
40
50
|
function idlType(t, borshOption = false) {
|
|
41
51
|
switch (t.kind) {
|
|
42
52
|
case "int":
|
|
@@ -62,7 +72,7 @@ function idlType(t, borshOption = false) {
|
|
|
62
72
|
// Borsh enum in arguments and events; fixed tag + payload in account data.
|
|
63
73
|
return borshOption || t.kind === "dataEnumArg" ? { defined: { name: t.name } } : { defined: { name: fixedEnumName(t.name) } };
|
|
64
74
|
case "vec":
|
|
65
|
-
return { vec: idlType(t.elem) };
|
|
75
|
+
return t.cap === undefined ? { vec: idlType(t.elem) } : { defined: { name: fixedVecName(t) } };
|
|
66
76
|
case "vecArg":
|
|
67
77
|
return { vec: idlType(t.elem) };
|
|
68
78
|
case "arrayArg":
|
|
@@ -125,7 +135,7 @@ function accountEntry(ix, name, k, programAddress) {
|
|
|
125
135
|
export function buildIdl(model, address, version = "0.1.0") {
|
|
126
136
|
const structs = [...model.structs.values()];
|
|
127
137
|
const usedStructs = structs.filter((s) => model.accountStructs.has(s.name) || model.events.has(s.name) || s.size !== undefined);
|
|
128
|
-
|
|
138
|
+
const idl = {
|
|
129
139
|
address,
|
|
130
140
|
metadata: { name: model.name, version, spec: "0.1.0", description: "Built with TypeSOL" },
|
|
131
141
|
instructions: model.instructions.map((ix) => ({
|
|
@@ -148,33 +158,47 @@ export function buildIdl(model, address, version = "0.1.0") {
|
|
|
148
158
|
fields: s.fields.map((f) => ({
|
|
149
159
|
name: snakeCase(f.name),
|
|
150
160
|
...docsOf(s.node.members.find((m) => m.name && ts.isIdentifier(m.name) && m.name.text === f.name)),
|
|
151
|
-
type: idlType(f.type, model.events.has(s.name) && !model.accountStructs.has(s.name)),
|
|
161
|
+
type: s.layout === "borsh" ? borshIdlType(f.type) : idlType(f.type, model.events.has(s.name) && !model.accountStructs.has(s.name)),
|
|
152
162
|
})),
|
|
153
163
|
},
|
|
154
164
|
})),
|
|
155
165
|
...[...model.enums.values()].map((e) => ({ name: e.name, type: { kind: "enum", variants: e.variants.map((v) => ({ name: v })) } })),
|
|
166
|
+
// A variable enum (it holds strings) only exists in Borsh: no fixed account-data form to describe.
|
|
156
167
|
...[...model.dataEnums.values()].flatMap((e) => [
|
|
157
168
|
{
|
|
158
169
|
name: e.name,
|
|
159
170
|
...docsOf(e.node),
|
|
160
171
|
type: {
|
|
161
172
|
kind: "enum",
|
|
162
|
-
variants: e.variants.map((v) => v.fields.length
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
fields: [
|
|
173
|
-
{ name: "tag", type: "u8" },
|
|
174
|
-
{ name: "payload", type: { array: ["u8", e.payload] } },
|
|
175
|
-
],
|
|
173
|
+
variants: e.variants.map((v) => v.fields.length
|
|
174
|
+
? {
|
|
175
|
+
name: v.name,
|
|
176
|
+
// A nested variable enum only exists in Borsh; fixed fields keep the fixed forms the bytes use.
|
|
177
|
+
fields: v.fields.map((f) => ({
|
|
178
|
+
name: snakeCase(f.name),
|
|
179
|
+
type: f.type.kind === "dataEnum" && model.dataEnums.get(f.type.name)?.variable ? { defined: { name: f.type.name } } : idlType(f.type),
|
|
180
|
+
})),
|
|
181
|
+
}
|
|
182
|
+
: { name: v.name }),
|
|
176
183
|
},
|
|
177
184
|
},
|
|
185
|
+
...(e.variable
|
|
186
|
+
? []
|
|
187
|
+
: [
|
|
188
|
+
{
|
|
189
|
+
name: fixedEnumName(e.name),
|
|
190
|
+
docs: [
|
|
191
|
+
`TypeSOL ${e.name} in account data: \`tag\` is the variant index, \`payload\` the variant's fields (Borsh layout) padded to the largest variant.`,
|
|
192
|
+
],
|
|
193
|
+
type: {
|
|
194
|
+
kind: "struct",
|
|
195
|
+
fields: [
|
|
196
|
+
{ name: "tag", type: "u8" },
|
|
197
|
+
{ name: "payload", type: { array: ["u8", e.payload] } },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
]),
|
|
178
202
|
]),
|
|
179
203
|
...[...model.options.values()].map((inner) => ({
|
|
180
204
|
name: fixedOptionName(inner),
|
|
@@ -187,6 +211,17 @@ export function buildIdl(model, address, version = "0.1.0") {
|
|
|
187
211
|
],
|
|
188
212
|
},
|
|
189
213
|
})),
|
|
214
|
+
...[...model.fixedVecs].map(([name, t]) => ({
|
|
215
|
+
name,
|
|
216
|
+
docs: [`TypeSOL FixedVec<${typeName(t.elem)}, ${t.cap}>: \`len\` elements in use, room for ${t.cap} (the rest zero or stale).`],
|
|
217
|
+
type: {
|
|
218
|
+
kind: "struct",
|
|
219
|
+
fields: [
|
|
220
|
+
{ name: "len", type: "u32" },
|
|
221
|
+
{ name: "items", type: { array: [idlType(t.elem), t.cap] } },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
})),
|
|
190
225
|
...[...model.fstrings]
|
|
191
226
|
.sort((a, b) => a - b)
|
|
192
227
|
.map((cap) => ({
|
|
@@ -202,4 +237,12 @@ export function buildIdl(model, address, version = "0.1.0") {
|
|
|
202
237
|
})),
|
|
203
238
|
],
|
|
204
239
|
};
|
|
240
|
+
// FixedString / FixedVec / FixedOption types only @layout borsh accounts declared (they list theirs as string / vec /
|
|
241
|
+
// option) are left out.
|
|
242
|
+
// FixedVecs first: a kept one may name a FixedString.
|
|
243
|
+
for (const prefix of [/^FixedVec\d/, /^FixedOption_/, /^FixedString\d/]) {
|
|
244
|
+
const text = JSON.stringify([idl.instructions, idl.types.filter((t) => !prefix.test(t.name))]);
|
|
245
|
+
idl.types = idl.types.filter((t) => !prefix.test(t.name) || text.includes(`"name":"${t.name}"`));
|
|
246
|
+
}
|
|
247
|
+
return idl;
|
|
205
248
|
}
|