@typesol/cli 0.22.0 → 0.23.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 +21 -0
- package/README.md +1 -1
- package/dist/compiler/analyze.js +93 -15
- package/dist/compiler/emit/base.d.ts +11 -1
- package/dist/compiler/emit/base.js +21 -0
- package/dist/compiler/emit/calls.d.ts +11 -1
- package/dist/compiler/emit/calls.js +68 -6
- package/dist/compiler/emit/cpi.js +42 -4
- package/dist/compiler/emit/expressions.js +9 -0
- package/dist/compiler/emit/function.js +5 -3
- package/dist/compiler/emit/program.js +9 -2
- package/dist/compiler/emit/shared.d.ts +32 -2
- package/dist/compiler/emit/shared.js +43 -0
- package/dist/compiler/emit/statements.d.ts +35 -0
- package/dist/compiler/emit/statements.js +237 -79
- package/dist/compiler/emit/storage.js +39 -13
- package/dist/compiler/model.d.ts +18 -1
- package/docs/language.md +9 -9
- package/lib/typesol.d.ts +8 -0
- package/package.json +1 -1
- package/runtime/typesol_rt.h +19 -0
package/dist/compiler/model.d.ts
CHANGED
|
@@ -38,9 +38,11 @@ export type ValueType = ScalarType | {
|
|
|
38
38
|
inner: ScalarType;
|
|
39
39
|
}
|
|
40
40
|
/** `Vec<T>` instruction argument: a read-only view into instruction data. T is a scalar or a fixed-size struct. */
|
|
41
|
+
/** A Vec argument (Borsh), read in place; `FixedVec<T, N>` arguments are the same with at most `cap` elements. */
|
|
41
42
|
| {
|
|
42
43
|
kind: "vecArg";
|
|
43
44
|
elem: FieldType;
|
|
45
|
+
cap?: number;
|
|
44
46
|
}
|
|
45
47
|
/** `FixedArray<T, N>` instruction argument (Borsh [T; N]), parsed into a read-only copy. */
|
|
46
48
|
| {
|
|
@@ -119,6 +121,8 @@ export type FieldType = ScalarType | {
|
|
|
119
121
|
export interface StructField {
|
|
120
122
|
name: string;
|
|
121
123
|
type: FieldType;
|
|
124
|
+
/** The declaration, while the struct is being analyzed (for messages). */
|
|
125
|
+
node?: ts.Node;
|
|
122
126
|
}
|
|
123
127
|
/** Every exported interface. Used as account data, nested in other structs, or emitted as an event. */
|
|
124
128
|
export interface StructDef {
|
|
@@ -129,12 +133,21 @@ export interface StructDef {
|
|
|
129
133
|
* or undefined when a field is a variable-length string (event-only structs).
|
|
130
134
|
*/
|
|
131
135
|
size: number | undefined;
|
|
132
|
-
/** The
|
|
136
|
+
/** The first growable Vec field, if any. Such structs can only be account data. */
|
|
133
137
|
vec?: {
|
|
134
138
|
name: string;
|
|
135
139
|
elem: FieldType;
|
|
136
140
|
elemSize: number;
|
|
137
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Every growable Vec, in stored order (after the fixed-size fields). The first ends the C struct (its u32 length,
|
|
144
|
+
* then its elements); each further one follows the previous one's elements: a u32 length, then its elements.
|
|
145
|
+
*/
|
|
146
|
+
vecs?: {
|
|
147
|
+
name: string;
|
|
148
|
+
elem: FieldType;
|
|
149
|
+
elemSize: number;
|
|
150
|
+
}[];
|
|
138
151
|
accountDiscriminator: number[];
|
|
139
152
|
eventDiscriminator: number[];
|
|
140
153
|
node: ts.InterfaceDeclaration;
|
|
@@ -436,6 +449,10 @@ export interface ProgramModel {
|
|
|
436
449
|
fixedVecs: Map<string, FieldType & {
|
|
437
450
|
kind: "vec";
|
|
438
451
|
}>;
|
|
452
|
+
/** FixedVec types of local lists (`const ys = xs.map(…)`), filled in while emitting: C typedefs only, not the IDL. */
|
|
453
|
+
localVecs: Map<string, FieldType & {
|
|
454
|
+
kind: "vec";
|
|
455
|
+
}>;
|
|
439
456
|
}
|
|
440
457
|
export declare function isWritable(k: ParamKind): boolean;
|
|
441
458
|
export declare function typeName(t: ValueType | FieldType): string;
|
package/docs/language.md
CHANGED
|
@@ -21,12 +21,12 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
21
21
|
| Destructuring | `const { owner, pos: p } = acct` (or `let` for scalars to change) from a name or a field path — one declaration per name, read like `acct.owner`: scalars are copies, nested structs are references. No rest, defaults or nesting |
|
|
22
22
|
| Struct values | `const p: Point = { x, y }` (or a copy of another Point) — a local struct; fields can be updated, store it with `acct.home = p` |
|
|
23
23
|
| Pubkey constants | `const ADMIN = pubkey("4Nd1…")` (checked at compile time) or inline `pubkey("…")`; usable as seeds |
|
|
24
|
-
| Events | `emit<Deposited>({ owner: owner.key, amount })` — Anchor-format `Program data:` logs, listed under `events` in the IDL; event structs may hold `string` and `
|
|
24
|
+
| Events | `emit<Deposited>({ owner: owner.key, amount })` — Anchor-format `Program data:` logs, listed under `events` in the IDL; event structs may hold `string`, `Option`, `FixedVec<T, N>` (its fixed layout, as in account data) and `Vec<T>` (Borsh: from a Vec argument or a list in account data) |
|
|
25
25
|
| Enums | `export enum Status { Pending, Approved }` — unit variants stored as the Borsh u8 index; `Status.Approved`, `===` |
|
|
26
26
|
| Options | `best: Option<u64>` (or `u64 \| null`) — `null`, `=== null`, TypeScript narrowing; `===` between options or with a value follows JS (null equals null, empty never equals a value); empty use aborts (5108); `acct?.field` on an optional account and `x.home?.field` on an `Option<struct>` give an Option (empty when the account was left out / the option is empty), `a ?? b` takes b only when a is empty (b runs only then; the result is an Option when b can be null), `x!` is the value or aborts with 5108; `${opt}` prints the value or `null`. Borsh `option` in arguments/events, fixed `{ is_some, value }` in account data |
|
|
27
|
-
| Vecs | `entries: Vec<Entry>`
|
|
27
|
+
| Vecs | `entries: Vec<Entry>` in account data, any number of them, declared anywhere: they are stored after the fixed-size fields in the order declared (so every other field keeps its offset; the IDL lists that order), and a push, pop or clear moves the Vecs after it (a reference into one of those cannot be used across it). In a `@layout borsh` struct a Vec is the last field (Anchor's order) — `get`, `set`, `push(v, payer)`, `pop()`, `clear()`, `.length`; the account is resized (realloc) and rent topped up by `payer`; `Vec<u64>` arguments are read-only views; `Vec<string>` arguments (Anchor's `Vec<String>`) are views of the instruction data, each checked as UTF-8 on entry (102), and go back out as Borsh through `invoke`; `pop()` of a Vec of structs works as a statement |
|
|
28
28
|
| Anchor's account bytes | `/** @layout borsh */` on an account struct stores it as Anchor does (Borsh: a `FixedString<N>` is a String, a `FixedVec<T, N>` a Vec, an `Option` one byte when empty, and the fields after them move), so a TypeSOL program can take over accounts an Anchor program wrote and the IDL lists Anchor's own types. The instruction unpacks it into the usual fixed layout when it starts (bad bytes fail with 3003) and packs it back when it returns (3004 when the account is too small for the new value), so everything else works on it unchanged. Programs invoked from the body (CPIs) see the account's bytes as they were when the instruction started. Costs more compute than the fixed layout (the whole account is read and written each time; see the benchmarks); nested structs and list elements must be fixed-size (FixedString elements allowed). The same struct is Borsh as a whole instruction argument (checked like Borsh deserialization, UTF-8 included), as return data and as an event; as those (not as account data) it may also hold `string` fields and data enums carrying strings, Anchor's `String` and enums in argument structs (account data takes data enums without strings); not inside list or enum arguments, `Zeroed<T>`, `loadAccount` or a growable `Vec<T>` |
|
|
29
|
-
| Fixed-capacity lists | `owners: FixedVec<Pubkey, 8>` — any field, any number of them, in nested structs too: a u32 length and room for N elements (always 4 + N × size bytes), the same `get`, `set`, `push(v)`, `pop()`, `clear()`, `.length` and `for … of`; `push` on a full one fails (5104); `acct.owners = fixedVec([a, b])` writes a whole one (unused slots zeroed).
|
|
29
|
+
| Fixed-capacity lists | `owners: FixedVec<Pubkey, 8>` — any field, any number of them, in nested structs too: a u32 length and room for N elements (always 4 + N × size bytes), the same `get`, `set`, `push(v)`, `pop()`, `clear()`, `.length` and `for … of`; `push` on a full one fails (5104); `acct.owners = fixedVec([a, b])` writes a whole one (unused slots zeroed). In account data, returned structs and events; as an argument (`ids: FixedVec<u64, 8>`) it is a Borsh Vec of at most N elements, read like a `Vec` argument (more fail with 102) |
|
|
30
30
|
| Struct arguments | `order: Order`, `ids: FixedArray<u8, 4>`, `orders: Vec<Order>` — fixed layouts, Borsh-identical, validated on entry (bools, enums, option tags, string lengths, enum tags; 102) and read-only |
|
|
31
31
|
| Remaining accounts | `remainingAccounts()` — `.length`, `.get(i)` → `key`, `owner`, `lamports` (writes check writability), `isSigner`, `isWritable`; `loadAccount<T>` / `loadAccountMut<T>` give checked struct views; pass them to `invoke`, one at a time or spread: `...remainingAccounts()` (each with the flags it was passed with), `...readonly(rest)` / `...writable(rest)` (all with that role), `rest.slice(a, b)` for part of them; a CPI lists at most 32 accounts (5113) |
|
|
32
32
|
| Struct literals | `book.entries.push({ who: who.key, amount, status: Status.Pending, note }, who)` — every field required |
|
|
@@ -44,8 +44,8 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
44
44
|
| Loops | `for (let i = 0n; i < n; i++)`, `while`, `do … while`, `for (const x of list)` over a `Vec` or `FixedArray` (each `x` is what `list.get(i)` gives: a value, or a reference into account data for structs; the length is read again every step, as in JS), `break`, `continue` — bounded by the compute budget. Remaining accounts are iterated by index |
|
|
45
45
|
| Helpers | non-exported functions (and every function of an imported file): take values, structs, Vec arguments, `SignerSeeds` and accounts the instruction already checked (`Signer`, `AccountInfo`/`AccountMeta`, `Mut<T>`, token accounts, programs); return `void`, a scalar or a fixed-size struct (`function at(x: u64, y: u64): Point { return { x, y }; }`: the caller gets its own copy, to bind, store with `acct.home = at(…)` / `arr.set(i, at(…))` or read as `at(…).x`); no recursion. A helper that takes a struct account as `Mut<T>` can grow its Vec (`push(value, payer)`: the account's size at instruction entry is passed along) |
|
|
46
46
|
| Arrow functions and closures | `const fee = (x: u64): u64 => x / 100n` (or `const f = function (…) {…}`) at the top level or inside a function. One that reads or writes the enclosing function's variables (a closure: `let total = 0n; const add = (v: u64): void => { total += v; }`) is expanded where it is called: its names mean what they meant where it was declared, struct parameters are references, `return` leaves the closure; it cannot call itself. One that captures nothing becomes a helper. Parameter types may be left out on closures (taken from the arguments) |
|
|
47
|
-
| Functions as arguments | a helper may take a function parameter (`function applyTwice(f: (x: u64) => u64, v: u64): u64`): it is compiled once per named function passed (a helper or a local function that captures nothing).
|
|
48
|
-
| List callbacks | on a `Vec`, `FixedArray`, `FixedVec` or `Vec` argument: `forEach`, `some`, `every`, `find` (an Option of the element; scalar elements), `findIndex` (i64, -1 when absent), `reduce(fn, initial)`, `includes(value)`, `indexOf(value)` (i64)
|
|
47
|
+
| Functions as arguments | a helper may take a function parameter (`function applyTwice(f: (x: u64) => u64, v: u64): u64`): it is compiled once per named function passed (a helper or a local function that captures nothing). An arrow written at the call works too (`applyTwice((x) => x * 3n, v)`: its types come from the parameter) when it reads none of the function's variables. Given a closure (one that reads them), the helper is expanded where it is called instead, like a closure, so it runs where the closure can; such a helper takes values, not accounts, and has no type parameters |
|
|
48
|
+
| List callbacks | on a `Vec`, `FixedArray`, `FixedVec` or `Vec` argument: `forEach`, `some`, `every`, `find` (an Option of the element; scalar elements), `findIndex` (i64, -1 when absent), `reduce(fn, initial)`, `includes(value)`, `indexOf(value)` (i64), `map` and `filter`. The arrow written at the call is expanded into a loop (elements as `list.get(i)` gives them, structs by reference) and may read and write the function's variables. A `map` / `filter` chain used right away (`xs.filter(f).map(g).reduce(h, 0n)`, `.length`, `for … of`) is one loop that stores nothing; callbacks after a filter get the index among the elements it let through. Stored (`const ys = xs.filter(f)`), it is a FixedVec on the stack as big as the list it starts from; from a `Vec`, give the size: `const ys: FixedVec<u64, 16> = …` (at most 2 KiB; one element more aborts with 5104). `map` gives integers, booleans, Pubkeys or enums |
|
|
49
49
|
| Generic helpers | `function max<T extends bigint>(a: T, b: T): T` — compiled once per set of types it is called with (`max(a, b)` with u64 arguments, or `max<u128>(x, 1n)` when only literals tell); a type parameter stands for an integer, boolean, Pubkey, enum or fixed-size struct (`pick<Point>(…)`, or from struct arguments; a struct `T` is copied like any struct value). Each instance is checked like any helper; one that is never called is not compiled (TypeScript still type-checks it). Arithmetic on `T` is a `bigint` to TypeScript: `return (a + b) as T` (`as` names a type the value already has; it never converts) |
|
|
50
50
|
| Evaluation order | operands and arguments are read left to right, as in JS, even when a later one changes account data (`v.length + v.pop()`) |
|
|
51
51
|
| `++` `--` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | all checked |
|
|
@@ -154,12 +154,12 @@ an integer literal, an argument, `acct.key` or a scalar field of an account, the
|
|
|
154
154
|
|
|
155
155
|
## Not supported yet
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
`@layout borsh` structs, growing an account by more than 10 KiB in one instruction (Solana's limit: realloc again in
|
|
157
|
+
A struct argument holding a FixedVec (use `@layout borsh` for the struct, or a FixedVec argument of its own), growing an account by more than 10 KiB in one instruction (Solana's limit: realloc again in
|
|
159
158
|
the next instruction), `?.` on anything but optional accounts and Option<struct> fields, `!` on anything but an Option
|
|
160
159
|
or an optional account, Core's external plugin adapters (oracles, app data, lifecycle hooks: use `invoke`), closures
|
|
161
|
-
stored or returned (they are called
|
|
162
|
-
|
|
160
|
+
stored or returned (they are called, passed to list methods or given to helpers), a helper with type parameters or
|
|
161
|
+
account parameters given a closure, a stored `map` result of structs or
|
|
162
|
+
strings, floating point (by design).
|
|
163
163
|
They are rejected at compile time rather than half-supported.
|
|
164
164
|
|
|
165
165
|
`Init.create` handles a PDA address someone pre-funded: it tops up rent, then allocates and assigns instead of
|
package/lib/typesol.d.ts
CHANGED
|
@@ -50,6 +50,14 @@ export interface ListMethods<T> {
|
|
|
50
50
|
includes(value: T): boolean;
|
|
51
51
|
/** The index of the first element equal to `value`, or -1. */
|
|
52
52
|
indexOf(value: T): i64;
|
|
53
|
+
/**
|
|
54
|
+
* The elements `fn` maps them to (integers, booleans, Pubkeys or enums). Use it right away (another list method,
|
|
55
|
+
* `.length`, `for … of`: one loop, nothing stored) or store it (`const ys = xs.map(…)`: a FixedVec on the stack, as
|
|
56
|
+
* big as `xs`; from a `Vec`, annotate it: `const ys: FixedVec<u64, 16> = …`).
|
|
57
|
+
*/
|
|
58
|
+
map<U>(fn: (x: T, index: u64) => U): FixedVec<U, never>;
|
|
59
|
+
/** The elements `fn` holds for, used or stored like `map`'s; callbacks after it get the index among those. */
|
|
60
|
+
filter(fn: (x: T, index: u64) => boolean): FixedVec<T, never>;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
export interface FixedArray<T, N extends number> extends ListMethods<T> {
|
package/package.json
CHANGED
package/runtime/typesol_rt.h
CHANGED
|
@@ -104,6 +104,25 @@ static inline __attribute__((always_inline)) void ts_memcpy(void *dst, const voi
|
|
|
104
104
|
for (; n; n--) *d++ = *s++;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/* Copy that may overlap (a growable Vec's followers moving when it grows or shrinks): forward or backward as needed. */
|
|
108
|
+
static inline void ts_memmove(void *dst, const void *src, uint64_t n) {
|
|
109
|
+
uint8_t *d = (uint8_t *)dst;
|
|
110
|
+
const uint8_t *s = (const uint8_t *)src;
|
|
111
|
+
if (d == s || n == 0) return;
|
|
112
|
+
if (d < s) {
|
|
113
|
+
ts_memcpy(d, s, n); /* forward: each 8-byte load happens before the store that could overlap it */
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
d += n;
|
|
117
|
+
s += n;
|
|
118
|
+
for (; n >= 8; n -= 8) {
|
|
119
|
+
d -= 8;
|
|
120
|
+
s -= 8;
|
|
121
|
+
ts_st64(d, ts_ld64(s));
|
|
122
|
+
}
|
|
123
|
+
while (n--) *--d = *--s;
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
static inline __attribute__((always_inline)) void ts_memzero(void *dst, uint64_t n) {
|
|
108
127
|
uint8_t *d = (uint8_t *)dst;
|
|
109
128
|
for (; n >= 8; n -= 8, d += 8) ts_st64(d, 0);
|