@typesol/cli 0.17.0 → 0.18.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 +75 -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 +129 -45
- 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 +93 -15
- package/dist/compiler/emit/cpi.d.ts +30 -0
- package/dist/compiler/emit/cpi.js +955 -39
- package/dist/compiler/emit/expressions.d.ts +5 -0
- package/dist/compiler/emit/expressions.js +66 -3
- package/dist/compiler/emit/function.js +51 -6
- 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 +369 -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 +30 -22
- package/docs/migrate-from-anchor.md +10 -2
- package/lib/typesol.d.ts +396 -21
- package/package.json +1 -1
- package/runtime/typesol_rt.h +198 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,81 @@ until 1.0, a minor version may change the language, the IDL layout or error code
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.18.0 — 2026-10-28
|
|
9
|
+
|
|
10
|
+
The remaining Anchor differences, closed: Anchor's argument structs (Strings, Vecs, Options and enums in Borsh),
|
|
11
|
+
keypair `init` with `space`, `init` of mints with Token-2022 extensions and of token accounts, `Vec<String>`
|
|
12
|
+
arguments, `SystemAccount`, string return values, and the Token-2022 and Token Metadata instructions programs call.
|
|
13
|
+
Closures, list callbacks and more everyday TypeScript; the rest of Bubblegum and Core. 26 of the 46 official
|
|
14
|
+
examples are now full ports. Existing programs keep their layouts and error codes (2018 and 3011 are new, raised only
|
|
15
|
+
by the new `Init<Mint>` and `SystemAccount` checks). Not externally audited (see SECURITY.md).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
Anchor accounts and arguments:
|
|
19
|
+
- `@layout borsh` structs as whole instruction arguments, return values and events, in Anchor's Borsh bytes (a
|
|
20
|
+
FixedString is a String, a FixedVec a Vec, an Option one byte when empty). Arguments are checked as Borsh
|
|
21
|
+
deserialization does, UTF-8 included (102 otherwise). `return vault` returns a whole struct account.
|
|
22
|
+
- `@layout borsh` structs used as arguments, return values or events may hold `string` fields and data enums (with or
|
|
23
|
+
without strings), as Anchor argument structs do; Borsh accounts may hold data enums without strings.
|
|
24
|
+
- `acct.create(payer, null)` on `Init<T>`: Anchor's `init` of a keypair account (it must sign; 3010 otherwise), and an
|
|
25
|
+
optional third argument for Anchor's `space = …` on `create` / `createIfNeeded`.
|
|
26
|
+
- Mint extensions on `Init<Mint>`: `mint.create(payer, seeds | null, decimals, authority, freeze, { … })` with
|
|
27
|
+
transferFee, closeAuthority, defaultAccountState, nonTransferable, interestBearing, permanentDelegate, transferHook,
|
|
28
|
+
metadataPointer, groupPointer and groupMemberPointer (Anchor's `extensions::…`).
|
|
29
|
+
- `Init<Mint>` / `InitIfNeeded<Mint>` with `mint.create(payer, seeds | null, decimals, mintAuthority, freezeAuthority)`
|
|
30
|
+
and `Init<TokenAccount>` / `InitIfNeeded<TokenAccount>` (without `Ata<…>`) with `vault.create(payer, seeds | null,
|
|
31
|
+
mint, authority)`: Anchor's `init` with `mint::` / `token::` constraints, at a PDA of this program or (with `null`) a
|
|
32
|
+
new keypair account that signs, under SPL Token or Token-2022 (token accounts sized with GetAccountDataSize). The
|
|
33
|
+
`createIfNeeded` forms check an existing account like Anchor's `init_if_needed` (2014 / 2015 / 2016 / 2017 and the
|
|
34
|
+
new 2018 ConstraintMintDecimals). The IDL marks keypair-created accounts as signers.
|
|
35
|
+
- `SystemAccount` / `Mut<SystemAccount>`: Anchor's `SystemAccount`, refused with 3011 (AccountNotSystemOwned) when
|
|
36
|
+
another program owns it.
|
|
37
|
+
- `Vec<string>` instruction arguments (Anchor's `Vec<String>`): `.get(i)`, `.length`, `for … of`, list callbacks,
|
|
38
|
+
helper parameters, storing into `FixedVec<FixedString<N>, M>`, and re-encoding through `invoke`; each string is
|
|
39
|
+
checked as UTF-8 on entry (102).
|
|
40
|
+
- Helpers and instructions can return `string` (an instruction's is Borsh String return data).
|
|
41
|
+
- `acct.key` of an `Init<…>` account before it is created (the address is known; Anchor seeds use it that way).
|
|
42
|
+
- `mint.create(…, tokenProgram)` / `vault.create(…, tokenProgram)`: an explicit token program, for instructions that
|
|
43
|
+
take more than one.
|
|
44
|
+
|
|
45
|
+
Language:
|
|
46
|
+
- Closures: a local function that reads or writes the enclosing function's variables is expanded where it is called,
|
|
47
|
+
with the names it was declared with (a block at the call that declares the same name does not change it), struct
|
|
48
|
+
parameters by reference and `return` leaving the closure. Recursion and storing / returning closures are refused.
|
|
49
|
+
- List callbacks on Vec, FixedArray, FixedVec and Vec arguments: `forEach`, `some`, `every`, `find`, `findIndex`,
|
|
50
|
+
`reduce`, `includes`, `indexOf`, expanded into loops with the arrow written at the call.
|
|
51
|
+
- Helpers can take function parameters (`f: (x: u64) => u64`): compiled once per named function passed.
|
|
52
|
+
- `do … while` loops (`continue` goes to the test).
|
|
53
|
+
- `x **= n` (the target must not call a function: it is read and written).
|
|
54
|
+
- `switch` on 128-bit integers (the case is picked with comparisons, then switched on as usual, fallthrough included).
|
|
55
|
+
- `?.` on optional accounts (`acct?.field`) and on `Option<struct>` fields (`x.home?.field`): an Option, empty when the
|
|
56
|
+
account was left out or the option is empty; use it with `??`.
|
|
57
|
+
- `acct!` on optional accounts (fails with 5115 when it was left out), Pubkeys / Hashes in template strings (base58),
|
|
58
|
+
and data-enum arguments whose variants carry fixed-size structs or arrays.
|
|
59
|
+
- Type parameters standing for fixed-size structs (`pick<Point>(…)`, or taken from struct arguments), and `cond ? a : b`
|
|
60
|
+
for struct values (places, literals or calls).
|
|
61
|
+
- Growable `Vec<T>` `push` / `pop` / `clear` inside helpers that take the account as `Mut<T>` (with its payer and
|
|
62
|
+
SystemProgram, as in instructions).
|
|
63
|
+
- `mulDiv` / `mulDivCeil` on u128 through a 256-bit product (all three u128; literals take that type).
|
|
64
|
+
|
|
65
|
+
CPI:
|
|
66
|
+
- Token-2022 extension instructions: `updateDefaultAccountState`, `updateTransferHook`, `enableRequiredMemoTransfers` /
|
|
67
|
+
`disableRequiredMemoTransfers`, `updateInterestRate`, `transferCheckedWithFee`, `harvestWithheldTokensToMint`,
|
|
68
|
+
`withdrawWithheldTokensFromMint`, `setTransferFee`; the Token Metadata interface: `updateMetadataField`,
|
|
69
|
+
`removeMetadataKey`, `updateMetadataAuthority`, `emitMetadata`; `closeAccount` of a mint; `mintTo` /
|
|
70
|
+
`initializeMetadata` on a mint created earlier in the instruction; `isTransferring(tokenAccount)` and
|
|
71
|
+
`transferHookAuthority(mint)`.
|
|
72
|
+
- Metaplex Core plugin values: MasterEdition, VerifiedCreators, Autograph, FreezeExecute, PermanentFreezeExecute, and
|
|
73
|
+
royalty rule sets (`ruleSet: { programAllowList | programDenyList }`).
|
|
74
|
+
- Bubblegum: `delegateV2`, `freezeV2`, `thawV2`, `setNonTransferableV2`, `updateMetadataV2`, `verifyCreatorV2`,
|
|
75
|
+
`unverifyCreatorV2`, `setCollectionV2`, and the V1 `transfer`, `burn`, `delegate`. Checked on the real programs with
|
|
76
|
+
every leaf recomputed: verify → delegate → freeze (a frozen leaf is refused) → thaw → rename, unverify, moving a leaf
|
|
77
|
+
into a collection, soulbinding a collection's leaf, and a V1 transfer signed by a PDA owner, delegate and burn.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
- Bubblegum leaf fields that may be null at run time (`assetDataHash: Hash | null`, `flags`) are sent as Borsh Options;
|
|
81
|
+
a null aborted the instruction before.
|
|
82
|
+
|
|
8
83
|
## 0.17.0 — 2026-10-28
|
|
9
84
|
|
|
10
85
|
Anchor's account bytes on request (`@layout borsh`, so a TypeSOL program can take over an Anchor program's accounts
|
package/README.md
CHANGED
|
@@ -189,10 +189,10 @@ Runtime failures point back to your source: `Error 6000 Unauthorized (counter.ts
|
|
|
189
189
|
|
|
190
190
|
| | |
|
|
191
191
|
| --- | --- |
|
|
192
|
-
| **Accounts** | `Signer`, `Account<T>`, `Mut<T>`, `Init<T>`, `InitIfNeeded<T>` (PDAs and associated token accounts), `Zeroed<T>` (up to 10 MiB), optional accounts (`A \| null`), token accounts and mints, programs, sysvars |
|
|
192
|
+
| **Accounts** | `Signer`, `Account<T>`, `Mut<T>`, `Init<T>`, `InitIfNeeded<T>` (PDAs or keypair accounts, with Anchor's `space`; mints, token accounts and associated token accounts), `Zeroed<T>` (up to 10 MiB), optional accounts (`A \| null`), token accounts and mints, `SystemAccount`, programs, sysvars |
|
|
193
193
|
| **Constraints** | `HasOne`, `Seeds` (also from account fields and under another program; `pdaBump` like `ctx.bumps`), `CloseTo`, `Ata`, `Address`, `Owner`, `Executable`. They are checked before the body runs and recorded in the IDL |
|
|
194
|
-
| **Data** | `u8`…`u128`, `i8`…`i128`, `boolean`, `Pubkey`, `Hash`, nested structs, `FixedArray`, `FixedString`, `Option`, unit enums, data enums (discriminated unions), `Vec` that resizes its account, `FixedVec` lists anywhere, and Anchor's own byte layout (`@layout borsh`) to take over existing Anchor accounts |
|
|
195
|
-
| **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too, arrow functions, generics) across multiple files |
|
|
194
|
+
| **Data** | `u8`…`u128`, `i8`…`i128`, `boolean`, `Pubkey`, `Hash`, nested structs, `FixedArray`, `FixedString`, `Option`, unit enums, data enums (discriminated unions), `Vec` that resizes its account, `FixedVec` lists anywhere, and Anchor's own byte layout (`@layout borsh`) to take over existing Anchor accounts and to pass, return and emit structs with String / Vec / Option fields as Anchor does |
|
|
195
|
+
| **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too, arrow functions, closures, function arguments, generics), list callbacks (`some`, `find`, `reduce`, …) across multiple files |
|
|
196
196
|
| **CPIs** | System, SPL Token, Token-2022 (including extension reads and metadata), ATA, Metaplex Token Metadata, Core, Bubblegum. Generic `invoke` with PDA signing, return data, and typed bindings generated from any Anchor IDL (`typesol cpi`) |
|
|
197
197
|
| **Events** | `emit` (log-based) and `emitCpi` (Anchor `emit_cpi!`, through a self-CPI) |
|
|
198
198
|
| **Crypto & oracles** | `sha256`, `keccak256`, Merkle proofs, Ed25519 and secp256k1 signature checks, Pyth pull oracle, Switchboard |
|
|
@@ -306,7 +306,7 @@ Details are in [docs/testing.md](docs/testing.md).
|
|
|
306
306
|
|
|
307
307
|
## Status
|
|
308
308
|
|
|
309
|
-
**TypeSOL is pre-1.0 (0.
|
|
309
|
+
**TypeSOL is pre-1.0 (0.18).** Releases follow the organization's
|
|
310
310
|
[versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
|
|
311
311
|
change.
|
|
312
312
|
- The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
|
package/dist/clientgen.js
CHANGED
|
@@ -39,6 +39,7 @@ export const TYPESOL_ERRORS = {
|
|
|
39
39
|
2015: "ConstraintTokenOwner",
|
|
40
40
|
2016: "ConstraintMintMintAuthority",
|
|
41
41
|
2017: "ConstraintMintFreezeAuthority",
|
|
42
|
+
2018: "ConstraintMintDecimals",
|
|
42
43
|
2040: "ConstraintDuplicateMutableAccount",
|
|
43
44
|
3000: "AccountDiscriminatorAlreadySet",
|
|
44
45
|
3002: "AccountDiscriminatorMismatch",
|
|
@@ -50,6 +51,7 @@ export const TYPESOL_ERRORS = {
|
|
|
50
51
|
3008: "InvalidProgramId",
|
|
51
52
|
3009: "InvalidProgramExecutable",
|
|
52
53
|
3010: "AccountNotSigner",
|
|
54
|
+
3011: "AccountNotSystemOwned",
|
|
53
55
|
3012: "AccountNotInitialized",
|
|
54
56
|
3016: "AccountReallocExceedsLimit",
|
|
55
57
|
4102: "InvalidNumericConversion",
|
|
@@ -12,7 +12,7 @@ export declare const MAX_CPI_ALLOCATION: number;
|
|
|
12
12
|
export interface Analysis {
|
|
13
13
|
model: ProgramModel;
|
|
14
14
|
/** A generic helper's instance for these type arguments (analyzed once, then kept in `model.helpers`). */
|
|
15
|
-
instantiate(g: GenericHelper, typeArgs:
|
|
15
|
+
instantiate(g: GenericHelper, typeArgs: TypeArg[], fnArgs?: string[]): Helper;
|
|
16
16
|
/** TypeScript's checker: used for narrowing (which variant a data-enum value is in a branch). */
|
|
17
17
|
checker: ts.TypeChecker;
|
|
18
18
|
sourceFile: ts.SourceFile;
|
|
@@ -22,11 +22,20 @@ export interface Analysis {
|
|
|
22
22
|
imports: Map<string, string>;
|
|
23
23
|
reporter: Reporter;
|
|
24
24
|
}
|
|
25
|
+
/** What a generic helper's type parameter stands for: a scalar, or a fixed-size struct. */
|
|
26
|
+
export type TypeArg = ScalarType | {
|
|
27
|
+
kind: "struct";
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
/** A struct named by a type reference, through the type parameters of a generic helper's instance. */
|
|
31
|
+
export declare function structNameOf(node: ts.TypeNode, scope: Pick<TypeScope, "structArgs">): string | undefined;
|
|
25
32
|
/** What type resolution needs: imports, unit enums, and where to record Option<T> uses. */
|
|
26
33
|
export interface TypeScope {
|
|
27
34
|
imports: Map<string, string>;
|
|
28
35
|
/** Inside a generic helper's instance: type parameter -> the type it stands for. */
|
|
29
36
|
typeArgs?: Map<string, ScalarType>;
|
|
37
|
+
/** Inside a generic helper's instance: type parameter -> the struct it stands for. */
|
|
38
|
+
structArgs?: Map<string, string>;
|
|
30
39
|
enums: Map<string, EnumDef>;
|
|
31
40
|
dataEnums?: Map<string, DataEnumDef>;
|
|
32
41
|
options: Map<string, ScalarType | {
|
package/dist/compiler/analyze.js
CHANGED
|
@@ -90,6 +90,7 @@ const TYPE_EXPORTS = new Set([
|
|
|
90
90
|
"SelfProgram",
|
|
91
91
|
"ProgramData",
|
|
92
92
|
"SignerSeeds",
|
|
93
|
+
"SystemAccount",
|
|
93
94
|
]);
|
|
94
95
|
/** Largest fixed array a struct may declare, and largest FixedString. */
|
|
95
96
|
const MAX_ARRAY_LEN = 1_000_000;
|
|
@@ -99,6 +100,12 @@ export const MAX_ACCOUNT_SIZE = 10 * 1024 * 1024;
|
|
|
99
100
|
/** sol_set_return_data's limit. */
|
|
100
101
|
export const MAX_RETURN_DATA = 1024;
|
|
101
102
|
export const MAX_CPI_ALLOCATION = 10 * 1024;
|
|
103
|
+
/** A struct named by a type reference, through the type parameters of a generic helper's instance. */
|
|
104
|
+
export function structNameOf(node, scope) {
|
|
105
|
+
if (!ts.isTypeReferenceNode(node) || !ts.isIdentifier(node.typeName) || node.typeArguments?.length)
|
|
106
|
+
return undefined;
|
|
107
|
+
return scope.structArgs?.get(node.typeName.text) ?? node.typeName.text;
|
|
108
|
+
}
|
|
102
109
|
export function snakeCase(name) {
|
|
103
110
|
return name
|
|
104
111
|
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
@@ -424,23 +431,35 @@ export function analyze(entry) {
|
|
|
424
431
|
if (h.node.body)
|
|
425
432
|
collectLocals(h.node.body, h.name, h.imports, h.node);
|
|
426
433
|
const localDecls = new Set(locals.map((l) => l.decl));
|
|
427
|
-
|
|
428
|
-
|
|
434
|
+
// A local function that reads the enclosing function's variables (or calls one that does) is a closure: it is
|
|
435
|
+
// expanded where it is called, in the enclosing function. The others become helpers of their own.
|
|
436
|
+
const inline = new Set(locals.filter((l) => captures(l, localDecls, checker)).map((l) => l.decl));
|
|
437
|
+
for (let changed = true; changed;) {
|
|
438
|
+
changed = false;
|
|
439
|
+
for (const l of locals)
|
|
440
|
+
if (!inline.has(l.decl) && references(l, inline, checker)) {
|
|
441
|
+
inline.add(l.decl);
|
|
442
|
+
changed = true;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const closureDecls = new Map(locals.filter((l) => inline.has(l.decl)).map((l) => [l.decl, l.display]));
|
|
429
446
|
const helpers = new Map();
|
|
430
447
|
const generics = new Map();
|
|
431
448
|
const helperDecls = new Map();
|
|
432
|
-
for (const fn of [...helperNodes, ...locals]) {
|
|
449
|
+
for (const fn of [...helperNodes, ...locals.filter((l) => !inline.has(l.decl))]) {
|
|
433
450
|
helperDecls.set(fn.decl, fn.name);
|
|
434
|
-
|
|
451
|
+
const fnParams = fn.node.parameters.map((p, i) => (p.type && ts.isFunctionTypeNode(p.type) ? i : -1)).filter((i) => i >= 0);
|
|
452
|
+
if (fn.node.typeParameters?.length || fnParams.length) {
|
|
435
453
|
reporter.guard(() => {
|
|
436
|
-
for (const tp of fn.node.typeParameters)
|
|
454
|
+
for (const tp of fn.node.typeParameters ?? [])
|
|
437
455
|
if (tp.default)
|
|
438
456
|
reporter.fail(tp, "type parameters of helpers take no default");
|
|
439
457
|
generics.set(fn.name, {
|
|
440
458
|
name: fn.name,
|
|
441
459
|
display: fn.display,
|
|
442
460
|
node: fn.node,
|
|
443
|
-
typeParams: fn.node.typeParameters.map((t) => t.name.text),
|
|
461
|
+
typeParams: (fn.node.typeParameters ?? []).map((t) => t.name.text),
|
|
462
|
+
fnParams,
|
|
444
463
|
imports: fn.imports,
|
|
445
464
|
});
|
|
446
465
|
});
|
|
@@ -451,20 +470,34 @@ export function analyze(entry) {
|
|
|
451
470
|
helpers.set(h.name, h);
|
|
452
471
|
}
|
|
453
472
|
const instances = new Map();
|
|
454
|
-
const instantiate = (g, typeArgs) => {
|
|
455
|
-
const
|
|
473
|
+
const instantiate = (g, typeArgs, fnArgs = []) => {
|
|
474
|
+
const argKey = (t) => (t.kind === "struct" ? `S${t.name}` : scalarKey(t));
|
|
475
|
+
const key = [g.name, ...typeArgs.map(argKey), ...fnArgs.map((f) => `fn_${f}`)].join("__");
|
|
456
476
|
const done = instances.get(key);
|
|
457
477
|
if (done)
|
|
458
478
|
return done;
|
|
459
|
-
const bound = new Map(
|
|
460
|
-
const
|
|
461
|
-
|
|
479
|
+
const bound = new Map();
|
|
480
|
+
const boundStructs = new Map();
|
|
481
|
+
g.typeParams.forEach((n, i) => {
|
|
482
|
+
const t = typeArgs[i];
|
|
483
|
+
if (t.kind === "struct")
|
|
484
|
+
boundStructs.set(n, t.name);
|
|
485
|
+
else
|
|
486
|
+
bound.set(n, t);
|
|
487
|
+
});
|
|
488
|
+
const display = `${g.display}<${[...typeArgs.map((t) => (t.kind === "struct" ? t.name : typeName(t))), ...fnArgs].join(", ")}>`;
|
|
489
|
+
const h = analyzeHelper(g.node, key, display, reporter, { ...scopeFor(g.imports), typeArgs: bound, structArgs: boundStructs }, structs, new Set(g.fnParams));
|
|
462
490
|
h.typeArgs = bound;
|
|
491
|
+
h.structArgs = boundStructs;
|
|
492
|
+
if (g.fnParams.length) {
|
|
493
|
+
h.fnArgs = new Map(g.fnParams.map((i, k) => [g.node.parameters[i].name.text, fnArgs[k]]));
|
|
494
|
+
h.fnArgIndexes = g.fnParams;
|
|
495
|
+
}
|
|
463
496
|
instances.set(key, h);
|
|
464
497
|
helpers.set(key, h);
|
|
465
498
|
return h;
|
|
466
499
|
};
|
|
467
|
-
reporter.guard(() => checkRecursion([...helperNodes, ...locals], helperDecls, checker, reporter));
|
|
500
|
+
reporter.guard(() => checkRecursion([...helperNodes, ...locals.filter((l) => !inline.has(l.decl))], helperDecls, checker, reporter));
|
|
468
501
|
const accountStructs = new Set();
|
|
469
502
|
const argStructs = new Set();
|
|
470
503
|
const instructions = [];
|
|
@@ -478,26 +511,29 @@ export function analyze(entry) {
|
|
|
478
511
|
for (const a of ix.args) {
|
|
479
512
|
const seen = new Set();
|
|
480
513
|
collectStructs(a.type, structs, dataEnums, seen);
|
|
514
|
+
// A @layout borsh struct is decoded as a whole argument; inside lists or enums it would need a fixed size.
|
|
515
|
+
const whole = a.type.kind === "structRef" ? a.type.struct : undefined;
|
|
481
516
|
for (const s of seen)
|
|
482
|
-
if (structs.get(s).layout === "borsh")
|
|
483
|
-
reporter.error(fn.node.name ?? fn.node, `argument ${a.name}: ${s} is @layout borsh,
|
|
517
|
+
if (structs.get(s).layout === "borsh" && s !== whole)
|
|
518
|
+
reporter.error(fn.node.name ?? fn.node, `argument ${a.name}: ${s} is @layout borsh; pass it as a whole argument, not inside a list or enum`);
|
|
484
519
|
}
|
|
485
|
-
|
|
486
|
-
reporter.error(fn.node.type ?? fn.node, `${ix.returns.name} is @layout borsh, an account layout only; return another struct`);
|
|
487
|
-
// Arguments are Borsh: a FixedVec's padded layout is account data only.
|
|
520
|
+
// Arguments are Borsh: a FixedVec's padded layout is account data only (a @layout borsh struct's FixedVec is a Borsh Vec).
|
|
488
521
|
for (const a of ix.args) {
|
|
489
522
|
const seen = new Set();
|
|
490
523
|
collectStructs(a.type, structs, dataEnums, seen);
|
|
491
524
|
for (const s of seen)
|
|
492
|
-
if (structs.get(s).fields.some((f) => f.type.kind === "vec" && f.type.cap !== undefined))
|
|
525
|
+
if (structs.get(s).layout !== "borsh" && structs.get(s).fields.some((f) => f.type.kind === "vec" && f.type.cap !== undefined))
|
|
493
526
|
reporter.error(fn.node.name ?? fn.node, `argument ${a.name}: ${s} holds a FixedVec, which only account data can; pass a Vec<T> argument instead`);
|
|
494
527
|
}
|
|
495
528
|
// A returned struct goes out in its fixed layout, like an argument comes in: it cannot also be a Borsh event.
|
|
496
529
|
if (ix.returns?.kind === "struct")
|
|
497
530
|
collectStructs(ix.returns, structs, dataEnums, argStructs);
|
|
498
531
|
for (const a of ix.accounts)
|
|
499
|
-
if (a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "zeroed" || a.kind.kind === "initIfNeeded")
|
|
532
|
+
if (a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "zeroed" || a.kind.kind === "initIfNeeded") {
|
|
500
533
|
accountStructs.add(a.kind.struct.name);
|
|
534
|
+
if (a.kind.struct.viewsOnly)
|
|
535
|
+
reporter.error(a.node, `${a.kind.struct.name} holds a string (or an enum carrying one), so it is an argument, return value or event; account data uses FixedString<N>`);
|
|
536
|
+
}
|
|
501
537
|
}
|
|
502
538
|
checkSnakeDuplicates(instructions.map((ix) => ({ name: ix.node.name.text, node: ix.node.name })), "instructions", reporter);
|
|
503
539
|
// emitCpi's event instruction (Anchor's EVENT_IX_TAG) is dispatched before every other instruction.
|
|
@@ -530,6 +566,7 @@ export function analyze(entry) {
|
|
|
530
566
|
helpers,
|
|
531
567
|
generics,
|
|
532
568
|
helperDecls,
|
|
569
|
+
closureDecls,
|
|
533
570
|
errors,
|
|
534
571
|
errorEnums,
|
|
535
572
|
enums,
|
|
@@ -585,7 +622,7 @@ export function resolveArgAggregate(node, scope, structs) {
|
|
|
585
622
|
if (scalar)
|
|
586
623
|
return scalar;
|
|
587
624
|
if (ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName) && !t.typeArguments?.length) {
|
|
588
|
-
const s = structs.get(t
|
|
625
|
+
const s = structs.get(structNameOf(t, scope));
|
|
589
626
|
if (s && s.size !== undefined && !s.vec)
|
|
590
627
|
return { kind: "struct", name: s.name };
|
|
591
628
|
}
|
|
@@ -668,6 +705,9 @@ export function resolveValueType(node, scope, allowVec = false) {
|
|
|
668
705
|
return { kind: "dataEnumArg", name: node.typeName.text };
|
|
669
706
|
if (allowVec && typesolName(node, scope.imports) === "Vec") {
|
|
670
707
|
const elemNode = node.typeArguments?.[0];
|
|
708
|
+
// Vec<string>: Borsh Vec<String>, read as views of the instruction data.
|
|
709
|
+
if (elemNode?.kind === ts.SyntaxKind.StringKeyword)
|
|
710
|
+
return { kind: "vecArg", elem: { kind: "string" } };
|
|
671
711
|
const elem = elemNode && resolveScalarType(elemNode, scope);
|
|
672
712
|
if (elem)
|
|
673
713
|
return { kind: "vecArg", elem };
|
|
@@ -935,7 +975,11 @@ function analyzeStructs(items, aliases, dataEnums, reporter, scopeFor, fstrings,
|
|
|
935
975
|
state.set(s.name, "visiting");
|
|
936
976
|
let total = 0;
|
|
937
977
|
for (const f of s.fields) {
|
|
938
|
-
|
|
978
|
+
// In a @layout borsh struct a string (or a data enum carrying one) is held as a TS_Str view: a C size, no layout.
|
|
979
|
+
const view = s.layout === "borsh" && (f.type.kind === "string" || (f.type.kind === "dataEnum" && sizeOf(f.type, s.node) === undefined));
|
|
980
|
+
if (view)
|
|
981
|
+
s.viewsOnly = true;
|
|
982
|
+
const fsz = view ? (f.type.kind === "string" ? 16 : 1 + dataEnums.get(f.type.name).payload) : sizeOf(f.type, s.node);
|
|
939
983
|
total = total === undefined || fsz === undefined ? undefined : total + fsz;
|
|
940
984
|
}
|
|
941
985
|
s.size = total;
|
|
@@ -954,19 +998,19 @@ function analyzeStructs(items, aliases, dataEnums, reporter, scopeFor, fstrings,
|
|
|
954
998
|
reporter.guard(() => visit(s));
|
|
955
999
|
for (const s of structs.values())
|
|
956
1000
|
if (s.layout === "borsh")
|
|
957
|
-
reporter.guard(() => checkBorshLayout(s, structs, reporter));
|
|
1001
|
+
reporter.guard(() => checkBorshLayout(s, structs, dataEnums, reporter));
|
|
958
1002
|
for (const e of dataEnums.values())
|
|
959
1003
|
reporter.guard(() => sizeOf({ kind: "dataEnum", name: e.name }, e.node));
|
|
960
1004
|
return structs;
|
|
961
1005
|
}
|
|
962
1006
|
const isFunctionNode = (n) => ts.isFunctionDeclaration(n) || ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isMethodDeclaration(n);
|
|
963
|
-
/**
|
|
964
|
-
|
|
965
|
-
* other local functions: reading a variable of the enclosing function (a closure) is refused.
|
|
966
|
-
*/
|
|
967
|
-
function checkCaptures(l, localDecls, checker, reporter) {
|
|
1007
|
+
/** Whether a local function reads a variable or parameter of a function around it (a closure). */
|
|
1008
|
+
function captures(l, localDecls, checker) {
|
|
968
1009
|
const fn = l.node;
|
|
1010
|
+
let found = false;
|
|
969
1011
|
const visit = (n) => {
|
|
1012
|
+
if (found)
|
|
1013
|
+
return;
|
|
970
1014
|
if (ts.isIdentifier(n)) {
|
|
971
1015
|
const sym = ts.isShorthandPropertyAssignment(n.parent) ? checker.getShorthandAssignmentValueSymbol(n.parent) : checker.getSymbolAtLocation(n);
|
|
972
1016
|
for (const d of sym?.declarations ?? []) {
|
|
@@ -974,30 +1018,53 @@ function checkCaptures(l, localDecls, checker, reporter) {
|
|
|
974
1018
|
continue;
|
|
975
1019
|
if (d.getSourceFile() !== fn.getSourceFile() || (d.pos >= fn.pos && d.end <= fn.end))
|
|
976
1020
|
continue;
|
|
977
|
-
// Declared in some function around this one: a captured variable.
|
|
978
1021
|
for (let p = d.parent; p; p = p.parent)
|
|
979
|
-
if (isFunctionNode(p))
|
|
980
|
-
|
|
981
|
-
}
|
|
1022
|
+
if (isFunctionNode(p))
|
|
1023
|
+
found = true;
|
|
982
1024
|
}
|
|
983
1025
|
}
|
|
984
1026
|
ts.forEachChild(n, visit);
|
|
985
1027
|
};
|
|
986
1028
|
if (fn.body)
|
|
987
1029
|
visit(fn.body);
|
|
1030
|
+
return found;
|
|
1031
|
+
}
|
|
1032
|
+
/** Whether a function's body names one of `decls` (a closure it calls). */
|
|
1033
|
+
function references(l, decls, checker) {
|
|
1034
|
+
let found = false;
|
|
1035
|
+
const visit = (n) => {
|
|
1036
|
+
if (found)
|
|
1037
|
+
return;
|
|
1038
|
+
if (ts.isIdentifier(n))
|
|
1039
|
+
for (const d of checker.getSymbolAtLocation(n)?.declarations ?? [])
|
|
1040
|
+
if (decls.has(d))
|
|
1041
|
+
found = true;
|
|
1042
|
+
ts.forEachChild(n, visit);
|
|
1043
|
+
};
|
|
1044
|
+
if (l.node.body)
|
|
1045
|
+
visit(l.node.body);
|
|
1046
|
+
return found;
|
|
988
1047
|
}
|
|
989
1048
|
/**
|
|
990
1049
|
* A `@layout borsh` struct: its own fields may be FixedString (Borsh String), FixedVec (Borsh Vec) and Option; anything
|
|
991
1050
|
* nested must already be Borsh-identical (fixed-size scalars, Pubkeys, enums, arrays and structs of them).
|
|
992
1051
|
*/
|
|
993
|
-
function checkBorshLayout(s, structs, reporter) {
|
|
1052
|
+
function checkBorshLayout(s, structs, dataEnums, reporter) {
|
|
994
1053
|
const model = { structs };
|
|
995
1054
|
const fixed = (t) => borshIdentical(t, model);
|
|
996
1055
|
for (const f of s.fields) {
|
|
997
1056
|
const t = f.type;
|
|
998
1057
|
const at = s.node.members.find((m) => m.name && ts.isIdentifier(m.name) && m.name.text === f.name) ?? s.node;
|
|
999
|
-
if (fixed(t) || t.kind === "fstring")
|
|
1058
|
+
if (fixed(t) || t.kind === "fstring" || t.kind === "string")
|
|
1000
1059
|
continue;
|
|
1060
|
+
if (t.kind === "dataEnum") {
|
|
1061
|
+
// Borsh enum: tag, then the variant's fields; each a fixed-size value or a string.
|
|
1062
|
+
for (const v of dataEnums.get(t.name)?.variants ?? [])
|
|
1063
|
+
for (const vf of v.fields)
|
|
1064
|
+
if (!fixed(vf.type) && vf.type.kind !== "string")
|
|
1065
|
+
reporter.fail(at, `${t.name}.${v.name}.${vf.name} in a @layout borsh struct: enum variants hold fixed-size values and strings`);
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1001
1068
|
if (t.kind === "vec" && t.cap === undefined)
|
|
1002
1069
|
reporter.fail(at, "a @layout borsh struct cannot end in a growable Vec<T>; use FixedVec<T, N> (Anchor's #[max_len(N)] Vec)");
|
|
1003
1070
|
if (t.kind === "vec" && (fixed(t.elem) || t.elem.kind === "fstring"))
|
|
@@ -1015,7 +1082,7 @@ function checkBorshLayout(s, structs, reporter) {
|
|
|
1015
1082
|
reporter.error(s.node.name, `${s.name} is @layout borsh, so it is account data only; ${other.name}.${f.name} cannot hold one`);
|
|
1016
1083
|
}
|
|
1017
1084
|
}
|
|
1018
|
-
function analyzeHelper(fn, name, display, reporter, scope, structs) {
|
|
1085
|
+
function analyzeHelper(fn, name, display, reporter, scope, structs, skip = new Set()) {
|
|
1019
1086
|
if (!fn.body)
|
|
1020
1087
|
reporter.fail(fn, "helper functions need a body");
|
|
1021
1088
|
if (fn.asteriskToken)
|
|
@@ -1024,16 +1091,23 @@ function analyzeHelper(fn, name, display, reporter, scope, structs) {
|
|
|
1024
1091
|
reporter.fail(fn, "helper functions cannot be async");
|
|
1025
1092
|
if (!fn.type)
|
|
1026
1093
|
reporter.fail(fn.name ?? fn, "helper functions need an explicit return type (void, an integer, boolean, Pubkey or a struct)");
|
|
1027
|
-
let returns = fn.type.kind === ts.SyntaxKind.VoidKeyword
|
|
1094
|
+
let returns = fn.type.kind === ts.SyntaxKind.VoidKeyword
|
|
1095
|
+
? undefined
|
|
1096
|
+
: fn.type.kind === ts.SyntaxKind.StringKeyword
|
|
1097
|
+
? { kind: "string" }
|
|
1098
|
+
: resolveScalarType(fn.type, scope);
|
|
1028
1099
|
if (fn.type.kind !== ts.SyntaxKind.VoidKeyword && !returns) {
|
|
1029
|
-
const
|
|
1100
|
+
const sn = structNameOf(fn.type, scope);
|
|
1101
|
+
const s = sn ? structs.get(sn) : undefined;
|
|
1030
1102
|
if (s && (s.size === undefined || s.vec))
|
|
1031
1103
|
reporter.fail(fn.type, `${s.name} has variable-size fields and cannot be returned; return a struct of fixed size`);
|
|
1032
1104
|
if (!s)
|
|
1033
|
-
reporter.fail(fn.type, "helpers return void, an integer, boolean, Pubkey, a struct or a type parameter");
|
|
1105
|
+
reporter.fail(fn.type, "helpers return void, an integer, boolean, Pubkey, string, a struct or a type parameter");
|
|
1034
1106
|
returns = { kind: "struct", name: s.name };
|
|
1035
1107
|
}
|
|
1036
|
-
const params = fn.parameters
|
|
1108
|
+
const params = fn.parameters
|
|
1109
|
+
.filter((_, i) => !skip.has(i))
|
|
1110
|
+
.map((p) => {
|
|
1037
1111
|
if (!ts.isIdentifier(p.name) || !p.type || p.initializer || p.questionToken || p.dotDotDotToken)
|
|
1038
1112
|
reporter.fail(p, "parameters need a plain name and an explicit type");
|
|
1039
1113
|
// `p_<name>__n` carries a SignerSeeds count: "__" in a name could collide with it.
|
|
@@ -1120,7 +1194,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1120
1194
|
reporter.fail(fn, "instructions cannot be async or generators");
|
|
1121
1195
|
let returns;
|
|
1122
1196
|
if (fn.type && fn.type.kind !== ts.SyntaxKind.VoidKeyword) {
|
|
1123
|
-
returns = resolveScalarType(fn.type, scope);
|
|
1197
|
+
returns = fn.type.kind === ts.SyntaxKind.StringKeyword ? { kind: "string" } : resolveScalarType(fn.type, scope);
|
|
1124
1198
|
const s = !returns && ts.isTypeReferenceNode(fn.type) && ts.isIdentifier(fn.type.typeName) && !fn.type.typeArguments
|
|
1125
1199
|
? structs.get(fn.type.typeName.text)
|
|
1126
1200
|
: undefined;
|
|
@@ -1133,7 +1207,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1133
1207
|
returns = { kind: "struct", name: s.name };
|
|
1134
1208
|
}
|
|
1135
1209
|
if (!returns)
|
|
1136
|
-
reporter.fail(fn.type, "instructions return void, one integer, boolean or
|
|
1210
|
+
reporter.fail(fn.type, "instructions return void, one integer, boolean, Pubkey or string, or a fixed-size struct (sent as return data)");
|
|
1137
1211
|
}
|
|
1138
1212
|
const accounts = [];
|
|
1139
1213
|
const args = [];
|
|
@@ -1173,7 +1247,8 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1173
1247
|
const inner = unwrapConstraints(typeNode, reporter, imports, constraints);
|
|
1174
1248
|
const kind = resolveAccountKind(inner, reporter, imports, structs);
|
|
1175
1249
|
if (optional &&
|
|
1176
|
-
(["init", "initIfNeeded", "zeroed", "pyth", "eventAuthority", "selfProgram", "programData"].includes(kind.kind) ||
|
|
1250
|
+
(["init", "initIfNeeded", "zeroed", "pyth", "eventAuthority", "selfProgram", "programData"].includes(kind.kind) ||
|
|
1251
|
+
((kind.kind === "token" || kind.kind === "mint") && kind.init)))
|
|
1177
1252
|
reporter.fail(p, "Init, InitIfNeeded, Zeroed, PythPriceUpdate, EventAuthority, SelfProgram and ProgramData accounts cannot be optional");
|
|
1178
1253
|
accounts.push({ name: p.name.text, index: accounts.length, kind, node: p, constraints, ...(optional ? { optional } : {}) });
|
|
1179
1254
|
});
|
|
@@ -1186,10 +1261,11 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1186
1261
|
// Constraints refer to other parameters by name: check them now that all are known.
|
|
1187
1262
|
for (const a of accounts) {
|
|
1188
1263
|
const c = a.constraints;
|
|
1189
|
-
if (a.kind.kind === "token" && a.kind.init &&
|
|
1190
|
-
reporter.fail(a.node, 'Init<TokenAccount> / InitIfNeeded<TokenAccount> create associated token accounts: wrap them in Ata<…, "owner", "mint">');
|
|
1191
|
-
if (a.kind.kind === "token" && a.kind.init && (c.hasOne.length || c.seeds || c.address || c.owner || c.executable))
|
|
1264
|
+
if (a.kind.kind === "token" && a.kind.init && c.ata && (c.hasOne.length || c.seeds || c.address || c.owner || c.executable))
|
|
1192
1265
|
reporter.fail(a.node, "an associated token account created here is checked against Ata<…>'s owner and mint; drop HasOne/Seeds/Address/Owner/Executable");
|
|
1266
|
+
// Token accounts and mints created in the body are checked there (seeds, mint, authorities).
|
|
1267
|
+
if ((a.kind.kind === "token" || a.kind.kind === "mint") && a.kind.init && !c.ata && (c.hasOne.length || c.seeds || c.address || c.owner || c.executable))
|
|
1268
|
+
reporter.fail(a.node, `${a.name}.create(...) takes the seeds, mint and authorities; drop HasOne/Seeds/Address/Owner/Executable`);
|
|
1193
1269
|
if (!c.hasOne.length && !c.seeds && !c.closeTo && !c.ata)
|
|
1194
1270
|
continue;
|
|
1195
1271
|
const struct = a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "initIfNeeded" || a.kind.kind === "zeroed" ? a.kind.struct : undefined;
|
|
@@ -1381,6 +1457,8 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1381
1457
|
// Any account (helpers that accept every kind, e.g. generated CPI helpers).
|
|
1382
1458
|
case "AccountMeta":
|
|
1383
1459
|
return { kind: "info", mut: false };
|
|
1460
|
+
case "SystemAccount":
|
|
1461
|
+
return { kind: "info", mut: false, systemOwned: true };
|
|
1384
1462
|
case "TokenAccount":
|
|
1385
1463
|
return { kind: "token", mut: false };
|
|
1386
1464
|
case "Mint":
|
|
@@ -1438,9 +1516,11 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1438
1516
|
case "Init": {
|
|
1439
1517
|
if (typesolName(inner(), imports) === "TokenAccount")
|
|
1440
1518
|
return { kind: "token", mut: true, init: "create" };
|
|
1519
|
+
if (typesolName(inner(), imports) === "Mint")
|
|
1520
|
+
return { kind: "mint", mut: true, init: "create" };
|
|
1441
1521
|
const s = structOf(inner());
|
|
1442
1522
|
if (!s)
|
|
1443
|
-
reporter.fail(node, "Init<T> needs a struct declared in this program");
|
|
1523
|
+
reporter.fail(node, "Init<T> needs a struct declared in this program, TokenAccount or Mint");
|
|
1444
1524
|
if (s.size + 8 > MAX_CPI_ALLOCATION)
|
|
1445
1525
|
reporter.fail(node, `${s.name} needs ${s.size + 8} bytes but a program can create at most ${MAX_CPI_ALLOCATION} through Init<T>; have the client allocate it and take it as Zeroed<${s.name}>`);
|
|
1446
1526
|
return { kind: "init", struct: s };
|
|
@@ -1448,6 +1528,8 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1448
1528
|
case "InitIfNeeded": {
|
|
1449
1529
|
if (typesolName(inner(), imports) === "TokenAccount")
|
|
1450
1530
|
return { kind: "token", mut: true, init: "ifNeeded" };
|
|
1531
|
+
if (typesolName(inner(), imports) === "Mint")
|
|
1532
|
+
return { kind: "mint", mut: true, init: "ifNeeded" };
|
|
1451
1533
|
const s = structOf(inner());
|
|
1452
1534
|
if (!s)
|
|
1453
1535
|
reporter.fail(node, "InitIfNeeded<T> needs a struct declared in this program");
|
|
@@ -1475,6 +1557,8 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1475
1557
|
return { kind: "signer", mut: true };
|
|
1476
1558
|
if (innerName === "AccountInfo" || innerName === "AccountMeta")
|
|
1477
1559
|
return { kind: "info", mut: true };
|
|
1560
|
+
if (innerName === "SystemAccount")
|
|
1561
|
+
return { kind: "info", mut: true, systemOwned: true };
|
|
1478
1562
|
if (innerName === "TokenAccount")
|
|
1479
1563
|
return { kind: "token", mut: true };
|
|
1480
1564
|
if (innerName === "Mint")
|
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
import { type Analysis, type TypeScope } from "../analyze.js";
|
|
3
3
|
import type { Reporter } from "../diagnostics.js";
|
|
4
4
|
import { type AccountParam, type Helper, type IdlSeed, type Instruction, type ProgramModel, type ValueType } from "../model.js";
|
|
5
|
-
import { type Value, type Local, type EmitOptions } from "./shared.js";
|
|
5
|
+
import { type Value, type Ref, type Local, type Closure, type EmitOptions } from "./shared.js";
|
|
6
6
|
/**
|
|
7
7
|
* State of one function being lowered, and the primitives every layer uses: emitting lines, temporaries,
|
|
8
8
|
* scopes, evaluation-order snapshots and account lookups.
|
|
@@ -24,6 +24,17 @@ export declare abstract class EmitterBase {
|
|
|
24
24
|
protected readonly pendingInit: Set<string>;
|
|
25
25
|
/** Bump temporaries from create()/createIfNeeded(), for `acct.pdaBump`. */
|
|
26
26
|
protected readonly bumps: Map<string, string>;
|
|
27
|
+
/** Closures being expanded, innermost last: a `return` inside one assigns `result` and jumps to `label`. */
|
|
28
|
+
protected inlineFrames: {
|
|
29
|
+
label: string;
|
|
30
|
+
result: string;
|
|
31
|
+
type?: ValueType;
|
|
32
|
+
declAt: number;
|
|
33
|
+
pad: string;
|
|
34
|
+
node: ts.Node;
|
|
35
|
+
}[];
|
|
36
|
+
/** Closures being expanded (a closure calling itself cannot be expanded). */
|
|
37
|
+
protected readonly inlining: Set<ts.Node>;
|
|
27
38
|
/** Enclosing loops, innermost last; each has a `continue` label. */
|
|
28
39
|
protected readonly loops: string[];
|
|
29
40
|
protected readonly ix: Instruction | undefined;
|
|
@@ -39,6 +50,14 @@ export declare abstract class EmitterBase {
|
|
|
39
50
|
constructor(analysis: Analysis, target: Instruction | Helper, opts?: EmitOptions);
|
|
40
51
|
/** The helper a call runs: a function, a const arrow function (top-level or local), or a generic one's instance. */
|
|
41
52
|
protected helperOf(node: ts.CallExpression): Helper | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The functions passed for a helper's function parameters: named functions only (a helper, or a local function that
|
|
55
|
+
* captures nothing). A closure can only run where it is declared, so it cannot be passed.
|
|
56
|
+
*/
|
|
57
|
+
private fnArgsOf;
|
|
58
|
+
/** A struct written as a type argument (`pick<Point>(…)`). */
|
|
59
|
+
private structArg;
|
|
60
|
+
private fixedStruct;
|
|
42
61
|
/** A generic call's type arguments: written out (`max<u64>(a, b)`), or the types of the arguments in `T` positions. */
|
|
43
62
|
private typeArgsOf;
|
|
44
63
|
protected line(s: string): void;
|
|
@@ -59,11 +78,17 @@ export declare abstract class EmitterBase {
|
|
|
59
78
|
protected block(fn: () => void): void;
|
|
60
79
|
protected fail(node: ts.Node, message: string): never;
|
|
61
80
|
protected exportName(node: ts.Expression): string | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* `acct!.field` on an optional account: the same access without `!` (using the account checks that it was passed,
|
|
83
|
+
* failing with 3020 otherwise). Returns undefined for anything else.
|
|
84
|
+
*/
|
|
85
|
+
protected withoutBang(node: ts.Expression): ts.PropertyAccessExpression | undefined;
|
|
62
86
|
protected accountParam(node: ts.Expression, what: string): AccountParam;
|
|
63
87
|
protected assertUsable(a: AccountParam, node: ts.Node): void;
|
|
64
88
|
protected abstract expect(v: Value, want: ValueType | undefined, node: ts.Node): Value;
|
|
65
89
|
protected abstract literal(value: bigint, type: ValueType, node: ts.Node): Value;
|
|
66
90
|
protected abstract expr(node: ts.Expression, hint?: ValueType): Value;
|
|
91
|
+
protected abstract ref(node: ts.Expression): Ref | undefined;
|
|
67
92
|
protected abstract call(node: ts.CallExpression, topLevel: boolean): Value | undefined;
|
|
68
93
|
protected abstract isRemaining(node: ts.Expression): boolean;
|
|
69
94
|
protected abstract handle(node: ts.Expression): string | undefined;
|
|
@@ -75,6 +100,7 @@ export declare abstract class EmitterBase {
|
|
|
75
100
|
protected abstract recordPda(account: AccountParam, idl: IdlSeed[] | undefined): void;
|
|
76
101
|
protected abstract create(node: ts.CallExpression, target: AccountParam, topLevel: boolean, ifNeeded?: boolean): Value;
|
|
77
102
|
protected abstract createAta(node: ts.CallExpression, target: AccountParam, topLevel: boolean): Value | undefined;
|
|
103
|
+
protected abstract createToken(node: ts.CallExpression, target: AccountParam, topLevel: boolean): Value;
|
|
78
104
|
protected abstract systemTransfer(node: ts.CallExpression): undefined;
|
|
79
105
|
protected abstract createAccount(node: ts.CallExpression, system: AccountParam): undefined;
|
|
80
106
|
protected abstract tokenOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
@@ -88,4 +114,6 @@ export declare abstract class EmitterBase {
|
|
|
88
114
|
program: string;
|
|
89
115
|
}): undefined;
|
|
90
116
|
protected abstract epilogue(): string[];
|
|
117
|
+
protected abstract expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node): Value | undefined;
|
|
118
|
+
protected abstract listMethod(node: ts.CallExpression, method: string): Value | undefined;
|
|
91
119
|
}
|