Skip to content

Commit 5b29a89

Browse files
committed
fix: generate code for cell as ref #59
1 parent f22eaee commit 5b29a89

9 files changed

Lines changed: 1795 additions & 2864 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixed
6+
7+
- Fix generate code for cell as ref [issues #59](https://github.com/ton-community/tlb-codegen/issues/59)
8+
59
### Chore
610

711
- Upgrade develop dependencies

src/astbuilder/handle_type.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,19 @@ export function getType(expr: ParserExpression, constructor: TLBConstructorBuild
260260
} else if (expr instanceof MathExpr) {
261261
if (fieldTypeName == '') {
262262
if (expr.op == '*') {
263+
// Handle (x * ^Cell) case - if right is CellRefExpr, treat it as array of Cell references
264+
if (expr.right instanceof CellRefExpr) {
265+
// For (x * ^Cell), we want TLBMultipleType with TLBCellType
266+
// Check if the inner expression is Cell or Any
267+
let innerExpr = expr.right.expr;
268+
if (innerExpr instanceof NameExpr && (innerExpr.name == 'Cell' || innerExpr.name == 'Any')) {
269+
return {
270+
kind: 'TLBMultipleType',
271+
times: getCalculatedExpression(convertToMathExpr(expr.left), constructor),
272+
value: { kind: 'TLBCellType' },
273+
};
274+
}
275+
}
263276
let subExprInfo = getType(expr.right, constructor, fieldTypeName);
264277
return {
265278
kind: 'TLBMultipleType',

src/generators/CodeBuilder.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,23 @@ export class CodeBuilder {
3737
addMultiline(text: string, inline = false) {
3838
let lines = text.split('\n');
3939
let i = 0;
40+
let endsWithNewline = text.endsWith('\n');
4041
for (let line of lines) {
4142
if (line === '\n' && lines.indexOf(line) === lines.length - 1) {
4243
continue;
4344
}
4445
if (inline && i === 0) {
45-
this.code += line + '\n';
46+
this.code += line;
47+
// Only add newline if this is not the last line or if text doesn't end with newline
48+
if (i < lines.length - 1 || !endsWithNewline) {
49+
this.code += '\n';
50+
}
4651
} else {
52+
// For the last line, don't add newline if text already ends with newline
53+
if (i === lines.length - 1 && endsWithNewline && line === '') {
54+
// Skip empty last line that was created by split('\n')
55+
continue;
56+
}
4757
this.add(line);
4858
}
4959
i++;

src/generators/typescript/generator.ts

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
TypeParametersExpression,
6565
TypedIdentifier,
6666
id,
67+
tArrowFunctionExpression,
6768
tBinaryExpression,
6869
tCodeAsIs,
6970
tComment,
@@ -778,7 +779,28 @@ export function loadBoolTrue(slice: Slice): Bool {
778779
let currentParamOutside = storeParametersOutside[0];
779780
let currentParamInside = storeParametersInside[0];
780781
if (subExprInfo.loadExpr) {
781-
result.loadExpr = loadTupleExpr(arrayLength, subExprInfo.loadExpr);
782+
// Special handling for arrays of Cell references - use loadRef() instead of asCell()
783+
if (fieldType.value.kind == 'TLBCellType') {
784+
result.loadExpr = tFunctionCall(
785+
tMemberExpression(
786+
tFunctionCall(tMemberExpression(id('Array'), id('from')), [
787+
tFunctionCall(
788+
tMemberExpression(tFunctionCall(id('Array'), [arrayLength]), id('keys')),
789+
[],
790+
),
791+
]),
792+
id('map'),
793+
),
794+
[
795+
tArrowFunctionExpression(
796+
[],
797+
[tReturnStatement(tFunctionCall(tMemberExpression(id(theSlice), id('loadRef')), []))],
798+
),
799+
],
800+
);
801+
} else {
802+
result.loadExpr = loadTupleExpr(arrayLength, subExprInfo.loadExpr);
803+
}
782804
}
783805
if (
784806
currentParamOutside &&
@@ -787,16 +809,44 @@ export function loadBoolTrue(slice: Slice): Bool {
787809
subExprInfo.storeStmtOutside
788810
) {
789811
if (subExprInfo.storeFunctionExpr && subExprInfo.storeStmtInside) {
790-
result.storeStmtOutside = storeTupleStmt(
791-
currentParamOutside,
792-
subExprInfo.storeStmtInside,
793-
subExprInfo.typeParamExpr,
794-
);
795-
result.storeStmtInside = storeTupleStmt(
796-
currentParamInside,
797-
subExprInfo.storeStmtInside,
798-
subExprInfo.typeParamExpr,
799-
);
812+
// Special handling for arrays of Cell references - use storeRef() instead of storeSlice()
813+
if (fieldType.value.kind == 'TLBCellType') {
814+
result.storeStmtOutside = tExpressionStatement(
815+
tFunctionCall(tMemberExpression(currentParamOutside, id('forEach')), [
816+
tArrowFunctionExpression(
817+
[tTypedIdentifier(id('arg'), id('Cell'))],
818+
[
819+
tExpressionStatement(
820+
tFunctionCall(tMemberExpression(id(theCell), id('storeRef')), [id('arg')]),
821+
),
822+
],
823+
),
824+
]),
825+
);
826+
result.storeStmtInside = tExpressionStatement(
827+
tFunctionCall(tMemberExpression(currentParamInside, id('forEach')), [
828+
tArrowFunctionExpression(
829+
[tTypedIdentifier(id('arg'), id('Cell'))],
830+
[
831+
tExpressionStatement(
832+
tFunctionCall(tMemberExpression(id(theCell), id('storeRef')), [id('arg')]),
833+
),
834+
],
835+
),
836+
]),
837+
);
838+
} else {
839+
result.storeStmtOutside = storeTupleStmt(
840+
currentParamOutside,
841+
subExprInfo.storeStmtInside,
842+
subExprInfo.typeParamExpr,
843+
);
844+
result.storeStmtInside = storeTupleStmt(
845+
currentParamInside,
846+
subExprInfo.storeStmtInside,
847+
subExprInfo.typeParamExpr,
848+
);
849+
}
800850
}
801851
}
802852
if (subExprInfo.typeParamExpr) {

src/generators/typescript/tsgen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ export function toCode(node: TheNode, code: CodeBuilder = new CodeBuilder()): Co
487487

488488
if (node.type == 'DeclareVariable') {
489489
code.add(
490-
`let ${toCode(node.name).render()}${node.typeName ? ': ' + toCode(node.typeName).render() : ''}`,
490+
`const ${toCode(node.name).render()}${node.typeName ? ': ' + toCode(node.typeName).render() : ''}`,
491491
false,
492492
);
493493
if (node.init) {

test/__snapshots__/main.spec.ts.snap

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://goo.gl/fbAQLP
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

33
exports[`main generateCode 1`] = `
44
"import { Builder } from '@ton/core'
@@ -84,19 +84,125 @@ export interface Foo {
8484
// _ x:# = Foo;
8585
8686
export function loadFoo(slice: Slice): Foo {
87-
let x: number = slice.loadUint(32);
87+
const x: number = slice.loadUint(32);
8888
return {
8989
kind: 'Foo',
9090
x: x,
9191
}
92-
9392
}
9493
9594
export function storeFoo(foo: Foo): (builder: Builder) => void {
9695
return ((builder: Builder) => {
9796
builder.storeUint(foo.x, 32);
9897
})
98+
}
99+
100+
"
101+
`;
102+
103+
exports[`main generateCode for cell as ref 1`] = `
104+
"import { Builder } from '@ton/core'
105+
import { Slice } from '@ton/core'
106+
import { beginCell } from '@ton/core'
107+
import { BitString } from '@ton/core'
108+
import { Cell } from '@ton/core'
109+
import { Address } from '@ton/core'
110+
import { ExternalAddress } from '@ton/core'
111+
import { Dictionary } from '@ton/core'
112+
import { DictionaryValue } from '@ton/core'
113+
import { TupleItem } from '@ton/core'
114+
import { parseTuple } from '@ton/core'
115+
import { serializeTuple } from '@ton/core'
116+
export function bitLen(n: number) {
117+
return n.toString(2).length;
118+
}
119+
120+
export interface Bool {
121+
readonly kind: 'Bool';
122+
readonly value: boolean;
123+
}
124+
125+
export function loadBool(slice: Slice): Bool {
126+
if (slice.remainingBits >= 1) {
127+
let value = slice.loadUint(1);
128+
return {
129+
kind: 'Bool',
130+
value: value == 1
131+
}
132+
133+
}
134+
throw new Error('Expected one of "BoolFalse" in loading "BoolFalse", but data does not satisfy any constructor');
135+
}
136+
137+
export function storeBool(bool: Bool): (builder: Builder) => void {
138+
return ((builder: Builder) => {
139+
builder.storeUint(bool.value ? 1: 0, 1);
140+
})
141+
142+
}
143+
144+
145+
146+
export function loadBoolFalse(slice: Slice): Bool {
147+
if (((slice.remainingBits >= 1) && (slice.preloadUint(1) == 0b0))) {
148+
slice.loadUint(1);
149+
return {
150+
kind: 'Bool',
151+
value: false
152+
}
153+
154+
}
155+
throw new Error('Expected one of "BoolFalse" in loading "BoolFalse", but data does not satisfy any constructor');
156+
}
157+
158+
export function loadBoolTrue(slice: Slice): Bool {
159+
if (((slice.remainingBits >= 1) && (slice.preloadUint(1) == 0b1))) {
160+
slice.loadUint(1);
161+
return {
162+
kind: 'Bool',
163+
value: true
164+
}
165+
166+
}
167+
throw new Error('Expected one of "BoolTrue" in loading "BoolTrue", but data does not satisfy any constructor');
168+
}
169+
170+
export function copyCellToBuilder(from: Cell, to: Builder): void {
171+
let slice = from.beginParse();
172+
to.storeBits(slice.loadBits(slice.remainingBits));
173+
while (slice.remainingRefs) {
174+
to.storeRef(slice.loadRef());
175+
}
176+
}
177+
// _ n:(## 3) c:(n * ^Cell) = T;
178+
179+
export interface T {
180+
readonly kind: 'T';
181+
readonly n: number;
182+
readonly c: Array<Cell>;
183+
}
184+
185+
// _ n:(## 3) c:(n * ^Cell) = T;
99186
187+
export function loadT(slice: Slice): T {
188+
const n: number = slice.loadUint(3);
189+
const c: Array<Cell> = Array.from(Array(n).keys()).map((() => {
190+
return slice.loadRef()
191+
}));
192+
return {
193+
kind: 'T',
194+
n: n,
195+
c: c,
196+
}
197+
}
198+
199+
export function storeT(t: T): (builder: Builder) => void {
200+
return ((builder: Builder) => {
201+
builder.storeUint(t.n, 3);
202+
t.c.forEach(((arg: Cell) => {
203+
builder.storeRef(arg);
204+
}));
205+
})
100206
}
101207
102208
"

0 commit comments

Comments
 (0)