gen-typescript-from-tolk-dev 0.1.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/README.md +47 -0
- package/bin/generator.js +15 -0
- package/package.json +32 -0
- package/src/abi-types.ts +157 -0
- package/src/abi.ts +132 -0
- package/src/cli-generate-from-abi-json.ts +21 -0
- package/src/codegen-ctx.ts +115 -0
- package/src/dynamic-ctx.ts +55 -0
- package/src/dynamic-debug-print.ts +191 -0
- package/src/dynamic-get-methods.ts +454 -0
- package/src/dynamic-serialization.ts +430 -0
- package/src/dynamic-validation.ts +55 -0
- package/src/emit-field-defs.ts +60 -0
- package/src/emit-pack-unpack.ts +280 -0
- package/src/emit-stack-rw.ts +239 -0
- package/src/emit-ts-types.ts +66 -0
- package/src/formatting.ts +98 -0
- package/src/generate-from-abi-json.ts +22 -0
- package/src/generate-ts-wrappers.ts +477 -0
- package/src/out-template.ts +514 -0
- package/src/tolk-to-abi.ts +5 -0
- package/src/types-kernel.ts +215 -0
- package/src/unsupported-errors.ts +82 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { Ty } from './abi-types';
|
|
2
|
+
import { CodegenCtx, RUNTIME } from './codegen-ctx';
|
|
3
|
+
import { emitTsType, emitDictKVTypeArgs, emitUnionLabelAndValue } from './emit-ts-types';
|
|
4
|
+
import { OutBuf, safeJsIdent } from './formatting';
|
|
5
|
+
import { CantGeneratePackUnpack, NonStandardDictKey } from './unsupported-errors';
|
|
6
|
+
import { createLabelsForUnion, renderTy } from './types-kernel';
|
|
7
|
+
|
|
8
|
+
// Emit a TS "load {ty} from slice `s`" expression.
|
|
9
|
+
// Parameter `fieldPath` is used for error messages only: e.g. when an input opcode is incorrect,
|
|
10
|
+
// the fired Error will contain struct/field name.
|
|
11
|
+
export function emitLoadExpr(ctx: CodegenCtx, fieldPath: string, ty: Ty): string {
|
|
12
|
+
switch (ty.kind) {
|
|
13
|
+
case 'int':
|
|
14
|
+
case 'builder':
|
|
15
|
+
case 'slice':
|
|
16
|
+
case 'unknown':
|
|
17
|
+
case 'callable': {
|
|
18
|
+
let hint = ty.kind === 'int' ? ' (not int32/uint64/etc.)' :
|
|
19
|
+
ty.kind === 'builder' || ty.kind === 'slice' ? ' (it can be used for writing only)' :
|
|
20
|
+
'';
|
|
21
|
+
throw new CantGeneratePackUnpack(`'${fieldPath}' is '${renderTy(ty)}'${hint}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
case 'intN': return `s.loadIntBig(${ty.n})`;
|
|
25
|
+
case 'uintN': return `s.loadUintBig(${ty.n})`;
|
|
26
|
+
case 'varintN': return `s.loadVarIntBig(${Math.log2(ty.n)})`;
|
|
27
|
+
case 'varuintN': return `s.loadVarUintBig(${Math.log2(ty.n)})`;
|
|
28
|
+
case 'coins': return `s.loadCoins()`;
|
|
29
|
+
case 'bool': return `s.loadBoolean()`;
|
|
30
|
+
case 'cell': return `s.loadRef()`;
|
|
31
|
+
case 'string': return `s.loadStringRefTail()`;
|
|
32
|
+
case 'remaining': return `${RUNTIME.loadTolkRemaining}(s)`;
|
|
33
|
+
case 'address': return `s.loadAddress()`;
|
|
34
|
+
case 'addressOpt': return `s.loadMaybeAddress()`;
|
|
35
|
+
case 'addressExt': return `s.loadExternalAddress()`;
|
|
36
|
+
case 'addressAny': return `${RUNTIME.loadTolkAddressAny}(s)`;
|
|
37
|
+
case 'bitsN': return `${RUNTIME.loadTolkBitsN}(s, ${ty.n})`;
|
|
38
|
+
case 'nullLiteral': return `null`;
|
|
39
|
+
case 'void': return `void 0`;
|
|
40
|
+
case 'nullable': return `s.loadBoolean() ? ${emitLoadExpr(ctx, fieldPath, ty.inner)} : null`;
|
|
41
|
+
case 'cellOf': return `${RUNTIME.loadCellRef}<${emitTsType(ctx, ty.inner)}>(s, ${emitLoadCallback(ctx, fieldPath, ty.inner)})`;
|
|
42
|
+
case 'arrayOf': return `${RUNTIME.loadArrayOf}<${emitTsType(ctx, ty.inner)}>(s, ${emitLoadCallback(ctx, fieldPath + '[ith]', ty.inner)})`;
|
|
43
|
+
case 'lispListOf': return `${RUNTIME.loadLispListOf}<${emitTsType(ctx, ty.inner)}>(s, ${emitLoadCallback(ctx, fieldPath + '[ith]', ty.inner)})`;
|
|
44
|
+
case 'tensor': return `[${ty.items.map((item, i) => emitLoadExpr(ctx, `${fieldPath}[${i}]`, item)).join(', ')}]`;
|
|
45
|
+
case 'shapedTuple': return `[${ty.items.map((item, i) => emitLoadExpr(ctx, `${fieldPath}[${i}]`, item)).join(', ')}]`;
|
|
46
|
+
case 'mapKV': return `c.Dictionary.load<${emitDictKVTypeArgs(ctx, ty.k, ty.v)}>(${emitDictKVSerializers(ctx, fieldPath, ty.k, ty.v)}, s)`;
|
|
47
|
+
case 'EnumRef': return `${ty.enumName}.fromSlice(s)`;
|
|
48
|
+
case 'StructRef': {
|
|
49
|
+
if (ty.typeArgs) {
|
|
50
|
+
const cb = ty.typeArgs.map((ta) => emitLoadCallback(ctx, fieldPath, ta)).join(', ');
|
|
51
|
+
return `${safeJsIdent(ty.structName)}.fromSlice<${ty.typeArgs.map((ta) => emitTsType(ctx, ta)).join(', ')}>(s, ${cb})`;
|
|
52
|
+
}
|
|
53
|
+
return `${safeJsIdent(ty.structName)}.fromSlice(s)`;
|
|
54
|
+
}
|
|
55
|
+
case 'AliasRef': {
|
|
56
|
+
if (ty.typeArgs) {
|
|
57
|
+
const cb = ty.typeArgs.map((ta) => emitLoadCallback(ctx, fieldPath, ta)).join(', ');
|
|
58
|
+
return `${safeJsIdent(ty.aliasName)}.fromSlice<${ty.typeArgs.map((ta) => emitTsType(ctx, ta)).join(', ')}>(s, ${cb})`;
|
|
59
|
+
}
|
|
60
|
+
return `${safeJsIdent(ty.aliasName)}.fromSlice(s)`;
|
|
61
|
+
}
|
|
62
|
+
case 'genericT': return `loadFn_${ty.nameT}(s)`;
|
|
63
|
+
case 'union': {
|
|
64
|
+
const variants = createLabelsForUnion(ctx.symbols, ty.variants);
|
|
65
|
+
const isEither01 = variants.length === 2 && variants[0].prefixStr === '0b0' && variants[1].prefixStr === '0b1';
|
|
66
|
+
if (isEither01) {
|
|
67
|
+
const l = emitUnionLabelAndValue(variants[0], emitLoadExpr(ctx, fieldPath, variants[0].variantTy));
|
|
68
|
+
const r = emitUnionLabelAndValue(variants[1], emitLoadExpr(ctx, fieldPath, variants[1].variantTy));
|
|
69
|
+
return `s.loadBoolean() ? ${r} : ${l}`;
|
|
70
|
+
}
|
|
71
|
+
let buf = new OutBuf();
|
|
72
|
+
for (let v of variants) {
|
|
73
|
+
const checkFn = v.isPrefixImplicit ? RUNTIME.lookupPrefixAndEat : RUNTIME.lookupPrefix;
|
|
74
|
+
const body = emitUnionLabelAndValue(v, emitLoadExpr(ctx, fieldPath, v.variantTy));
|
|
75
|
+
buf.push(`${checkFn}(s, ${v.prefixStr}, ${v.prefixLen}) ? ${body} :`);
|
|
76
|
+
if (v.isPrefixImplicit) {
|
|
77
|
+
ctx.has_implicitUnionPrefix = true;
|
|
78
|
+
}
|
|
79
|
+
if (v.prefixStr === variants[0].prefixStr) {
|
|
80
|
+
buf.indent();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
buf.push(`${RUNTIME.throwNonePrefixMatch}('${fieldPath}')`);
|
|
84
|
+
return buf.toString();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Emit a TS "load {ty} from slice `s`" callback (not expression).
|
|
90
|
+
// Callbacks are passed for generics (user-defined generic types and built-ins),
|
|
91
|
+
// e.g., `loadCellRef<T>(s, {callbackForT})`.
|
|
92
|
+
// We could just emit `(s) => loadExprForT(s)`, but to produce less boilerplate, shorten common cases:
|
|
93
|
+
// not `(s) => SomeStruct.fromSlice(s)` but `SomeStruct.fromSlice` as a valid callback.
|
|
94
|
+
export function emitLoadCallback(ctx: CodegenCtx, fieldPath: string, ty: Ty): string {
|
|
95
|
+
if (ty.kind === 'addressAny') return RUNTIME.loadTolkAddressAny;
|
|
96
|
+
if (ty.kind === 'remaining') return RUNTIME.loadTolkRemaining;
|
|
97
|
+
if (ty.kind === 'EnumRef') return `${ty.enumName}.fromSlice`;
|
|
98
|
+
if (ty.kind === 'genericT') return `loadFn_${ty.nameT}`;
|
|
99
|
+
if (ty.kind === 'StructRef' && !ty.typeArgs) return `${safeJsIdent(ty.structName)}.fromSlice`;
|
|
100
|
+
if (ty.kind === 'AliasRef' && !ty.typeArgs) return `${safeJsIdent(ty.aliasName)}.fromSlice`;
|
|
101
|
+
|
|
102
|
+
return OutBuf.onNewLine(`(s) => ${emitLoadExpr(ctx, fieldPath, ty)}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Emit a TS "store {expr of type ty} into builder `b`" statement.
|
|
106
|
+
// Parameter `tsExpr` is a valid expression: "self.field", "v" (inside a callback), and other.
|
|
107
|
+
export function emitStoreStatement(ctx: CodegenCtx, tsExpr: string, ty: Ty): string {
|
|
108
|
+
switch (ty.kind) {
|
|
109
|
+
case 'int':
|
|
110
|
+
case 'unknown':
|
|
111
|
+
case 'nullLiteral':
|
|
112
|
+
case 'callable': {
|
|
113
|
+
let hint = ty.kind === 'int' ? ' (not int32/uint64/etc.)' : '';
|
|
114
|
+
throw new CantGeneratePackUnpack(`'${tsExpr}' is '${renderTy(ty)}'${hint}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case 'intN': return `b.storeInt(${tsExpr}, ${ty.n});`;
|
|
118
|
+
case 'uintN': return `b.storeUint(${tsExpr}, ${ty.n});`;
|
|
119
|
+
case 'varintN': return `b.storeVarInt(${tsExpr}, ${Math.log2(ty.n)});`;
|
|
120
|
+
case 'varuintN': return `b.storeVarUint(${tsExpr}, ${Math.log2(ty.n)});`;
|
|
121
|
+
case 'coins': return `b.storeCoins(${tsExpr});`;
|
|
122
|
+
case 'bool': return `b.storeBit(${tsExpr});`;
|
|
123
|
+
case 'cell': return `b.storeRef(${tsExpr});`;
|
|
124
|
+
case 'builder': return `b.storeBuilder(${tsExpr});`;
|
|
125
|
+
case 'slice': return `b.storeSlice(${tsExpr});`;
|
|
126
|
+
case 'string': return `b.storeStringRefTail(${tsExpr});`;
|
|
127
|
+
case 'remaining': return `${RUNTIME.storeTolkRemaining}(${tsExpr}, b);`;
|
|
128
|
+
case 'address': return `b.storeAddress(${tsExpr});`;
|
|
129
|
+
case 'addressOpt': return `b.storeAddress(${tsExpr});`;
|
|
130
|
+
case 'addressExt': return `b.storeAddress(${tsExpr});`;
|
|
131
|
+
case 'addressAny': return `${RUNTIME.storeTolkAddressAny}(${tsExpr}, b);`;
|
|
132
|
+
case 'bitsN': return `${RUNTIME.storeTolkBitsN}(${tsExpr}, ${ty.n}, b);`;
|
|
133
|
+
case 'void': return ``;
|
|
134
|
+
case 'nullable': return `${RUNTIME.storeTolkNullable}<${emitTsType(ctx, ty.inner)}>(${tsExpr}, b, ${emitStoreCallback(ctx, ty.inner)});`;
|
|
135
|
+
case 'cellOf': return `${RUNTIME.storeCellRef}<${emitTsType(ctx, ty.inner)}>(${tsExpr}, b, ${emitStoreCallback(ctx, ty.inner)});`;
|
|
136
|
+
case 'arrayOf': return `${RUNTIME.storeArrayOf}<${emitTsType(ctx, ty.inner)}>(${tsExpr}, b, ${emitStoreCallback(ctx, ty.inner)});`;
|
|
137
|
+
case 'lispListOf': return `${RUNTIME.storeLispListOf}<${emitTsType(ctx, ty.inner)}>(${tsExpr}, b, ${emitStoreCallback(ctx, ty.inner)});`;
|
|
138
|
+
case 'tensor': return ty.items.length ? ty.items.map((_, i) => emitStoreStatement(ctx, `${tsExpr}[${i}]`, ty.items[i])).join('\n') : `{}`;
|
|
139
|
+
case 'shapedTuple': return ty.items.length ? ty.items.map((_, i) => emitStoreStatement(ctx, `${tsExpr}[${i}]`, ty.items[i])).join('\n') : `{}`;
|
|
140
|
+
case 'mapKV': return `b.storeDict<${emitDictKVTypeArgs(ctx, ty.k, ty.v)}>(${tsExpr}, ${emitDictKVSerializers(ctx, tsExpr, ty.k, ty.v)});`;
|
|
141
|
+
case 'EnumRef': return `${ty.enumName}.store(${tsExpr}, b);`;
|
|
142
|
+
case 'StructRef': {
|
|
143
|
+
if (ty.typeArgs) {
|
|
144
|
+
const cb = ty.typeArgs.map(ta => emitStoreCallback(ctx, ta)).join(', ');
|
|
145
|
+
return `${safeJsIdent(ty.structName)}.store<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>(${tsExpr}, b, ${cb});`;
|
|
146
|
+
}
|
|
147
|
+
return `${safeJsIdent(ty.structName)}.store(${tsExpr}, b);`;
|
|
148
|
+
}
|
|
149
|
+
case 'AliasRef': {
|
|
150
|
+
if (ty.typeArgs) {
|
|
151
|
+
const cb = ty.typeArgs.map(ta => emitStoreCallback(ctx, ta)).join(', ');
|
|
152
|
+
return `${safeJsIdent(ty.aliasName)}.store<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>(${tsExpr}, b, ${cb});`;
|
|
153
|
+
}
|
|
154
|
+
return `${safeJsIdent(ty.aliasName)}.store(${tsExpr}, b);`;
|
|
155
|
+
}
|
|
156
|
+
case 'genericT': return `storeFn_${ty.nameT}(${tsExpr}, b);`;
|
|
157
|
+
case 'union': {
|
|
158
|
+
const buf = new OutBuf();
|
|
159
|
+
const variants = createLabelsForUnion(ctx.symbols, ty.variants);
|
|
160
|
+
const hasNull = variants.find(v => v.variantTy.kind === 'nullLiteral');
|
|
161
|
+
if (hasNull) {
|
|
162
|
+
buf.push(`if (${tsExpr} === null) {`);
|
|
163
|
+
buf.push(`b.storeUint(${hasNull.prefixStr}, ${hasNull.prefixLen});`);
|
|
164
|
+
buf.push(`} else switch (${tsExpr}.$) {`);
|
|
165
|
+
} else {
|
|
166
|
+
buf.push(`switch (${tsExpr}.$) {`);
|
|
167
|
+
}
|
|
168
|
+
for (let v of variants) {
|
|
169
|
+
if (v.variantTy.kind === 'nullLiteral') continue;
|
|
170
|
+
buf.push(`case '${v.labelStr}':`).indent();
|
|
171
|
+
if (v.isPrefixImplicit) {
|
|
172
|
+
buf.push(`b.storeUint(${v.prefixStr}, ${v.prefixLen});`);
|
|
173
|
+
}
|
|
174
|
+
buf.push(emitStoreStatement(ctx, v.hasValueField ? `${tsExpr}.value` : tsExpr, v.variantTy));
|
|
175
|
+
buf.push('break;').outdent();
|
|
176
|
+
}
|
|
177
|
+
buf.push('}');
|
|
178
|
+
return buf.toString();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Emit a TS "store {expr of type ty} into builder `b`" callback (not expression).
|
|
184
|
+
// Callbacks are passed for generics (user-defined generic types and built-ins),
|
|
185
|
+
// e.g., `storeTolkNullable<T>(s, {callbackForT})`.
|
|
186
|
+
export function emitStoreCallback(ctx: CodegenCtx, ty: Ty): string {
|
|
187
|
+
if (ty.kind === 'addressAny') return RUNTIME.storeTolkAddressAny;
|
|
188
|
+
if (ty.kind === 'remaining') return RUNTIME.storeTolkRemaining;
|
|
189
|
+
if (ty.kind === 'EnumRef') return `${ty.enumName}.store`;
|
|
190
|
+
if (ty.kind === 'genericT') return `storeFn_${ty.nameT}`;
|
|
191
|
+
if (ty.kind === 'StructRef' && !ty.typeArgs) return `${safeJsIdent(ty.structName)}.store`;
|
|
192
|
+
if (ty.kind === 'AliasRef' && !ty.typeArgs) return `${safeJsIdent(ty.aliasName)}.store`;
|
|
193
|
+
|
|
194
|
+
// for simple statements, drop trailing `;` and use it as an expression:
|
|
195
|
+
// not `(v,b) => { b.storeInt(v, 8); }` but `(v,b) => b.storeInt(v, 8)`
|
|
196
|
+
let stmt = emitStoreStatement(ctx, 'v', ty);
|
|
197
|
+
if (stmt.indexOf(';') !== stmt.length - 1 || stmt.includes('\n') || stmt.includes('{')) {
|
|
198
|
+
stmt = '{ ' + stmt + ' }'
|
|
199
|
+
} else if (stmt[stmt.length - 1] === ';') {
|
|
200
|
+
stmt = stmt.substring(0, stmt.length - 1)
|
|
201
|
+
}
|
|
202
|
+
return OutBuf.onNewLine(`(v,b) => ${stmt}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Emit a TS "make a cell from {expr}" expression.
|
|
206
|
+
// It's essentially beginCell + call storing callback + endCell.
|
|
207
|
+
// Parameter `tsExpr` is a valid expression: "self.field", "v" (inside a callback), and other.
|
|
208
|
+
export function emitMakeCellFromExpr(ctx: CodegenCtx, tsExpr: string, ty: Ty, disableShortHand = false): string {
|
|
209
|
+
if (disableShortHand) {
|
|
210
|
+
return `${RUNTIME.makeCellFrom}<${emitTsType(ctx, ty)}>(${tsExpr}, ${emitStoreCallback(ctx, ty)})`;
|
|
211
|
+
}
|
|
212
|
+
switch (ty.kind) {
|
|
213
|
+
case 'EnumRef': {
|
|
214
|
+
return `${safeJsIdent(ty.enumName)}.toCell(${tsExpr})`;
|
|
215
|
+
}
|
|
216
|
+
case 'StructRef': {
|
|
217
|
+
if (ty.typeArgs) {
|
|
218
|
+
const cb = ty.typeArgs.map(ta => emitStoreCallback(ctx, ta)).join(', ');
|
|
219
|
+
return `${safeJsIdent(ty.structName)}.toCell<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>(${tsExpr}, ${cb})`;
|
|
220
|
+
}
|
|
221
|
+
return `${safeJsIdent(ty.structName)}.toCell(${tsExpr})`;
|
|
222
|
+
}
|
|
223
|
+
case 'AliasRef': {
|
|
224
|
+
if (ty.typeArgs) {
|
|
225
|
+
const cb = ty.typeArgs.map(ta => emitStoreCallback(ctx, ta)).join(', ');
|
|
226
|
+
return `${safeJsIdent(ty.aliasName)}.toCell<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>(${tsExpr}, ${cb})`;
|
|
227
|
+
}
|
|
228
|
+
return `${safeJsIdent(ty.aliasName)}.toCell(${tsExpr})`;
|
|
229
|
+
}
|
|
230
|
+
default:
|
|
231
|
+
return emitMakeCellFromExpr(ctx, tsExpr, ty, true);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Emit a TS "StructName.create({bodyArg})" expression.
|
|
236
|
+
// For instance, sendXXX and fromStorage methods accept struct fields and call create()
|
|
237
|
+
// in order to enrich passed fields with `$` tag and default values.
|
|
238
|
+
export function emitCallToCreateMethodExpr(ctx: CodegenCtx, bodyArg: string, ty: Ty) {
|
|
239
|
+
if (ty.kind === 'StructRef') {
|
|
240
|
+
let structRef = ctx.symbols.getStruct(ty.structName);
|
|
241
|
+
if (structRef.fields.length === 0) {
|
|
242
|
+
bodyArg = ''; // create() is without arguments, body is an empty object
|
|
243
|
+
}
|
|
244
|
+
if (ty.typeArgs) {
|
|
245
|
+
return `${safeJsIdent(ty.structName)}.create<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>(${bodyArg})`;
|
|
246
|
+
}
|
|
247
|
+
return `${safeJsIdent(ty.structName)}.create(${bodyArg})`;
|
|
248
|
+
}
|
|
249
|
+
throw new Error(`can not emit create() call for ${ty.kind}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Emit a key (de)serializer for c.Dictionary<K, V>.
|
|
253
|
+
// Only a strict subset of KeyT is allowed by the @ton/core library.
|
|
254
|
+
// For others, we can not generate wrappers at all (not only to/from cell, but even declarations).
|
|
255
|
+
function emitDictKeySerializer(ctx: CodegenCtx, fieldPath: string, tyK: Ty): string {
|
|
256
|
+
if (tyK.kind === 'intN') return `c.Dictionary.Keys.BigInt(${tyK.n})`;
|
|
257
|
+
if (tyK.kind === 'uintN') return `c.Dictionary.Keys.BigUint(${tyK.n})`;
|
|
258
|
+
if (tyK.kind === 'address') return `c.Dictionary.Keys.Address()`;
|
|
259
|
+
|
|
260
|
+
throw new NonStandardDictKey(`'${fieldPath}' is 'map<${renderTy(tyK)}, ...>': such a non-standard map key can not be handled by @ton/core library`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Emit a value (de)serializer for c.Dictionary<K, V>.
|
|
264
|
+
// Unlike keys, @ton/core allows arbitrary values (passing custom (de)serializers).
|
|
265
|
+
// For common cases, use a built-in one; for others, create a custom.
|
|
266
|
+
function emitDictValueSerializer(ctx: CodegenCtx, fieldPath: string, tyV: Ty): string {
|
|
267
|
+
if (tyV.kind === 'intN') return `c.Dictionary.Values.BigInt(${tyV.n})`;
|
|
268
|
+
if (tyV.kind === 'uintN') return `c.Dictionary.Values.BigUint(${tyV.n})`;
|
|
269
|
+
if (tyV.kind === 'coins') return `c.Dictionary.Values.BigVarUint(4)`;
|
|
270
|
+
if (tyV.kind === 'bool') return `c.Dictionary.Values.Bool()`;
|
|
271
|
+
if (tyV.kind === 'cell') return `c.Dictionary.Values.Cell()`;
|
|
272
|
+
|
|
273
|
+
ctx.has_customDictV = true;
|
|
274
|
+
return `${RUNTIME.createDictionaryValue}<${emitTsType(ctx, tyV)}>(${emitLoadCallback(ctx, fieldPath, tyV)}, ${emitStoreCallback(ctx, tyV)})`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Emit (de)serializers for c.Dictionary<K, V> (two arguments separated by a comma).
|
|
278
|
+
export function emitDictKVSerializers(ctx: CodegenCtx, fieldPath: string, tyK: Ty, tyV: Ty): string {
|
|
279
|
+
return emitDictKeySerializer(ctx, fieldPath, tyK) + ', ' + emitDictValueSerializer(ctx, fieldPath, tyV);
|
|
280
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { Ty } from './abi-types';
|
|
2
|
+
import { CodegenCtx, RUNTIME, SymTable } from './codegen-ctx';
|
|
3
|
+
import { emitTsType, emitDictKVTypeArgs } from './emit-ts-types';
|
|
4
|
+
import { OutBuf, safeFieldDecl } from './formatting';
|
|
5
|
+
import { emitDictKVSerializers, emitLoadCallback, emitMakeCellFromExpr } from './emit-pack-unpack';
|
|
6
|
+
import { NotSupportedTypeOnStack } from './unsupported-errors';
|
|
7
|
+
import { calcWidthOnStack, createLabelsForUnion, instantiateGenerics } from './types-kernel'
|
|
8
|
+
|
|
9
|
+
// Emit a TS "read {ty} from a stack `r`" expression.
|
|
10
|
+
// Parameter `fieldPath` is used for error messages only.
|
|
11
|
+
// Used to read getters return value, since contract getters work via the stack, not serialization.
|
|
12
|
+
export function emitStackReadExpr(ctx: CodegenCtx, fieldPath: string, ty: Ty, unTupleIfW = false): string {
|
|
13
|
+
ctx.stackReadsUnknown ||= ty.kind === 'unknown';
|
|
14
|
+
ctx.stackReadsArrayOf ||= ty.kind === 'arrayOf';
|
|
15
|
+
ctx.stackReadsLispListOf ||= ty.kind === 'lispListOf';
|
|
16
|
+
ctx.stackReadsSnakeString ||= ty.kind === 'string';
|
|
17
|
+
ctx.stackReadsTuple ||= ty.kind === 'arrayOf' || ty.kind === 'lispListOf' || ty.kind === 'shapedTuple';
|
|
18
|
+
ctx.stackReadsMapKV ||= ty.kind === 'mapKV';
|
|
19
|
+
ctx.stackReadsBuilder ||= ty.kind === 'builder';
|
|
20
|
+
ctx.stackReadsNullable ||= ty.kind === 'nullable' && ty.stackTypeId === undefined;
|
|
21
|
+
ctx.stackReadsWideNullable ||= ty.kind === 'nullable' && ty.stackTypeId !== undefined;
|
|
22
|
+
ctx.stackReadsUnionType ||= ty.kind === 'union';
|
|
23
|
+
ctx.stackReadsCellRef ||= ty.kind === 'cellOf';
|
|
24
|
+
ctx.stackReadsNullLiteral ||= ty.kind === 'nullLiteral';
|
|
25
|
+
|
|
26
|
+
if (unTupleIfW) { // inside `array<T>` or `[T, ...]`, if T is non-primitive, it's a sub-tuple
|
|
27
|
+
let wOnStack = calcWidthOnStack(ctx.symbols, ty);
|
|
28
|
+
if (wOnStack !== 1) {
|
|
29
|
+
return `r.readTuple<${emitTsType(ctx, ty)}>(${wOnStack}, ${emitStackReadCallback(ctx, fieldPath, ty, false)})`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
switch (ty.kind) {
|
|
34
|
+
case 'int':
|
|
35
|
+
case 'intN':
|
|
36
|
+
case 'uintN':
|
|
37
|
+
case 'varintN':
|
|
38
|
+
case 'varuintN':
|
|
39
|
+
case 'coins': return `r.readBigInt()`;
|
|
40
|
+
case 'bool': return `r.readBoolean()`;
|
|
41
|
+
case 'cell': return `r.readCell()`;
|
|
42
|
+
case 'builder': return `r.readBuilder()`;
|
|
43
|
+
case 'slice': return `r.readSlice()`;
|
|
44
|
+
case 'string': return `r.readSnakeString()`;
|
|
45
|
+
case 'remaining': return `r.readSlice()`;
|
|
46
|
+
case 'address': return `r.readSlice().loadAddress()`;
|
|
47
|
+
case 'addressOpt': return emitStackReadExpr(ctx, fieldPath, { kind: 'nullable', inner: { kind: 'address' } });
|
|
48
|
+
case 'addressExt': return `r.readSlice().loadExternalAddress()`;
|
|
49
|
+
case 'addressAny': return `${RUNTIME.loadTolkAddressAny}(r.readSlice())`;
|
|
50
|
+
case 'bitsN': return `r.readSlice()`;
|
|
51
|
+
case 'nullLiteral': return `r.readNullLiteral()`;
|
|
52
|
+
case 'callable': throw new NotSupportedTypeOnStack(ty, fieldPath);
|
|
53
|
+
case 'void': return `void 0`;
|
|
54
|
+
case 'unknown': return `r.readUnknown()`;
|
|
55
|
+
case 'nullable': {
|
|
56
|
+
if (ty.stackTypeId) {
|
|
57
|
+
return `r.readWideNullable<${emitTsType(ctx, ty.inner)}>(${ty.stackWidth}, ${emitStackReadCallback(ctx, fieldPath, ty.inner, false)})`;
|
|
58
|
+
}
|
|
59
|
+
return `r.readNullable<${emitTsType(ctx, ty.inner)}>(${emitStackReadCallback(ctx, fieldPath, ty.inner, false)})`;
|
|
60
|
+
}
|
|
61
|
+
case 'cellOf': return `r.readCellRef<${emitTsType(ctx, ty.inner)}>(${emitLoadCallback(ctx, fieldPath + '.ref', ty.inner)})`;
|
|
62
|
+
case 'arrayOf': return `r.readArrayOf<${emitTsType(ctx, ty.inner)}>(${emitStackReadCallback(ctx, fieldPath, ty.inner, true)})`;
|
|
63
|
+
case 'lispListOf': return `r.readLispListOf<${emitTsType(ctx, ty.inner)}>(${emitStackReadCallback(ctx, fieldPath, ty.inner, true)})`;
|
|
64
|
+
case 'tensor': {
|
|
65
|
+
let buf = new OutBuf();
|
|
66
|
+
buf.push(`[`);
|
|
67
|
+
for (let i = 0; i < ty.items.length; ++i) {
|
|
68
|
+
buf.push(emitStackReadExpr(ctx, `${fieldPath}[${i}]`, ty.items[i]) + ',');
|
|
69
|
+
}
|
|
70
|
+
buf.push(']');
|
|
71
|
+
return buf.toString();
|
|
72
|
+
}
|
|
73
|
+
case 'shapedTuple': {
|
|
74
|
+
let buf = new OutBuf();
|
|
75
|
+
buf.push(`r.readTuple<${emitTsType(ctx, ty)}>(${ty.items.length}, (r) => [`);
|
|
76
|
+
for (let i = 0; i < ty.items.length; ++i) {
|
|
77
|
+
buf.push(emitStackReadExpr(ctx, `${fieldPath}[${i}]`, ty.items[i], true) + ',');
|
|
78
|
+
}
|
|
79
|
+
buf.push('])');
|
|
80
|
+
return buf.toString();
|
|
81
|
+
}
|
|
82
|
+
case 'mapKV': return `r.readDictionary<${emitDictKVTypeArgs(ctx, ty.k, ty.v)}>(${emitDictKVSerializers(ctx, fieldPath, ty.k, ty.v)})`;
|
|
83
|
+
case 'EnumRef': return `r.readBigInt()`;
|
|
84
|
+
case 'StructRef': {
|
|
85
|
+
const structRef = ctx.symbols.getStruct(ty.structName);
|
|
86
|
+
let buf = new OutBuf();
|
|
87
|
+
buf.push(`({`);
|
|
88
|
+
buf.push(`$: '${ty.structName}',`);
|
|
89
|
+
for (let f of structRef.fields) {
|
|
90
|
+
const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
|
|
91
|
+
buf.push(`${safeFieldDecl(f.name)}: ${emitStackReadExpr(ctx, `${fieldPath}.${f.name}`, fTy)},`);
|
|
92
|
+
}
|
|
93
|
+
buf.push(`})`);
|
|
94
|
+
return buf.toString();
|
|
95
|
+
}
|
|
96
|
+
case 'AliasRef': {
|
|
97
|
+
const aliasRef = ctx.symbols.getAlias(ty.aliasName);
|
|
98
|
+
const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
|
|
99
|
+
return emitStackReadExpr(ctx, fieldPath, targetTy);
|
|
100
|
+
}
|
|
101
|
+
case 'genericT': throw new Error(`unexpected genericT=${ty.nameT} in '${fieldPath}'`);
|
|
102
|
+
case 'union': {
|
|
103
|
+
const variants = createLabelsForUnion(ctx.symbols, ty.variants);
|
|
104
|
+
let buf = new OutBuf();
|
|
105
|
+
buf.push(`r.readUnionType<${emitTsType(ctx, ty)}>(${ty.stackWidth}, {`);
|
|
106
|
+
for (let v of variants) {
|
|
107
|
+
buf.push(`${v.stackTypeId}: [${v.stackWidth}, ${v.hasValueField ? `'${v.labelStr}'` : 'null'}, ${emitStackReadCallback(ctx, `${fieldPath}#${v.stackTypeId}`, v.variantTy, false)}],`);
|
|
108
|
+
}
|
|
109
|
+
buf.push(`})`);
|
|
110
|
+
return buf.toString();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Emit a TS "read {ty} from a stack `r`" callback (not expression).
|
|
116
|
+
// Callbacks are passed for generics — for example, to read a nullable (when a stack slot is not null).
|
|
117
|
+
function emitStackReadCallback(ctx: CodegenCtx, fieldPath: string, ty: Ty, unTupleIfW: boolean): string {
|
|
118
|
+
return OutBuf.onNewLine(`(r) => ${emitStackReadExpr(ctx, fieldPath, ty, unTupleIfW)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Emit a TS "write {expr of type ty} to a stack" expression compatible with @ton/core TupleItem.
|
|
122
|
+
// Used to pass getters arguments, since contract getters work via the stack, not serialization.
|
|
123
|
+
// Parameter `tsExpr` is a valid expression: "p1.field", "p2[0]", "ith" (inside a callback), and other.
|
|
124
|
+
export function emitStackWriteItems(ctx: CodegenCtx, tsExpr: string, ty: Ty, tupleIfW: boolean = false): string[] {
|
|
125
|
+
if (tupleIfW) { // inside `array<T>` or `[T, ...]`, if T is non-primitive, it's a sub-tuple
|
|
126
|
+
if (calcWidthOnStack(ctx.symbols, ty) === 1) {
|
|
127
|
+
return emitStackWriteItems(ctx, tsExpr, ty, false);
|
|
128
|
+
}
|
|
129
|
+
let buf = new OutBuf();
|
|
130
|
+
buf.push(`{ type: 'tuple', items: [`);
|
|
131
|
+
for (let line of emitStackWriteItems(ctx, tsExpr, ty, false)) {
|
|
132
|
+
buf.push(line + ',');
|
|
133
|
+
}
|
|
134
|
+
buf.push(`]}`);
|
|
135
|
+
return [buf.toString()];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
switch (ty.kind) {
|
|
139
|
+
case 'int':
|
|
140
|
+
case 'intN':
|
|
141
|
+
case 'uintN':
|
|
142
|
+
case 'varintN':
|
|
143
|
+
case 'varuintN':
|
|
144
|
+
case 'coins': return [`{ type: 'int', value: ${tsExpr} }`];
|
|
145
|
+
case 'bool': return [`{ type: 'int', value: (${tsExpr} ? -1n : 0n) }`];
|
|
146
|
+
case 'cell': return [`{ type: 'cell', cell: ${tsExpr} }`];
|
|
147
|
+
case 'builder': return [`{ type: 'builder', cell: ${emitMakeCellFromExpr(ctx, tsExpr, ty)} }`];
|
|
148
|
+
case 'slice': return [`{ type: 'slice', cell: ${RUNTIME.beginCell}().storeSlice(${tsExpr}).endCell() }`];
|
|
149
|
+
case 'string': return [`{ type: 'cell', cell: ${RUNTIME.beginCell}().storeStringTail(${tsExpr}).endCell() }`];
|
|
150
|
+
case 'remaining': return [`{ type: 'slice', cell: ${emitMakeCellFromExpr(ctx, tsExpr, ty)} }`];
|
|
151
|
+
case 'address': return [`{ type: 'slice', cell: ${emitMakeCellFromExpr(ctx, tsExpr, ty)} }`];
|
|
152
|
+
case 'addressOpt': return emitStackWriteItems(ctx, tsExpr, { kind: 'nullable', inner: { kind: 'address' } });
|
|
153
|
+
case 'addressExt': return [`{ type: 'slice', cell: ${emitMakeCellFromExpr(ctx, tsExpr, ty)} }`];
|
|
154
|
+
case 'addressAny': return [`{ type: 'slice', cell: ${emitMakeCellFromExpr(ctx, tsExpr, ty)} }`];
|
|
155
|
+
case 'bitsN': return [`{ type: 'slice', cell: ${emitMakeCellFromExpr(ctx, tsExpr, ty)} }`];
|
|
156
|
+
case 'nullLiteral': return [`{ type: 'null' }`];
|
|
157
|
+
case 'callable': throw new NotSupportedTypeOnStack(ty, tsExpr);
|
|
158
|
+
case 'void': return [];
|
|
159
|
+
case 'unknown': return [tsExpr];
|
|
160
|
+
case 'nullable': {
|
|
161
|
+
if (ty.stackTypeId) {
|
|
162
|
+
let buf = new OutBuf();
|
|
163
|
+
buf.push(`...(`);
|
|
164
|
+
buf.push(`${tsExpr} === null ? [`);
|
|
165
|
+
for (let i = 0; i < ty.stackWidth! - 1; ++i) {
|
|
166
|
+
buf.push(`{ type: 'null' },`);
|
|
167
|
+
}
|
|
168
|
+
buf.push(`{ type: 'int', value: 0n },`);
|
|
169
|
+
buf.push(`] : [`);
|
|
170
|
+
emitStackWriteItems(ctx, tsExpr, ty.inner).forEach(w =>
|
|
171
|
+
buf.push(w + ',')
|
|
172
|
+
);
|
|
173
|
+
buf.push(`{ type: 'int', value: ${ty.stackTypeId}n },`);
|
|
174
|
+
buf.push(`]`);
|
|
175
|
+
buf.push(`) as c.TupleItem[]`);
|
|
176
|
+
return [buf.toString()];
|
|
177
|
+
}
|
|
178
|
+
return [`${tsExpr} === null ? { type: 'null' } : ${emitStackWriteItems(ctx, tsExpr, ty.inner)[0]}`];
|
|
179
|
+
}
|
|
180
|
+
case 'cellOf': return [`{ type: 'cell', cell: ${emitMakeCellFromExpr(ctx, `${tsExpr}.ref`, ty.inner)} }`];
|
|
181
|
+
case 'arrayOf': return [`{ type: 'tuple', items: ${tsExpr}.map(${OutBuf.onNewLine(`(ith) => (${emitStackWriteItems(ctx, 'ith', ty.inner, true)})`)})}`];
|
|
182
|
+
case 'tensor': return ty.items.flatMap((item, idx) => emitStackWriteItems(ctx, `${tsExpr}[${idx}]`, item));
|
|
183
|
+
case 'shapedTuple': {
|
|
184
|
+
let buf = new OutBuf();
|
|
185
|
+
buf.push(`{ type: 'tuple', items: [`);
|
|
186
|
+
for (let i = 0; i < ty.items.length; ++i) {
|
|
187
|
+
buf.push(emitStackWriteItems(ctx, `${tsExpr}[${i}]`, ty.items[i], true)[0] + ',');
|
|
188
|
+
}
|
|
189
|
+
buf.push(']}');
|
|
190
|
+
return [buf.toString()];
|
|
191
|
+
}
|
|
192
|
+
case 'lispListOf': {
|
|
193
|
+
let buf = new OutBuf();
|
|
194
|
+
buf.push(`${tsExpr}.reduceRight((tail, head) => ({ type: 'tuple', items: [`);
|
|
195
|
+
buf.push(emitStackWriteItems(ctx, `head`, ty.inner, true)[0] + ',');
|
|
196
|
+
buf.push('tail');
|
|
197
|
+
buf.push(`]}), { type: 'null' } as c.TupleItem)`);
|
|
198
|
+
return [buf.toString()];
|
|
199
|
+
}
|
|
200
|
+
case 'mapKV': return [`${tsExpr}.size === 0 ? { type: 'null' } : { type: 'cell', cell: ${RUNTIME.beginCell}().storeDictDirect<${emitDictKVTypeArgs(ctx, ty.k, ty.v)}>(${tsExpr}, ${emitDictKVSerializers(ctx, tsExpr, ty.k, ty.v)}).endCell() }`];
|
|
201
|
+
case 'EnumRef': return [`{ type: 'int', value: ${tsExpr} }`];
|
|
202
|
+
case 'StructRef': {
|
|
203
|
+
const structRef = ctx.symbols.getStruct(ty.structName);
|
|
204
|
+
return structRef.fields.flatMap(f => {
|
|
205
|
+
const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
|
|
206
|
+
return emitStackWriteItems(ctx, `${tsExpr}.${f.name}`, fTy);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
case 'AliasRef': {
|
|
210
|
+
const aliasRef = ctx.symbols.getAlias(ty.aliasName);
|
|
211
|
+
const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
|
|
212
|
+
return emitStackWriteItems(ctx, tsExpr, targetTy);
|
|
213
|
+
}
|
|
214
|
+
case 'genericT': throw new Error(`unexpected genericT=${ty.nameT} in '${tsExpr}'`);
|
|
215
|
+
case 'union': {
|
|
216
|
+
const variants = createLabelsForUnion(ctx.symbols, ty.variants);
|
|
217
|
+
variants.sort((a, b) => a.stackTypeId - b.stackTypeId); // move `null` variant (if exists) to the top
|
|
218
|
+
let buf = new OutBuf();
|
|
219
|
+
buf.push(`...(`);
|
|
220
|
+
for (let v of variants) {
|
|
221
|
+
if (v.labelStr === '')
|
|
222
|
+
buf.push(`${tsExpr} === null ? [`)
|
|
223
|
+
else
|
|
224
|
+
buf.push(`${tsExpr}.$ === '${v.labelStr}' ? [`);
|
|
225
|
+
for (let i = 0; i < ty.stackWidth - 1 - v.stackWidth; ++i) {
|
|
226
|
+
buf.push(`{ type: 'null' },`);
|
|
227
|
+
}
|
|
228
|
+
emitStackWriteItems(ctx, v.hasValueField ? tsExpr + '.value' : tsExpr, v.variantTy).forEach(w =>
|
|
229
|
+
buf.push(w + ',')
|
|
230
|
+
);
|
|
231
|
+
buf.push(`{ type: 'int', value: ${v.stackTypeId}n },`);
|
|
232
|
+
buf.push(`] :`);
|
|
233
|
+
}
|
|
234
|
+
buf.push(`(() => { throw new Error('invalid $ field in a union') })()`);
|
|
235
|
+
buf.push(`) as c.TupleItem[]`);
|
|
236
|
+
return [buf.toString()];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Ty } from './abi-types';
|
|
2
|
+
import { CodegenCtx } from './codegen-ctx';
|
|
3
|
+
import { safeJsIdent } from './formatting';
|
|
4
|
+
import { createLabelsForUnion, UnionVariantLabeled } from './types-kernel';
|
|
5
|
+
|
|
6
|
+
// Given a union `T1 | T2` after detecting whether each `T_i` should have `$ + value`,
|
|
7
|
+
// stringify it
|
|
8
|
+
export function emitUnionLabelAndValue(label: UnionVariantLabeled, contents: string): string {
|
|
9
|
+
return label.hasValueField ? `{ $: '${label.labelStr}', value: ${contents} }` : contents;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Convert any Tolk type (ABI representation) to a corresponding TypeScript type.
|
|
13
|
+
// Tolk `int32` -> TS `bigint`, but for readability, `type int32 = bigint` is created.
|
|
14
|
+
// `c.` is import @ton/core.
|
|
15
|
+
export function emitTsType(ctx: CodegenCtx, ty: Ty): string {
|
|
16
|
+
if (ty.kind === 'intN') ctx.intNOccurred.add(`int${ty.n}`);
|
|
17
|
+
if (ty.kind === 'uintN') ctx.uintNOccurred.add(`uint${ty.n}`);
|
|
18
|
+
if (ty.kind === 'varintN') ctx.varIntNOccurred.add(`varint${ty.n}`);
|
|
19
|
+
if (ty.kind === 'varuintN') ctx.varIntNOccurred.add(`varuint${ty.n}`);
|
|
20
|
+
if (ty.kind === 'bitsN') ctx.bitsNOccurred.add(`bits${ty.n}`)
|
|
21
|
+
if (ty.kind === 'remaining') ctx.has_RemainingBitsAndRefs = true;
|
|
22
|
+
if (ty.kind === 'addressAny') ctx.has_addressAny = true;
|
|
23
|
+
if (ty.kind === 'arrayOf') ctx.has_arrayOf = true;
|
|
24
|
+
if (ty.kind === 'lispListOf') ctx.has_lispListOf = true;
|
|
25
|
+
|
|
26
|
+
switch (ty.kind) {
|
|
27
|
+
case 'int': return `bigint`;
|
|
28
|
+
case 'intN': return `int${ty.n}`;
|
|
29
|
+
case 'uintN': return `uint${ty.n}`;
|
|
30
|
+
case 'varintN': return `varint${ty.n}`;
|
|
31
|
+
case 'varuintN': return `varuint${ty.n}`;
|
|
32
|
+
case 'coins': return `coins`;
|
|
33
|
+
case 'bool': return `boolean`;
|
|
34
|
+
case 'cell': return `c.Cell`;
|
|
35
|
+
case 'builder': return `c.Builder`;
|
|
36
|
+
case 'slice': return `c.Slice`;
|
|
37
|
+
case 'string': return 'string';
|
|
38
|
+
case 'remaining': return `RemainingBitsAndRefs`;
|
|
39
|
+
case 'address': return `c.Address`;
|
|
40
|
+
case 'addressOpt': return `c.Address | null`;
|
|
41
|
+
case 'addressExt': return `c.ExternalAddress`;
|
|
42
|
+
case 'addressAny': return `any_address`;
|
|
43
|
+
case 'bitsN': return `bits${ty.n}`;
|
|
44
|
+
case 'nullLiteral': return `null`;
|
|
45
|
+
case 'callable': return `any`;
|
|
46
|
+
case 'void': return `void`;
|
|
47
|
+
case 'unknown': return `c.TupleItem`;
|
|
48
|
+
case 'nullable': return `${emitTsType(ctx, ty.inner)} | null`;
|
|
49
|
+
case 'cellOf': return `CellRef<${emitTsType(ctx, ty.inner)}>`;
|
|
50
|
+
case 'arrayOf': return `array<${emitTsType(ctx, ty.inner)}>`;
|
|
51
|
+
case 'lispListOf': return `lisp_list<${emitTsType(ctx, ty.inner)}>`;
|
|
52
|
+
case 'tensor': return `[${ty.items.map(item => emitTsType(ctx, item)).join(', ')}]`;
|
|
53
|
+
case 'shapedTuple': return `[${ty.items.map(item => emitTsType(ctx, item)).join(', ')}]`;
|
|
54
|
+
case 'mapKV': return `c.Dictionary<${emitDictKVTypeArgs(ctx, ty.k, ty.v)}>`;
|
|
55
|
+
case 'EnumRef': return ty.enumName;
|
|
56
|
+
case 'StructRef': return safeJsIdent(ty.structName) + (ty.typeArgs ? `<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>` : '');
|
|
57
|
+
case 'AliasRef': return safeJsIdent(ty.aliasName) + (ty.typeArgs ? `<${ty.typeArgs.map(ta => emitTsType(ctx, ta)).join(', ')}>` : '');
|
|
58
|
+
case 'genericT': return ty.nameT;
|
|
59
|
+
case 'union': return createLabelsForUnion(ctx.symbols, ty.variants).map(v => emitUnionLabelAndValue(v, emitTsType(ctx, v.variantTy))).join(' | ');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Emit c.Dictionary<K, V> (K and V — inside brackets, two types separated by comma).
|
|
64
|
+
export function emitDictKVTypeArgs(ctx: CodegenCtx, tyK: Ty, tyV: Ty): string {
|
|
65
|
+
return emitTsType(ctx, tyK) + ', ' + emitTsType(ctx, tyV);
|
|
66
|
+
}
|