@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,9 +1,9 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { calleeName, resolveScalarType } from "../analyze.js";
|
|
2
|
+
import { calleeName, resolveScalarType, resolveValueType } from "../analyze.js";
|
|
3
3
|
import { ANCHOR_ERRORS } from "../anchor-errors.js";
|
|
4
4
|
import { decodeAddress } from "../../keys.js";
|
|
5
5
|
import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
|
|
6
|
-
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, } from "./shared.js";
|
|
6
|
+
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, emptySizeC, vecLenCheck, } from "./shared.js";
|
|
7
7
|
import { ExpressionEmitter } from "./expressions.js";
|
|
8
8
|
/**
|
|
9
9
|
* Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
|
|
@@ -195,7 +195,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
195
195
|
if (!isWritable(acct.kind) || !struct)
|
|
196
196
|
this.fail(args[0], "realloc needs a Mut<Struct> account of this program");
|
|
197
197
|
if (struct?.vec)
|
|
198
|
-
this.fail(args[0], `${struct.name}
|
|
198
|
+
this.fail(args[0], `${struct.name} has a growable Vec, which sizes the account itself (push/pop/clear)`);
|
|
199
199
|
const size = this.expect(this.expr(args[1], U64), U64, args[1]);
|
|
200
200
|
const payer = this.accountParam(args[2], "the payer");
|
|
201
201
|
if (payer.kind.kind !== "signer" || !payer.kind.mut)
|
|
@@ -460,11 +460,25 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
460
460
|
}
|
|
461
461
|
if (builtin)
|
|
462
462
|
this.fail(callee, `${builtin} cannot be called here`);
|
|
463
|
+
// `adder(5n)(1n)`: the function a function returns, called right away.
|
|
464
|
+
if (ts.isCallExpression(callee)) {
|
|
465
|
+
const made = this.madeFunction(callee);
|
|
466
|
+
if (made)
|
|
467
|
+
return this.expandClosure(made, node.arguments, node);
|
|
468
|
+
}
|
|
463
469
|
if (ts.isIdentifier(callee)) {
|
|
464
470
|
const local = this.lookup(callee.text);
|
|
471
|
+
if (local?.closure && this.returnedFunction(local.closure.node))
|
|
472
|
+
this.fail(node, `${callee.text} returns a function: bind it (const f = ${callee.text}(…)) or call it right away (${callee.text}(…)(…))`);
|
|
465
473
|
if (local?.closure)
|
|
466
474
|
return this.expandClosure(local.closure, node.arguments, node);
|
|
475
|
+
const name = calleeName(callee, this.model.helperDecls, this.checker) ?? callee.text;
|
|
476
|
+
if (!local && this.model.factories.has(name))
|
|
477
|
+
this.fail(node, `${callee.text} returns a function: bind it (const f = ${callee.text}(…)) or call it right away (${callee.text}(…)(…))`);
|
|
467
478
|
}
|
|
479
|
+
const inlined = this.helperWithClosure(node);
|
|
480
|
+
if (inlined)
|
|
481
|
+
return inlined.value;
|
|
468
482
|
const helper = this.helperOf(node);
|
|
469
483
|
if (helper)
|
|
470
484
|
return this.callHelper(node, helper);
|
|
@@ -473,9 +487,12 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
473
487
|
// xs.some(…) and friends on a Vec, FixedArray, FixedVec or Vec argument: a loop with the callback expanded in it.
|
|
474
488
|
if (LIST_METHODS.has(method)) {
|
|
475
489
|
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))
|
|
490
|
+
if (l?.type.kind === "vecArg" || l?.type.kind === "array" || this.isPipeline(callee.expression) || this.peekArray(callee.expression))
|
|
477
491
|
return this.listMethod(node, method);
|
|
478
492
|
}
|
|
493
|
+
// A map / filter chain is consumed by a list method, `.length` or for…of, or stored: const ys = xs.map(…).
|
|
494
|
+
if (this.isPipeline(node))
|
|
495
|
+
this.fail(node, `${method} builds a new list: store it (const ys = xs.${method}(…)), or use it right away with a list method, .length or for…of`);
|
|
479
496
|
// Vec<T> instruction arguments are read-only views.
|
|
480
497
|
if (ts.isIdentifier(callee.expression)) {
|
|
481
498
|
const l = this.lookup(callee.expression.text);
|
|
@@ -620,6 +637,21 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
620
637
|
this.fail(node, "only Vecs of Mut<...> / Init<...> accounts can grow or shrink");
|
|
621
638
|
this.line(`TS_TRY(ts_resize(params, ${payer}, a_${v.account.name}, 8 + ${info.fixed} + (${newLen}) * ${info.elemSize}, entry_${v.account.name}));`);
|
|
622
639
|
};
|
|
640
|
+
/**
|
|
641
|
+
* The growable Vecs stored after this one move by `by` bytes (+ grows, - shrinks): where they start and how many
|
|
642
|
+
* bytes they take, read before the resize; references into them cannot outlive this call.
|
|
643
|
+
*/
|
|
644
|
+
const tail = info.tail;
|
|
645
|
+
const tailStart = () => {
|
|
646
|
+
if (!tail)
|
|
647
|
+
return undefined;
|
|
648
|
+
this.assertNotAliased(tail.paths, node);
|
|
649
|
+
const at = this.tmp("tail");
|
|
650
|
+
const n = this.tmp("tail_n");
|
|
651
|
+
this.line(`uint8_t *${at} = ${tail.end};`);
|
|
652
|
+
this.line(`uint64_t ${n} = (uint64_t)(${tail.dataEnd} - ${at});`);
|
|
653
|
+
return { at, n };
|
|
654
|
+
};
|
|
623
655
|
switch (method) {
|
|
624
656
|
case "set": {
|
|
625
657
|
if (args.length !== 2)
|
|
@@ -655,8 +687,11 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
655
687
|
const write = stageElem(args[0]);
|
|
656
688
|
const n = this.tmp("n");
|
|
657
689
|
this.line(`uint64_t ${n} = (uint64_t)${info.len};`);
|
|
658
|
-
|
|
690
|
+
const t = tailStart();
|
|
691
|
+
// Resizing does not move the data, so element pointers stay valid; the Vecs after this one shift up.
|
|
659
692
|
resize(`${n} + 1`, `a_${payer.name}`);
|
|
693
|
+
if (t)
|
|
694
|
+
this.line(`ts_memmove(${t.at} + ${info.elemSize}, ${t.at}, ${t.n});`);
|
|
660
695
|
this.line(`${info.len} = (uint32_t)(${n} + 1);`);
|
|
661
696
|
write(elemRef(n));
|
|
662
697
|
return undefined;
|
|
@@ -686,6 +721,9 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
686
721
|
this.line(`${valueC(st)} ${out} = ${val.c};`);
|
|
687
722
|
result = st.kind === "pubkey" ? { c: `(&${out})`, type: st } : { c: out, type: st };
|
|
688
723
|
}
|
|
724
|
+
const t = info.cap === undefined ? tailStart() : undefined;
|
|
725
|
+
if (t)
|
|
726
|
+
this.line(`ts_memmove(${t.at} - ${info.elemSize}, ${t.at}, ${t.n});`);
|
|
689
727
|
this.line(`${info.len} = (uint32_t)(${n} - 1);`);
|
|
690
728
|
resize(`${n} - 1`, "NULL");
|
|
691
729
|
return result;
|
|
@@ -695,6 +733,9 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
695
733
|
this.fail(node, "clear() takes no arguments");
|
|
696
734
|
needWritable();
|
|
697
735
|
this.assertNotAliased(elemPaths(), node);
|
|
736
|
+
const t = info.cap === undefined ? tailStart() : undefined;
|
|
737
|
+
if (t)
|
|
738
|
+
this.line(`ts_memmove((uint8_t *)${info.items}, ${t.at}, ${t.n});`);
|
|
698
739
|
this.line(`${info.len} = 0;`);
|
|
699
740
|
resize("0", "NULL");
|
|
700
741
|
return undefined;
|
|
@@ -933,9 +974,10 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
933
974
|
if (this.model.events.has(s.name) && s.fields.some((f) => f.type.kind === "option" || f.type.kind === "dataEnum"))
|
|
934
975
|
this.fail(node, `${s.name} is emitted as an event and has Option or enum fields; declare a separate event struct`);
|
|
935
976
|
this.model.accountStructs.add(s.name);
|
|
936
|
-
this.line(`TS_TRY(ts_check_program_account(${h}, params->program_id, TS_DISC_${s.name},
|
|
977
|
+
this.line(`TS_TRY(ts_check_program_account(${h}, params->program_id, TS_DISC_${s.name}, ${emptySizeC(s)}));`);
|
|
937
978
|
if (s.vec)
|
|
938
|
-
|
|
979
|
+
for (const l of vecLenCheck(s, h, `((TSS_${s.name} *)(${h}->data + 8))`, ""))
|
|
980
|
+
this.line(l);
|
|
939
981
|
if (mut) {
|
|
940
982
|
this.line(`TS_TRY(ts_check_writable(${h}));`);
|
|
941
983
|
this.line(`TS_TRY(ts_check_unaliased(params, ${h}));`);
|
|
@@ -967,6 +1009,116 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
967
1009
|
visit(body);
|
|
968
1010
|
return found;
|
|
969
1011
|
}
|
|
1012
|
+
/**
|
|
1013
|
+
* A helper given a closure (a function that reads the caller's variables) for a function parameter: a closure only
|
|
1014
|
+
* runs where it is declared, so the helper is expanded here, like a closure, with the closure bound to the
|
|
1015
|
+
* parameter. Helpers expanded so take values (not accounts) and have no type parameters.
|
|
1016
|
+
*/
|
|
1017
|
+
helperWithClosure(node) {
|
|
1018
|
+
const g = this.givenClosure(node);
|
|
1019
|
+
if (!g)
|
|
1020
|
+
return undefined;
|
|
1021
|
+
// Type parameters: bound from the call (as an instance would be) while the body is expanded.
|
|
1022
|
+
const saved = this.scope;
|
|
1023
|
+
if (g.typeParams.length) {
|
|
1024
|
+
const typeArgs = this.typeArgsOf(node, g);
|
|
1025
|
+
const bound = new Map();
|
|
1026
|
+
const structs = new Map();
|
|
1027
|
+
g.typeParams.forEach((n, i) => {
|
|
1028
|
+
const t = typeArgs[i];
|
|
1029
|
+
if (t.kind === "struct")
|
|
1030
|
+
structs.set(n, t.name);
|
|
1031
|
+
else
|
|
1032
|
+
bound.set(n, t);
|
|
1033
|
+
});
|
|
1034
|
+
this.scope = { ...this.scope, typeArgs: bound, structArgs: structs };
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
return { value: this.expandClosure({ node: g.node, name: g.display, scopes: [], env: this.noEnv(), imports: g.imports }, node.arguments, node) };
|
|
1038
|
+
}
|
|
1039
|
+
finally {
|
|
1040
|
+
this.scope = saved;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/** The function a function returns (`return (x) => x + n`, or an arrow body `(n) => (x) => x + n`), if it returns one. */
|
|
1044
|
+
returnedFunction(fn) {
|
|
1045
|
+
const strip = (e) => (e && ts.isParenthesizedExpression(e) ? strip(e.expression) : e);
|
|
1046
|
+
const body = fn.body;
|
|
1047
|
+
let ret;
|
|
1048
|
+
if (body && !ts.isBlock(body))
|
|
1049
|
+
ret = strip(body);
|
|
1050
|
+
else if (body && body.statements.length === 1 && ts.isReturnStatement(body.statements[0]))
|
|
1051
|
+
ret = strip(body.statements[0].expression);
|
|
1052
|
+
if (ret && (ts.isArrowFunction(ret) || ts.isFunctionExpression(ret)))
|
|
1053
|
+
return ret;
|
|
1054
|
+
if (fn.type && ts.isFunctionTypeNode(fn.type))
|
|
1055
|
+
this.fail(fn, "a function that returns a function has one statement: return (x) => … (the function it makes)");
|
|
1056
|
+
return undefined;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* `adder(5n)`, where `adder` returns a function: the arguments are evaluated here into locals of the current block,
|
|
1060
|
+
* and the function it returns is bound to them as a closure (expanded where it is called, like any closure).
|
|
1061
|
+
*/
|
|
1062
|
+
madeFunction(call) {
|
|
1063
|
+
if (!ts.isIdentifier(call.expression))
|
|
1064
|
+
return undefined;
|
|
1065
|
+
const local = this.lookup(call.expression.text);
|
|
1066
|
+
let maker = local?.closure;
|
|
1067
|
+
if (!maker && !local) {
|
|
1068
|
+
const name = calleeName(call.expression, this.model.helperDecls, this.checker) ?? call.expression.text;
|
|
1069
|
+
const f = this.model.factories.get(name);
|
|
1070
|
+
if (f)
|
|
1071
|
+
maker = { node: f.node, name: call.expression.text, scopes: [], env: this.noEnv(), imports: f.imports };
|
|
1072
|
+
}
|
|
1073
|
+
if (!maker)
|
|
1074
|
+
return undefined;
|
|
1075
|
+
const made = this.returnedFunction(maker.node);
|
|
1076
|
+
if (!made)
|
|
1077
|
+
return undefined;
|
|
1078
|
+
const fn = maker.node;
|
|
1079
|
+
if (call.arguments.length !== fn.parameters.length)
|
|
1080
|
+
this.fail(call, `${maker.name}() takes ${fn.parameters.length} argument(s)`);
|
|
1081
|
+
const params = new Map();
|
|
1082
|
+
fn.parameters.forEach((p, i) => {
|
|
1083
|
+
if (!ts.isIdentifier(p.name) || p.initializer || p.dotDotDotToken)
|
|
1084
|
+
this.fail(p, "parameters are plain names");
|
|
1085
|
+
const arg = call.arguments[i];
|
|
1086
|
+
if (p.type && ts.isFunctionTypeNode(p.type)) {
|
|
1087
|
+
params.set(p.name.text, { c: "", type: BOOL, mutable: false, closure: this.closureOf(arg) });
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const annotated = p.type ? resolveValueType(p.type, this.scope) : undefined;
|
|
1091
|
+
let v = this.expr(arg, annotated);
|
|
1092
|
+
if (v.literal !== undefined && !annotated)
|
|
1093
|
+
v = this.literal(v.literal, U64, arg);
|
|
1094
|
+
const type = annotated ?? v.type;
|
|
1095
|
+
if (!["int", "bool", "pubkey", "string", "enum", "option"].includes(type.kind))
|
|
1096
|
+
this.fail(arg, `the function made keeps integers, booleans, Pubkeys, strings, enums or Options, not ${typeName(type)}`);
|
|
1097
|
+
v = this.expect(v, type, arg);
|
|
1098
|
+
const c = `l_${p.name.text}_${this.temp++}`;
|
|
1099
|
+
if (type.kind === "pubkey")
|
|
1100
|
+
this.line(`SolPubkey ${c} = *(${v.c});`);
|
|
1101
|
+
else
|
|
1102
|
+
this.line(`${valueC(type)} ${c} = ${this.ownedString(v).c};`);
|
|
1103
|
+
params.set(p.name.text, { c, type, mutable: true });
|
|
1104
|
+
});
|
|
1105
|
+
return { node: made, name: `the function ${maker.name}() made`, scopes: [...maker.scopes, params], env: maker.env, imports: maker.imports };
|
|
1106
|
+
}
|
|
1107
|
+
/** A function given for a function parameter of an expanded helper or closure, as something to expand. */
|
|
1108
|
+
closureOf(arg) {
|
|
1109
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg))
|
|
1110
|
+
return { node: arg, name: "the function passed", scopes: [...this.scopes], env: this.envNow(), imports: this.scope.imports };
|
|
1111
|
+
if (ts.isIdentifier(arg)) {
|
|
1112
|
+
const l = this.lookup(arg.text);
|
|
1113
|
+
if (l?.closure)
|
|
1114
|
+
return l.closure;
|
|
1115
|
+
const name = calleeName(arg, this.model.helperDecls, this.checker) ?? arg.text;
|
|
1116
|
+
const h = this.model.helpers.get(name) ?? this.model.generics.get(name);
|
|
1117
|
+
if (h)
|
|
1118
|
+
return { node: h.node, name: h.display, scopes: [], env: this.noEnv(), imports: h.imports };
|
|
1119
|
+
}
|
|
1120
|
+
this.fail(arg, "pass a function: its name, or an arrow written here");
|
|
1121
|
+
}
|
|
970
1122
|
callHelper(node, h) {
|
|
971
1123
|
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
1124
|
this.fail(node, `${h.display}() grows a Vec (push): the instruction needs a SystemProgram parameter to top up rent`);
|
|
@@ -8,6 +8,9 @@ type MplField = {
|
|
|
8
8
|
} | {
|
|
9
9
|
value: ts.Expression;
|
|
10
10
|
type: ValueType;
|
|
11
|
+
} | {
|
|
12
|
+
c: string;
|
|
13
|
+
n: string;
|
|
11
14
|
};
|
|
12
15
|
/**
|
|
13
16
|
* Other programs: PDAs and seeds, account creation, System/Token/ATA/Metaplex CPIs, generic invoke, Borsh
|
|
@@ -102,6 +105,26 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
102
105
|
protected corePluginList(n: ts.Expression | undefined): MplField[];
|
|
103
106
|
/** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
|
|
104
107
|
protected coreOp(node: ts.CallExpression, core: AccountParam, method: string): undefined;
|
|
108
|
+
/** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
|
|
109
|
+
private coreProps;
|
|
110
|
+
/** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */
|
|
111
|
+
private coreAuthority;
|
|
112
|
+
private coreOption;
|
|
113
|
+
private coreSchema;
|
|
114
|
+
/** Vec<(HookableLifecycleEvent, ExternalCheckResult)>: `[{ event: "transfer", canReject: true }, …]`. */
|
|
115
|
+
private coreChecks;
|
|
116
|
+
/** ExtraAccount: `{ kind: "address", address, isSigner?, isWritable? }` or a preconfigured one (`{ kind: "owner" }`, …). */
|
|
117
|
+
private coreExtraAccount;
|
|
118
|
+
/**
|
|
119
|
+
* ExternalPluginAdapterInitInfo from a literal: `{ kind: "AppData", dataAuthority, schema? }`, `{ kind: "Oracle",
|
|
120
|
+
* baseAddress, checks, resultsOffset?, baseAddressConfig? }`, `{ kind: "LifecycleHook", hookedProgram, checks,
|
|
121
|
+
* extraAccounts?, dataAuthority?, schema? }` and the Linked forms (collections); `authority?` is the plugin's own.
|
|
122
|
+
*/
|
|
123
|
+
protected coreExternalPlugin(n: ts.Expression): MplField[];
|
|
124
|
+
/** ExternalPluginAdapterKey: `{ kind: "AppData", dataAuthority }`, `{ kind: "Oracle", address }`, `{ kind: "LifecycleHook", address }`, … */
|
|
125
|
+
protected coreExternalKey(n: ts.Expression): MplField[];
|
|
126
|
+
/** The data written: Option<Vec<u8>> from a string, a Vec<u8> argument, or null (clears it). */
|
|
127
|
+
protected coreExternalData(n: ts.Expression): MplField[];
|
|
105
128
|
/** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
|
|
106
129
|
protected corePluginType(n: ts.Expression): MplField;
|
|
107
130
|
/** Bubblegum (compressed NFTs on SPL account compression): createTree, mint (MintV1, no collection, no creators). */
|
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
import { discriminator, snakeCase } from "../analyze.js";
|
|
3
3
|
import { borshMinSize } from "./borsh.js";
|
|
4
4
|
import { INT_INFO, isScalar, isWritable, optionKey, sameType, scalarSize, typeName, } from "../model.js";
|
|
5
|
-
import { MAX_ACCOUNTS, MAX_SEEDS, MAX_CPI_DATA, MAX_EVENT, U64, U8, BOOL, PUBKEY, STRING, cBytes, isStable, cString, scalarStorageC, optC, valueC, } from "./shared.js";
|
|
5
|
+
import { MAX_ACCOUNTS, MAX_SEEDS, MAX_CPI_DATA, MAX_EVENT, U64, U8, BOOL, PUBKEY, STRING, cBytes, isStable, cString, scalarStorageC, optC, valueC, emptySizeC, fixedVecC, vecCount, } from "./shared.js";
|
|
6
6
|
import { CallEmitter } from "./calls.js";
|
|
7
7
|
/**
|
|
8
8
|
* Bubblegum instructions that replace a leaf: discriminator, accounts in order ([label, writable, signer, optional,
|
|
@@ -321,7 +321,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
321
321
|
const b = this.tmp("bump");
|
|
322
322
|
// The account's size: the struct's (8 + its fixed size, Anchor's InitSpace for @layout borsh), or Anchor's
|
|
323
323
|
// `space = …` when given, which must hold the struct.
|
|
324
|
-
const need = `8 + sizeof(TSS_${s.name})`;
|
|
324
|
+
const need = s.layout === "borsh" ? `8 + sizeof(TSS_${s.name})` : `8 + ${emptySizeC(s)}`;
|
|
325
325
|
let space = need;
|
|
326
326
|
const spaceArg = node.arguments[2];
|
|
327
327
|
if (spaceArg) {
|
|
@@ -1526,6 +1526,8 @@ export class CpiEmitter extends CallEmitter {
|
|
|
1526
1526
|
this.line(`static const uint8_t ${name}[${f.bytes.length}] = ${cBytes(f.bytes)};`);
|
|
1527
1527
|
put(name, String(f.bytes.length), f.bytes.length);
|
|
1528
1528
|
}
|
|
1529
|
+
else if ("c" in f)
|
|
1530
|
+
put(f.c, f.n);
|
|
1529
1531
|
else
|
|
1530
1532
|
this.serialize(this.expect(this.expr(f.value, f.type), f.type, f.value), f.value, put);
|
|
1531
1533
|
}
|
|
@@ -1829,8 +1831,237 @@ export class CpiEmitter extends CallEmitter {
|
|
|
1829
1831
|
this.cpi(p, metas, data.buf, data.len, seedsAt(4));
|
|
1830
1832
|
return undefined;
|
|
1831
1833
|
}
|
|
1834
|
+
case "addExternalPlugin":
|
|
1835
|
+
case "removeExternalPlugin":
|
|
1836
|
+
case "writeExternalPluginData": {
|
|
1837
|
+
// Add/RemoveExternalPluginAdapterV1: asset, collection, payer, authority, systemProgram, logWrapper;
|
|
1838
|
+
// WriteExternalPluginAdapterDataV1 has a buffer account (left out: the data is in the instruction) before systemProgram.
|
|
1839
|
+
const write = method === "writeExternalPluginData";
|
|
1840
|
+
const n = write ? 7 : 6;
|
|
1841
|
+
if (a.length !== n && a.length !== n + 1)
|
|
1842
|
+
this.fail(node, `${method}(asset, collection | null, payer, authority | null, systemProgram, ${method === "addExternalPlugin" ? "plugin" : write ? "key, data" : "key"}, signerSeeds?)`);
|
|
1843
|
+
const metas = [
|
|
1844
|
+
this.mplMeta(a[0], core, "the asset", true, false),
|
|
1845
|
+
this.mplMeta(a[1], core, "the collection", true, false),
|
|
1846
|
+
this.mplMeta(a[2], core, "the payer", true, true),
|
|
1847
|
+
this.mplMeta(a[3], core, "the authority", false, true),
|
|
1848
|
+
...(write ? [self] : []),
|
|
1849
|
+
this.mplMeta(a[4], core, "the system program", false, false),
|
|
1850
|
+
self,
|
|
1851
|
+
];
|
|
1852
|
+
const body = method === "addExternalPlugin"
|
|
1853
|
+
? this.coreExternalPlugin(a[5])
|
|
1854
|
+
: write
|
|
1855
|
+
? [...this.coreExternalKey(a[5]), ...this.coreExternalData(a[6])]
|
|
1856
|
+
: this.coreExternalKey(a[5]);
|
|
1857
|
+
const data = this.mplData(node, 1024, [{ bytes: [{ addExternalPlugin: 22, removeExternalPlugin: 24, writeExternalPluginData: 28 }[method]] }, ...body]);
|
|
1858
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(n));
|
|
1859
|
+
return undefined;
|
|
1860
|
+
}
|
|
1861
|
+
case "addCollectionExternalPlugin":
|
|
1862
|
+
case "removeCollectionExternalPlugin":
|
|
1863
|
+
case "writeCollectionExternalPluginData": {
|
|
1864
|
+
// The same on a collection: collection, payer, authority, (buffer,) systemProgram, logWrapper.
|
|
1865
|
+
const write = method === "writeCollectionExternalPluginData";
|
|
1866
|
+
const n = write ? 6 : 5;
|
|
1867
|
+
if (a.length !== n && a.length !== n + 1)
|
|
1868
|
+
this.fail(node, `${method}(collection, payer, authority | null, systemProgram, ${method === "addCollectionExternalPlugin" ? "plugin" : write ? "key, data" : "key"}, signerSeeds?)`);
|
|
1869
|
+
const metas = [
|
|
1870
|
+
this.mplMeta(a[0], core, "the collection", true, false),
|
|
1871
|
+
this.mplMeta(a[1], core, "the payer", true, true),
|
|
1872
|
+
this.mplMeta(a[2], core, "the authority", false, true),
|
|
1873
|
+
...(write ? [self] : []),
|
|
1874
|
+
this.mplMeta(a[3], core, "the system program", false, false),
|
|
1875
|
+
self,
|
|
1876
|
+
];
|
|
1877
|
+
const body = method === "addCollectionExternalPlugin"
|
|
1878
|
+
? this.coreExternalPlugin(a[4])
|
|
1879
|
+
: write
|
|
1880
|
+
? [...this.coreExternalKey(a[4]), ...this.coreExternalData(a[5])]
|
|
1881
|
+
: this.coreExternalKey(a[4]);
|
|
1882
|
+
const data = this.mplData(node, 1024, [
|
|
1883
|
+
{ bytes: [{ addCollectionExternalPlugin: 23, removeCollectionExternalPlugin: 25, writeCollectionExternalPluginData: 29 }[method]] },
|
|
1884
|
+
...body,
|
|
1885
|
+
]);
|
|
1886
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(n));
|
|
1887
|
+
return undefined;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin, addExternalPlugin, removeExternalPlugin, writeExternalPluginData (and the Collection forms), transferAsset and burnAsset, not ${method}`);
|
|
1891
|
+
}
|
|
1892
|
+
/** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
|
|
1893
|
+
coreProps(node, what) {
|
|
1894
|
+
let n = node;
|
|
1895
|
+
while (ts.isParenthesizedExpression(n) || ts.isAsExpression(n) || ts.isSatisfiesExpression(n))
|
|
1896
|
+
n = n.expression;
|
|
1897
|
+
if (!ts.isObjectLiteralExpression(n))
|
|
1898
|
+
this.fail(n, `${what} is an object literal: { kind: "…", … }`);
|
|
1899
|
+
const props = new Map();
|
|
1900
|
+
for (const q of n.properties) {
|
|
1901
|
+
if (ts.isPropertyAssignment(q) && ts.isIdentifier(q.name))
|
|
1902
|
+
props.set(q.name.text, q.initializer);
|
|
1903
|
+
else if (ts.isShorthandPropertyAssignment(q))
|
|
1904
|
+
props.set(q.name.text, q.name);
|
|
1905
|
+
else
|
|
1906
|
+
this.fail(q, "write the fields as name: value");
|
|
1907
|
+
}
|
|
1908
|
+
return props;
|
|
1909
|
+
}
|
|
1910
|
+
/** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */
|
|
1911
|
+
coreAuthority(n) {
|
|
1912
|
+
if (ts.isStringLiteral(n)) {
|
|
1913
|
+
const i = ["none", "owner", "updateAuthority"].indexOf(n.text);
|
|
1914
|
+
if (i < 0)
|
|
1915
|
+
this.fail(n, 'an authority is "none", "owner", "updateAuthority" or a Pubkey');
|
|
1916
|
+
return [{ bytes: [i] }];
|
|
1917
|
+
}
|
|
1918
|
+
return [{ bytes: [3] }, { value: n, type: PUBKEY }];
|
|
1919
|
+
}
|
|
1920
|
+
coreOption(n, inner) {
|
|
1921
|
+
return n ? [{ bytes: [1] }, ...inner(n)] : [{ bytes: [0] }];
|
|
1922
|
+
}
|
|
1923
|
+
coreSchema(n) {
|
|
1924
|
+
const i = ts.isStringLiteral(n) ? ["binary", "json", "msgpack"].indexOf(n.text) : -1;
|
|
1925
|
+
if (i < 0)
|
|
1926
|
+
this.fail(n, 'schema is "binary", "json" or "msgpack"');
|
|
1927
|
+
return [{ bytes: [i] }];
|
|
1928
|
+
}
|
|
1929
|
+
/** Vec<(HookableLifecycleEvent, ExternalCheckResult)>: `[{ event: "transfer", canReject: true }, …]`. */
|
|
1930
|
+
coreChecks(n, at) {
|
|
1931
|
+
if (!n || !ts.isArrayLiteralExpression(n))
|
|
1932
|
+
this.fail(n ?? at, 'checks is a list: [{ event: "transfer", canApprove: true, canReject: true }]');
|
|
1933
|
+
const out = [{ bytes: [n.elements.length & 0xff, 0, 0, 0] }];
|
|
1934
|
+
for (const e of n.elements) {
|
|
1935
|
+
const p = this.coreProps(e, "a lifecycle check");
|
|
1936
|
+
const ev = p.get("event");
|
|
1937
|
+
const i = ev && ts.isStringLiteral(ev) ? ["create", "transfer", "burn", "update", "execute"].indexOf(ev.text) : -1;
|
|
1938
|
+
if (i < 0)
|
|
1939
|
+
this.fail(ev ?? e, 'event is "create", "transfer", "burn", "update" or "execute"');
|
|
1940
|
+
let flags = 0;
|
|
1941
|
+
for (const [k, bit] of [
|
|
1942
|
+
["canListen", 1],
|
|
1943
|
+
["canApprove", 2],
|
|
1944
|
+
["canReject", 4],
|
|
1945
|
+
]) {
|
|
1946
|
+
const v = p.get(k);
|
|
1947
|
+
if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
|
|
1948
|
+
this.fail(v, `${k} is true or false`);
|
|
1949
|
+
if (v?.kind === ts.SyntaxKind.TrueKeyword)
|
|
1950
|
+
flags |= bit;
|
|
1951
|
+
}
|
|
1952
|
+
for (const k of p.keys())
|
|
1953
|
+
if (!["event", "canListen", "canApprove", "canReject"].includes(k))
|
|
1954
|
+
this.fail(e, `a lifecycle check has event, canListen, canApprove and canReject, not ${k}`);
|
|
1955
|
+
out.push({ bytes: [i, flags, 0, 0, 0] });
|
|
1956
|
+
}
|
|
1957
|
+
return out;
|
|
1958
|
+
}
|
|
1959
|
+
/** ExtraAccount: `{ kind: "address", address, isSigner?, isWritable? }` or a preconfigured one (`{ kind: "owner" }`, …). */
|
|
1960
|
+
coreExtraAccount(n) {
|
|
1961
|
+
const p = this.coreProps(n, "an extra account");
|
|
1962
|
+
const kind = p.get("kind");
|
|
1963
|
+
const kinds = ["program", "collection", "owner", "recipient", "asset", "customPda", "address"];
|
|
1964
|
+
const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
|
|
1965
|
+
if (i < 0 || i === 5)
|
|
1966
|
+
this.fail(kind ?? n, 'an extra account is { kind: "address", address } or "program", "collection", "owner", "recipient", "asset"');
|
|
1967
|
+
const flag = (k) => {
|
|
1968
|
+
const v = p.get(k);
|
|
1969
|
+
if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
|
|
1970
|
+
this.fail(v, `${k} is true or false`);
|
|
1971
|
+
return v?.kind === ts.SyntaxKind.TrueKeyword ? 1 : 0;
|
|
1972
|
+
};
|
|
1973
|
+
if (i === 6) {
|
|
1974
|
+
const addr = p.get("address");
|
|
1975
|
+
if (!addr)
|
|
1976
|
+
this.fail(n, "an address extra account needs address");
|
|
1977
|
+
return [{ bytes: [6] }, { value: addr, type: PUBKEY }, { bytes: [flag("isSigner"), flag("isWritable")] }];
|
|
1978
|
+
}
|
|
1979
|
+
return [{ bytes: [i, flag("isSigner"), flag("isWritable")] }];
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* ExternalPluginAdapterInitInfo from a literal: `{ kind: "AppData", dataAuthority, schema? }`, `{ kind: "Oracle",
|
|
1983
|
+
* baseAddress, checks, resultsOffset?, baseAddressConfig? }`, `{ kind: "LifecycleHook", hookedProgram, checks,
|
|
1984
|
+
* extraAccounts?, dataAuthority?, schema? }` and the Linked forms (collections); `authority?` is the plugin's own.
|
|
1985
|
+
*/
|
|
1986
|
+
coreExternalPlugin(n) {
|
|
1987
|
+
const p = this.coreProps(n, "a Core external plugin");
|
|
1988
|
+
const kind = p.get("kind");
|
|
1989
|
+
const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
|
|
1990
|
+
const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
|
|
1991
|
+
if (i < 0)
|
|
1992
|
+
this.fail(kind ?? n, `a Core external plugin is ${kinds.join(", ")}`);
|
|
1993
|
+
const allowed = {
|
|
1994
|
+
0: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
|
|
1995
|
+
1: ["baseAddress", "checks", "baseAddressConfig", "resultsOffset"],
|
|
1996
|
+
2: ["dataAuthority", "schema"],
|
|
1997
|
+
3: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
|
|
1998
|
+
4: ["dataAuthority", "schema"],
|
|
1999
|
+
};
|
|
2000
|
+
for (const k of p.keys())
|
|
2001
|
+
if (k !== "kind" && k !== "authority" && !allowed[i].includes(k))
|
|
2002
|
+
this.fail(n, `${kinds[i]} takes ${allowed[i].join(", ")} and authority, not ${k}`);
|
|
2003
|
+
const need = (k) => p.get(k) ?? this.fail(n, `${kinds[i]} needs ${k}`);
|
|
2004
|
+
const auth = this.coreOption(p.get("authority"), (x) => this.coreAuthority(x));
|
|
2005
|
+
const schema = this.coreOption(p.get("schema"), (x) => this.coreSchema(x));
|
|
2006
|
+
if (i === 2 || i === 4)
|
|
2007
|
+
return [{ bytes: [i] }, ...this.coreAuthority(need("dataAuthority")), ...auth, ...schema];
|
|
2008
|
+
if (i === 1) {
|
|
2009
|
+
const offset = p.get("resultsOffset");
|
|
2010
|
+
const off = !offset
|
|
2011
|
+
? [{ bytes: [0] }]
|
|
2012
|
+
: ts.isStringLiteral(offset) && (offset.text === "none" || offset.text === "anchor")
|
|
2013
|
+
? [{ bytes: [1, offset.text === "none" ? 0 : 1] }]
|
|
2014
|
+
: [{ bytes: [1, 2] }, { value: offset, type: U64 }];
|
|
2015
|
+
return [
|
|
2016
|
+
{ bytes: [1] },
|
|
2017
|
+
{ value: need("baseAddress"), type: PUBKEY },
|
|
2018
|
+
...auth,
|
|
2019
|
+
...this.coreChecks(need("checks"), n),
|
|
2020
|
+
...this.coreOption(p.get("baseAddressConfig"), (x) => this.coreExtraAccount(x)),
|
|
2021
|
+
...off,
|
|
2022
|
+
];
|
|
2023
|
+
}
|
|
2024
|
+
const extras = p.get("extraAccounts");
|
|
2025
|
+
if (extras && !ts.isArrayLiteralExpression(extras))
|
|
2026
|
+
this.fail(extras, "extraAccounts is a list of extra accounts");
|
|
2027
|
+
return [
|
|
2028
|
+
{ bytes: [i] },
|
|
2029
|
+
{ value: need("hookedProgram"), type: PUBKEY },
|
|
2030
|
+
...auth,
|
|
2031
|
+
...this.coreChecks(need("checks"), n),
|
|
2032
|
+
...(extras ? [{ bytes: [1, extras.elements.length & 0xff, 0, 0, 0] }, ...extras.elements.flatMap((e) => this.coreExtraAccount(e))] : [{ bytes: [0] }]),
|
|
2033
|
+
...this.coreOption(p.get("dataAuthority"), (x) => this.coreAuthority(x)),
|
|
2034
|
+
...schema,
|
|
2035
|
+
];
|
|
2036
|
+
}
|
|
2037
|
+
/** ExternalPluginAdapterKey: `{ kind: "AppData", dataAuthority }`, `{ kind: "Oracle", address }`, `{ kind: "LifecycleHook", address }`, … */
|
|
2038
|
+
coreExternalKey(n) {
|
|
2039
|
+
const p = this.coreProps(n, "a Core external plugin key");
|
|
2040
|
+
const kind = p.get("kind");
|
|
2041
|
+
const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
|
|
2042
|
+
const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
|
|
2043
|
+
if (i < 0)
|
|
2044
|
+
this.fail(kind ?? n, `a Core external plugin key is ${kinds.join(", ")}`);
|
|
2045
|
+
if (i === 2 || i === 4)
|
|
2046
|
+
return [{ bytes: [i] }, ...this.coreAuthority(p.get("dataAuthority") ?? this.fail(n, `${kinds[i]}'s key is its dataAuthority`))];
|
|
2047
|
+
return [
|
|
2048
|
+
{ bytes: [i] },
|
|
2049
|
+
{ value: p.get("address") ?? this.fail(n, `${kinds[i]}'s key is its address (the oracle's base address or the hooked program)`), type: PUBKEY },
|
|
2050
|
+
];
|
|
2051
|
+
}
|
|
2052
|
+
/** The data written: Option<Vec<u8>> from a string, a Vec<u8> argument, or null (clears it). */
|
|
2053
|
+
coreExternalData(n) {
|
|
2054
|
+
if (n.kind === ts.SyntaxKind.NullKeyword)
|
|
2055
|
+
return [{ bytes: [0] }];
|
|
2056
|
+
const l = ts.isIdentifier(n) ? this.lookup(n.text) : undefined;
|
|
2057
|
+
if (l?.type.kind === "vecArg") {
|
|
2058
|
+
if (l.type.elem.kind !== "int" || l.type.elem.name !== "u8")
|
|
2059
|
+
this.fail(n, "data is a string, a Vec<u8> argument or null");
|
|
2060
|
+
const t = this.tmp("dl");
|
|
2061
|
+
this.line(`uint32_t ${t} = (uint32_t)${l.c}.len;`);
|
|
2062
|
+
return [{ bytes: [1] }, { c: `&${t}`, n: "4" }, { c: `${l.c}.ptr`, n: `(uint64_t)${t}` }];
|
|
1832
2063
|
}
|
|
1833
|
-
|
|
2064
|
+
return [{ bytes: [1] }, { value: n, type: STRING }];
|
|
1834
2065
|
}
|
|
1835
2066
|
/** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
|
|
1836
2067
|
corePluginType(n) {
|
|
@@ -2642,8 +2873,46 @@ export class CpiEmitter extends CallEmitter {
|
|
|
2642
2873
|
this.fail(obj, `missing field "${f.name}" of ${s.name}`);
|
|
2643
2874
|
for (const [name, e] of given) {
|
|
2644
2875
|
const f = s.fields.find((x) => x.name === name);
|
|
2645
|
-
if (f.type.kind === "vec")
|
|
2646
|
-
|
|
2876
|
+
if (f.type.kind === "vec" && f.type.cap !== undefined) {
|
|
2877
|
+
// A FixedVec keeps its fixed layout, as in account data: u32 length, then room for N elements.
|
|
2878
|
+
const ft = f.type;
|
|
2879
|
+
const t = this.tmp("evv");
|
|
2880
|
+
this.line(`${fixedVecC(ft)} ${t};`);
|
|
2881
|
+
this.line(`ts_memzero(&${t}, sizeof(${t}));`);
|
|
2882
|
+
this.writeAggregate({ lv: t, type: ft, writable: true }, e);
|
|
2883
|
+
const n = this.fieldBytes(ft);
|
|
2884
|
+
staged.set(name, (put) => put(`&${t}`, String(n), n));
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
if (f.type.kind === "vec") {
|
|
2888
|
+
// A Vec is Borsh: u32 length, then the elements, read from a Vec argument or account data when emitted.
|
|
2889
|
+
const l = ts.isIdentifier(e) ? this.lookup(e.text) : undefined;
|
|
2890
|
+
const r = l?.type.kind === "vecArg" ? undefined : this.ref(e);
|
|
2891
|
+
const elemSize = this.fieldBytes(f.type.elem);
|
|
2892
|
+
if (l?.type.kind === "vecArg") {
|
|
2893
|
+
if (!sameType(l.type.elem, f.type.elem))
|
|
2894
|
+
this.fail(e, `expected a ${typeName(f.type)}`);
|
|
2895
|
+
const c = l.c;
|
|
2896
|
+
staged.set(name, (put) => {
|
|
2897
|
+
const n = this.tmp("vl");
|
|
2898
|
+
this.line(`uint32_t ${n} = (uint32_t)${c}.len;`);
|
|
2899
|
+
put(`&${n}`, "4");
|
|
2900
|
+
put(`(const uint8_t *)${c}.ptr`, `(uint64_t)${n} * ${elemSize}`);
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
else if (r?.vec && r.type.kind === "vec" && sameType(r.type.elem, f.type.elem)) {
|
|
2904
|
+
const info = r.vec;
|
|
2905
|
+
staged.set(name, (put) => {
|
|
2906
|
+
const n = this.tmp("vl");
|
|
2907
|
+
this.line(`uint32_t ${n} = (uint32_t)${vecCount(info)};`);
|
|
2908
|
+
put(`&${n}`, "4");
|
|
2909
|
+
put(`(const uint8_t *)${info.items}`, `(uint64_t)${n} * ${elemSize}`);
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
else
|
|
2913
|
+
this.fail(e, `expected a ${typeName(f.type)}: a Vec argument or a list in account data`);
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2647
2916
|
if (f.type.kind === "option" && f.type.inner.kind === "struct")
|
|
2648
2917
|
this.fail(obj, "Option<struct> fields are not supported in events yet");
|
|
2649
2918
|
const vt = f.type.kind === "string" ? STRING : f.type.kind === "option" || isScalar(f.type) ? f.type : undefined;
|
|
@@ -32,17 +32,25 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
|
|
|
32
32
|
text: string;
|
|
33
33
|
arr?: undefined;
|
|
34
34
|
n?: undefined;
|
|
35
|
+
only?: undefined;
|
|
35
36
|
} | {
|
|
36
37
|
text?: undefined;
|
|
37
38
|
arr: string;
|
|
38
39
|
n: number;
|
|
40
|
+
only?: undefined;
|
|
41
|
+
} | {
|
|
42
|
+
only: string;
|
|
43
|
+
text?: undefined;
|
|
39
44
|
};
|
|
40
45
|
/**
|
|
41
46
|
* The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
|
|
42
47
|
* literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
|
|
43
48
|
*/
|
|
44
49
|
protected constantText(e: ts.Expression): string | undefined;
|
|
45
|
-
|
|
50
|
+
/** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
|
|
51
|
+
protected toText(node: ts.Expression, converted?: {
|
|
52
|
+
yes: boolean;
|
|
53
|
+
}): string;
|
|
46
54
|
/** C statement writing the decimal text of an integer into the TS_Str `out`. */
|
|
47
55
|
protected textOfInt(name: IntName, c: string, out: string): string;
|
|
48
56
|
/** `c ? a : b` on values: only the chosen branch runs (its checks and side effects included). */
|