@typesol/cli 0.17.0 → 0.19.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 +99 -0
- package/README.md +4 -4
- package/dist/clientgen.js +2 -0
- package/dist/compiler/analyze.d.ts +10 -1
- package/dist/compiler/analyze.js +165 -49
- package/dist/compiler/anchor-errors.d.ts +1 -0
- package/dist/compiler/anchor-errors.js +80 -0
- package/dist/compiler/emit/base.d.ts +29 -1
- package/dist/compiler/emit/base.js +79 -8
- package/dist/compiler/emit/borsh.js +71 -5
- package/dist/compiler/emit/calls.d.ts +2 -3
- package/dist/compiler/emit/calls.js +101 -15
- package/dist/compiler/emit/cpi.d.ts +31 -0
- package/dist/compiler/emit/cpi.js +1061 -39
- package/dist/compiler/emit/expressions.d.ts +24 -0
- package/dist/compiler/emit/expressions.js +137 -19
- package/dist/compiler/emit/function.js +57 -10
- package/dist/compiler/emit/shared.d.ts +8 -0
- package/dist/compiler/emit/shared.js +4 -1
- package/dist/compiler/emit/statements.d.ts +21 -0
- package/dist/compiler/emit/statements.js +386 -16
- package/dist/compiler/emit/storage.d.ts +2 -0
- package/dist/compiler/emit/storage.js +28 -4
- package/dist/compiler/idl.js +4 -1
- package/dist/compiler/model.d.ts +29 -2
- package/dist/portgen.js +32 -2
- package/docs/errors.md +3 -2
- package/docs/language.md +31 -24
- package/docs/migrate-from-anchor.md +13 -3
- package/lib/typesol.d.ts +528 -22
- package/package.json +1 -1
- package/runtime/typesol_rt.h +279 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { resolveScalarType } from "../analyze.js";
|
|
2
|
+
import { calleeName, resolveScalarType } from "../analyze.js";
|
|
3
|
+
import { ANCHOR_ERRORS } from "../anchor-errors.js";
|
|
3
4
|
import { decodeAddress } from "../../keys.js";
|
|
4
5
|
import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
|
|
5
6
|
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, } from "./shared.js";
|
|
@@ -7,6 +8,8 @@ import { ExpressionEmitter } from "./expressions.js";
|
|
|
7
8
|
/**
|
|
8
9
|
* Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
|
|
9
10
|
*/
|
|
11
|
+
/** List methods taking a callback (or a value) that expand into a loop. */
|
|
12
|
+
const LIST_METHODS = new Set(["forEach", "some", "every", "find", "findIndex", "reduce", "includes", "indexOf"]);
|
|
10
13
|
export class CallEmitter extends ExpressionEmitter {
|
|
11
14
|
call(node, topLevel) {
|
|
12
15
|
const callee = node.expression;
|
|
@@ -126,14 +129,26 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
126
129
|
}
|
|
127
130
|
case "mulDiv":
|
|
128
131
|
case "mulDivCeil": {
|
|
129
|
-
// a * b / c with a 128-bit
|
|
132
|
+
// a * b / c with a double-width intermediate (128-bit for u64, 256-bit for u128), rounding down or up;
|
|
133
|
+
// aborts if the result does not fit. All three are u64, or all u128 (literals take the others' type).
|
|
130
134
|
if (args.length !== 3)
|
|
131
|
-
this.fail(node, `${builtin}(a, b, c) takes three u64 values`);
|
|
132
|
-
const
|
|
135
|
+
this.fail(node, `${builtin}(a, b, c) takes three u64 or three u128 values`);
|
|
136
|
+
const vals = [];
|
|
137
|
+
for (const a of args) {
|
|
138
|
+
const mark = this.lines.length;
|
|
139
|
+
const v = this.expr(a);
|
|
140
|
+
for (let j = 0; j < vals.length; j++)
|
|
141
|
+
vals[j] = this.settle(vals[j], mark);
|
|
142
|
+
vals.push(v);
|
|
143
|
+
}
|
|
144
|
+
const typed = vals.find((v) => v.literal === undefined);
|
|
145
|
+
const wide = typed?.type.kind === "int" && typed.type.name === "u128";
|
|
146
|
+
const ty = wide ? { kind: "int", name: "u128" } : U64;
|
|
147
|
+
const [x, y, z] = vals.map((v, i) => this.expect(v.literal !== undefined ? this.literal(v.literal, ty, args[i]) : v, ty, args[i]));
|
|
133
148
|
const t = this.tmp("md");
|
|
134
|
-
this.line(
|
|
135
|
-
this.line(`TS_TRY(ts_mul_div(${x.c}, ${y.c}, ${z.c}, ${builtin === "mulDivCeil"}, &${t}));`);
|
|
136
|
-
return { c: t, type:
|
|
149
|
+
this.line(`${wide ? "unsigned __int128" : "uint64_t"} ${t};`);
|
|
150
|
+
this.line(`TS_TRY(ts_mul_div${wide ? "_u128" : ""}(${x.c}, ${y.c}, ${z.c}, ${builtin === "mulDivCeil"}, &${t}));`);
|
|
151
|
+
return { c: t, type: ty };
|
|
137
152
|
}
|
|
138
153
|
case "isqrt": {
|
|
139
154
|
if (args.length !== 1)
|
|
@@ -219,7 +234,18 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
219
234
|
this.line(`TS_TRY(ts_t22_transfer_fee(${m}, ${amt.c}, &${t}));`);
|
|
220
235
|
return { c: t, type: U64 };
|
|
221
236
|
}
|
|
237
|
+
case "isTransferring": {
|
|
238
|
+
// TransferHookAccount (15) on a Token-2022 token account: set only while Token-2022 runs a transfer of it.
|
|
239
|
+
if (args.length !== 1)
|
|
240
|
+
this.fail(node, "isTransferring(tokenAccount) takes the source token account");
|
|
241
|
+
const m = this.anyAccount(args[0], "the token account");
|
|
242
|
+
this.line(`TS_TRY(ts_t22_layout(${m}, 2));`);
|
|
243
|
+
const off = this.tmp("eo");
|
|
244
|
+
this.line(`uint64_t ${off}, ${off}l;`);
|
|
245
|
+
return { c: `(ts_t22_ext(${m}, 15, &${off}, &${off}l) && ${off}l >= 1 && ${m}->data[${off}] != 0)`, type: BOOL };
|
|
246
|
+
}
|
|
222
247
|
case "transferHookProgram":
|
|
248
|
+
case "transferHookAuthority":
|
|
223
249
|
case "permanentDelegate":
|
|
224
250
|
case "mintCloseAuthority": {
|
|
225
251
|
// Token-2022 mint extensions holding an optional Pubkey: TransferHook (14, program at +32),
|
|
@@ -228,7 +254,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
228
254
|
this.fail(node, `${builtin}(mint) takes the mint`);
|
|
229
255
|
const m = this.anyAccount(args[0], "the mint");
|
|
230
256
|
this.line(`TS_TRY(ts_t22_layout(${m}, 1));`);
|
|
231
|
-
const [type, field] = builtin === "transferHookProgram" ? [14, 32] : builtin === "permanentDelegate" ? [12, 0] : [3, 0];
|
|
257
|
+
const [type, field] = builtin === "transferHookProgram" ? [14, 32] : builtin === "transferHookAuthority" ? [14, 0] : builtin === "permanentDelegate" ? [12, 0] : [3, 0];
|
|
232
258
|
this.model.options.set("pubkey", PUBKEY);
|
|
233
259
|
const t = this.tmp("ext");
|
|
234
260
|
this.line(`${optC(PUBKEY)} ${t};`);
|
|
@@ -434,11 +460,22 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
434
460
|
}
|
|
435
461
|
if (builtin)
|
|
436
462
|
this.fail(callee, `${builtin} cannot be called here`);
|
|
463
|
+
if (ts.isIdentifier(callee)) {
|
|
464
|
+
const local = this.lookup(callee.text);
|
|
465
|
+
if (local?.closure)
|
|
466
|
+
return this.expandClosure(local.closure, node.arguments, node);
|
|
467
|
+
}
|
|
437
468
|
const helper = this.helperOf(node);
|
|
438
469
|
if (helper)
|
|
439
470
|
return this.callHelper(node, helper);
|
|
440
471
|
if (ts.isPropertyAccessExpression(callee)) {
|
|
441
472
|
const method = callee.name.text;
|
|
473
|
+
// xs.some(…) and friends on a Vec, FixedArray, FixedVec or Vec argument: a loop with the callback expanded in it.
|
|
474
|
+
if (LIST_METHODS.has(method)) {
|
|
475
|
+
const l = ts.isIdentifier(callee.expression) ? this.lookup(callee.expression.text) : undefined;
|
|
476
|
+
if (l?.type.kind === "vecArg" || l?.type.kind === "array" || this.peekArray(callee.expression))
|
|
477
|
+
return this.listMethod(node, method);
|
|
478
|
+
}
|
|
442
479
|
// Vec<T> instruction arguments are read-only views.
|
|
443
480
|
if (ts.isIdentifier(callee.expression)) {
|
|
444
481
|
const l = this.lookup(callee.expression.text);
|
|
@@ -446,6 +483,10 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
446
483
|
if (method !== "get" || args.length !== 1)
|
|
447
484
|
this.fail(node, "Vec arguments are read-only: use .get(i) and .length");
|
|
448
485
|
const elem = l.type.elem;
|
|
486
|
+
if (elem.kind === "string") {
|
|
487
|
+
const idx = this.index(args[0], `(uint64_t)${l.c}.len`);
|
|
488
|
+
return { c: `(((const TS_Str *)${l.c}.ptr)[${idx}])`, type: STRING };
|
|
489
|
+
}
|
|
449
490
|
if (!isScalar(elem))
|
|
450
491
|
this.fail(node, `read a field of the element: ${callee.expression.text}.get(i).field`);
|
|
451
492
|
const idx = this.index(args[0], `(uint64_t)${l.c}.len`);
|
|
@@ -506,8 +547,10 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
506
547
|
// Program accounts used through their methods (systemProgram.transfer, tokenProgram.*…) may be optional.
|
|
507
548
|
if (target?.optional)
|
|
508
549
|
this.assertUsable(target, callee.expression);
|
|
509
|
-
if (target?.kind.kind === "token"
|
|
510
|
-
|
|
550
|
+
if ((target?.kind.kind === "token" || target?.kind.kind === "mint") &&
|
|
551
|
+
target.kind.init &&
|
|
552
|
+
method === (target.kind.init === "create" ? "create" : "createIfNeeded"))
|
|
553
|
+
return target.kind.kind === "token" && target.constraints.ata ? this.createAta(node, target, topLevel) : this.createToken(node, target, topLevel);
|
|
511
554
|
if (target?.kind.kind === "init" && method === "create")
|
|
512
555
|
return this.create(node, target, topLevel);
|
|
513
556
|
if (target?.kind.kind === "initIfNeeded" && method === "createIfNeeded")
|
|
@@ -574,7 +617,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
574
617
|
if (info.cap !== undefined)
|
|
575
618
|
return; // a FixedVec has its room already
|
|
576
619
|
if (!info.resizable || !v.account)
|
|
577
|
-
this.fail(node, "only Vecs of
|
|
620
|
+
this.fail(node, "only Vecs of Mut<...> / Init<...> accounts can grow or shrink");
|
|
578
621
|
this.line(`TS_TRY(ts_resize(params, ${payer}, a_${v.account.name}, 8 + ${info.fixed} + (${newLen}) * ${info.elemSize}, entry_${v.account.name}));`);
|
|
579
622
|
};
|
|
580
623
|
switch (method) {
|
|
@@ -899,8 +942,37 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
899
942
|
}
|
|
900
943
|
return { c: `((TSS_${s.name} *)(${h}->data + 8))`, type: { kind: "structRef", struct: s.name, writable: mut } };
|
|
901
944
|
}
|
|
945
|
+
/** Whether a helper (or one it calls) grows a Vec, which needs the instruction's SystemProgram for rent. */
|
|
946
|
+
helperGrows(body, seen = new Set()) {
|
|
947
|
+
if (seen.has(body))
|
|
948
|
+
return false;
|
|
949
|
+
seen.add(body);
|
|
950
|
+
let found = false;
|
|
951
|
+
const visit = (n) => {
|
|
952
|
+
if (found)
|
|
953
|
+
return;
|
|
954
|
+
if (ts.isCallExpression(n)) {
|
|
955
|
+
// Vec.push(value, payer) grows the account; FixedVec.push(value) does not.
|
|
956
|
+
if (ts.isPropertyAccessExpression(n.expression) && n.expression.name.text === "push" && n.arguments.length === 2)
|
|
957
|
+
found = true;
|
|
958
|
+
else if (ts.isIdentifier(n.expression)) {
|
|
959
|
+
const name = calleeName(n.expression, this.model.helperDecls, this.checker);
|
|
960
|
+
const callee = name ? (this.model.generics.get(name)?.node ?? this.model.helpers.get(name)?.node) : undefined;
|
|
961
|
+
if (callee && this.helperGrows(callee, seen))
|
|
962
|
+
found = true;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
ts.forEachChild(n, visit);
|
|
966
|
+
};
|
|
967
|
+
visit(body);
|
|
968
|
+
return found;
|
|
969
|
+
}
|
|
902
970
|
callHelper(node, h) {
|
|
903
|
-
if (
|
|
971
|
+
if (this.ix && !this.ix.accounts.some((a) => a.kind.kind === "system" && !a.optional) && h.params.some((p) => p.account) && this.helperGrows(h.node))
|
|
972
|
+
this.fail(node, `${h.display}() grows a Vec (push): the instruction needs a SystemProgram parameter to top up rent`);
|
|
973
|
+
// Function arguments were bound when the helper was instantiated: they have no C parameter.
|
|
974
|
+
const argNodes = h.fnArgIndexes ? node.arguments.filter((_, i) => !h.fnArgIndexes.includes(i)) : node.arguments;
|
|
975
|
+
if (argNodes.length !== h.params.length)
|
|
904
976
|
this.fail(node, `${h.display}() takes ${h.params.length} argument(s)`);
|
|
905
977
|
// Arguments are evaluated left to right: settle earlier ones when a later one runs statements.
|
|
906
978
|
// A helper that takes accounts may change account data, so account text it gets is copied first.
|
|
@@ -908,7 +980,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
908
980
|
const values = [];
|
|
909
981
|
const pieces = [];
|
|
910
982
|
h.params.forEach((p, i) => {
|
|
911
|
-
const n =
|
|
983
|
+
const n = argNodes[i];
|
|
912
984
|
const mark = this.lines.length;
|
|
913
985
|
const raw = this.helperArg(h, p, n);
|
|
914
986
|
for (let j = 0; j < values.length; j++)
|
|
@@ -922,8 +994,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
922
994
|
});
|
|
923
995
|
// A helper that takes a writable struct account may replace anything stored in it (see assertNotAliased).
|
|
924
996
|
for (const [i, p] of h.params.entries())
|
|
925
|
-
if (p.account && p.account.kind === "account" && p.account.mut && ts.isIdentifier(
|
|
926
|
-
this.assertNotAliased([
|
|
997
|
+
if (p.account && p.account.kind === "account" && p.account.mut && ts.isIdentifier(argNodes[i]))
|
|
998
|
+
this.assertNotAliased([argNodes[i].text], node, h.display);
|
|
927
999
|
const args = ["params", ...pieces.map((x) => (typeof x === "number" ? (values[x].type.kind === "pubkey" ? `*(${values[x].c})` : values[x].c) : x))];
|
|
928
1000
|
if (!h.returns) {
|
|
929
1001
|
this.line(`TS_TRY(fn_${h.name}(${args.join(", ")}));`);
|
|
@@ -937,6 +1009,11 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
937
1009
|
this.line(`TS_TRY(fn_${h.name}(${[...args, `&${t}`].join(", ")}));`);
|
|
938
1010
|
return { c: `(&${t})`, type: { kind: "structRef", struct: h.returns.name, writable: true } };
|
|
939
1011
|
}
|
|
1012
|
+
if (h.returns.kind === "string") {
|
|
1013
|
+
this.line(`TS_Str ${t};`);
|
|
1014
|
+
this.line(`TS_TRY(fn_${h.name}(${[...args, `&${t}`].join(", ")}));`);
|
|
1015
|
+
return { c: t, type: STRING };
|
|
1016
|
+
}
|
|
940
1017
|
this.line(`${scalarC(h.returns)} ${t};`);
|
|
941
1018
|
this.line(`TS_TRY(fn_${h.name}(${[...args, `&${t}`].join(", ")}));`);
|
|
942
1019
|
return h.returns.kind === "pubkey" ? { c: `(&${t})`, type: h.returns } : { c: t, type: h.returns };
|
|
@@ -951,6 +1028,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
951
1028
|
// A @layout borsh account goes with the struct it is being worked on as.
|
|
952
1029
|
if (p.account.kind === "account" && p.account.struct.layout === "borsh")
|
|
953
1030
|
return `a_${acct.name}, d_${acct.name}`;
|
|
1031
|
+
if (p.account.kind === "account" && p.account.mut && p.account.struct.vec)
|
|
1032
|
+
return `a_${acct.name}, entry_${acct.name}`;
|
|
954
1033
|
return `a_${acct.name}`;
|
|
955
1034
|
}
|
|
956
1035
|
switch (p.type.kind) {
|
|
@@ -1013,6 +1092,13 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
1013
1092
|
this.fail(n, "pass signer([...seeds with the bump]) or a SignerSeeds parameter");
|
|
1014
1093
|
}
|
|
1015
1094
|
errorRef(node) {
|
|
1095
|
+
// `AnchorError.X`: one of Anchor's own codes (not one of this program's errors, so not in the IDL).
|
|
1096
|
+
if (ts.isPropertyAccessExpression(node) && this.exportName(node.expression) === "AnchorError") {
|
|
1097
|
+
const code = ANCHOR_ERRORS[node.name.text];
|
|
1098
|
+
if (code === undefined)
|
|
1099
|
+
this.fail(node.name, `AnchorError has no ${node.name.text}`);
|
|
1100
|
+
return { code, name: node.name.text, msg: node.name.text };
|
|
1101
|
+
}
|
|
1016
1102
|
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
|
1017
1103
|
const def = this.model.errorEnums.get(node.expression.text)?.get(node.name.text);
|
|
1018
1104
|
if (def)
|
|
@@ -26,6 +26,26 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
26
26
|
protected describeSeed(el: ts.Expression): IdlSeed | undefined;
|
|
27
27
|
protected recordPda(account: AccountParam, idl: IdlSeed[] | undefined): void;
|
|
28
28
|
protected create(node: ts.CallExpression, target: AccountParam, topLevel: boolean, ifNeeded?: boolean): Value;
|
|
29
|
+
/**
|
|
30
|
+
* `vault.create(payer, seeds | null, mint, authority)` on `Init<TokenAccount>` and `mint.create(payer, seeds | null,
|
|
31
|
+
* decimals, authority, freezeAuthority | null)` on `Init<Mint>` (Anchor's `init` with `token::` / `mint::`
|
|
32
|
+
* constraints): a PDA of this program (or, with `null`, a new keypair account that signs), created owned by the
|
|
33
|
+
* instruction's token program and initialized. `createIfNeeded` on `InitIfNeeded<…>` does that unless the account
|
|
34
|
+
* exists, and then checks it is that PDA, owned by the token program, and has that mint and owner / those
|
|
35
|
+
* authorities and decimals (Anchor's init_if_needed checks). create returns the bump (0 for a keypair),
|
|
36
|
+
* createIfNeeded whether it created the account.
|
|
37
|
+
*/
|
|
38
|
+
protected createToken(node: ts.CallExpression, target: AccountParam, topLevel: boolean): Value;
|
|
39
|
+
/**
|
|
40
|
+
* The extensions in `mint.create(…, { … })` (Anchor's `extensions::…` constraints): their values are evaluated now,
|
|
41
|
+
* in source order; the returned lines (each extension's Token-2022 initializer, mint writable, before
|
|
42
|
+
* InitializeMint2) run after the account is created, sized for exactly these extensions.
|
|
43
|
+
*/
|
|
44
|
+
protected mintExtensions(lit: ts.ObjectLiteralExpression, tp: string, acct: string, ifNeeded?: boolean): {
|
|
45
|
+
space: number;
|
|
46
|
+
lines: string[];
|
|
47
|
+
checks: string[];
|
|
48
|
+
};
|
|
29
49
|
/**
|
|
30
50
|
* `ata.create(payer)` / `ata.createIfNeeded(payer)` on `Ata<Init|InitIfNeeded<TokenAccount>, "owner", "mint">`
|
|
31
51
|
* (Anchor's `init` / `init_if_needed` with `associated_token::…`). create always calls the ATA program's Create, which
|
|
@@ -47,6 +67,7 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
47
67
|
}, tail?: {
|
|
48
68
|
from: string;
|
|
49
69
|
to: string;
|
|
70
|
+
asPassed?: boolean;
|
|
50
71
|
}): void;
|
|
51
72
|
protected signerSeeds(node: ts.Expression | undefined, authority: AccountParam, at: ts.Node): {
|
|
52
73
|
name: string;
|
|
@@ -87,6 +108,12 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
87
108
|
protected bubblegumOp(node: ts.CallExpression, bg: AccountParam, method: string): undefined;
|
|
88
109
|
/** Bubblegum V2 (trees on mpl-account-compression with mpl-noop; optional Metaplex Core collections). */
|
|
89
110
|
protected bubblegumV2(node: ts.CallExpression, bg: AccountParam, method: string): undefined;
|
|
111
|
+
/** Object literal properties by name, refusing others. */
|
|
112
|
+
protected literalProps(o: ts.ObjectLiteralExpression, allowed: string[], what: string): Map<string, ts.Expression>;
|
|
113
|
+
/** Vec<Creator> (address, verified, share) from a literal list; shares add up to 100 when literal. */
|
|
114
|
+
protected bubblegumCreators(creators: ts.Expression | undefined): MplField[];
|
|
115
|
+
/** UpdateArgs: every field optional (left out or null: unchanged). */
|
|
116
|
+
protected bubblegumUpdateArgs(n: ts.Expression): MplField[];
|
|
90
117
|
/** `acct.key` of an account parameter, as an expression to serialize. */
|
|
91
118
|
protected keyOf(n: ts.Expression): ts.Expression;
|
|
92
119
|
/**
|
|
@@ -117,6 +144,10 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
117
144
|
authority: string;
|
|
118
145
|
program: string;
|
|
119
146
|
}): undefined;
|
|
147
|
+
/** emit<T> of a @layout borsh struct: built like account data, then packed as Anchor serializes it. */
|
|
148
|
+
private emitBorshEvent;
|
|
149
|
+
/** Discriminator + `body`, logged (emit) or self-invoked (emitCpi). */
|
|
150
|
+
private sendEvent;
|
|
120
151
|
/** Structs reachable through the fields of struct `name` (not including itself unless it recurses). */
|
|
121
152
|
protected nestedStructs(name: string): Set<string>;
|
|
122
153
|
/** Structs whose IDL description is the fixed layout: account data, arguments, and everything nested in them. */
|