Skip to content

Commit 8d650aa

Browse files
committed
[Tolk] Support custom pack/unpack serializers for any T
For any type alias, one can now specify > fun T.packToBuilder > fun T.unpackFromSlice That will be called whenever that type is serialized. Perfect to express custom TL/B behavior, like > len: (## 8) > data: (bits (len*8))
1 parent 2bb89b4 commit 8d650aa

16 files changed

+433
-9
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
type CustomInt = int;
2+
3+
fun CustomInt.packToBuilder(self, mutate b: builder) {
4+
}
5+
6+
struct WithC {
7+
a: int32;
8+
b: CustomInt;
9+
}
10+
11+
fun main() {
12+
WithC.fromSlice("");
13+
}
14+
15+
/**
16+
@compilation_should_fail
17+
@stderr auto-serialization via fromSlice() is not available for type `WithC`
18+
@stderr because field `WithC.b` of type `CustomInt` can't be serialized
19+
@stderr because type `CustomInt` defines a custom pack function, but does not define unpack
20+
@stderr hint: declare unpacker like this:
21+
@stderr fun CustomInt.unpackFromSlice(mutate s: slice): CustomInt
22+
*/
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type CustomInt = int;
2+
3+
fun CustomInt.packToBuilder(self, b: builder) {
4+
}
5+
6+
fun CustomInt.unpackFromSlice(mutate s: slice) {
7+
}
8+
9+
10+
fun main() {
11+
(5 as CustomInt).toCell()
12+
}
13+
14+
/**
15+
@compilation_should_fail
16+
@stderr auto-serialization via toCell() is not available for type `CustomInt`
17+
@stderr because `CustomInt.packToBuilder()` is declared incorrectly
18+
@stderr hint: it must accept 2 parameters and return nothing:
19+
@stderr fun CustomInt.packToBuilder(self, mutate b: builder)
20+
*/
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type CustomInt = int;
2+
3+
fun CustomInt.packToBuilder(self, mutate b: builder) {
4+
}
5+
6+
fun CustomInt.unpackFromSlice(self, mutate s: slice) {
7+
}
8+
9+
10+
fun main() {
11+
(5 as CustomInt).toCell()
12+
}
13+
14+
/**
15+
@compilation_should_fail
16+
@stderr auto-serialization via toCell() is not available for type `CustomInt`
17+
@stderr because `CustomInt.unpackFromSlice()` is declared incorrectly
18+
@stderr hint: it must accept 1 parameter and return an object:
19+
@stderr fun CustomInt.unpackFromSlice(mutate s: slice)
20+
*/
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
type CustomInt = int;
2+
3+
fun CustomInt.packToBuilder(self, mutate b: builder) {
4+
}
5+
6+
fun CustomInt.unpackFromSlice(mutate s: slice) {
7+
return (1, 2, 3)
8+
}
9+
10+
fun main() {
11+
(5 as CustomInt).toCell()
12+
}
13+
14+
/**
15+
@compilation_should_fail
16+
@stderr auto-serialization via toCell() is not available for type `CustomInt`
17+
@stderr because `CustomInt.unpackFromSlice()` is declared incorrectly
18+
@stderr hint: it must accept 1 parameter and return an object:
19+
*/
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
type CustomInt = int;
2+
3+
fun CustomInt.packToBuilder(self, mutate b: builder) {
4+
}
5+
6+
fun CustomInt.unpackFromSlice(mutate s: slice) {
7+
if (s.remainingBitsCount() > 10) {
8+
return 123;
9+
}
10+
return 600;
11+
}
12+
13+
fun main() {
14+
var s = "";
15+
s.loadAny<CustomInt>();
16+
}
17+
18+
/**
19+
@compilation_should_fail
20+
@stderr auto-serialization via loadAny() is not available for type `CustomInt`
21+
@stderr because `CustomInt.unpackFromSlice()` can't be inlined; probably, it contains `return` in the middle
22+
*/

tolk-tester/tests/lazy-load-tests.tolk

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,25 @@ struct WithVariadicInts {
222222
i2: varuint32;
223223
}
224224

225+
global gModByCustom: int;
226+
227+
type MagicGlobalModifier = ();
228+
229+
fun MagicGlobalModifier.packToBuilder(self, mutate b: builder) {
230+
b.storeUint(gModByCustom, 8);
231+
}
232+
233+
fun MagicGlobalModifier.unpackFromSlice(mutate s: slice) {
234+
gModByCustom = s.loadUint(8);
235+
return ();
236+
}
237+
238+
struct WithGlobalModifier {
239+
a: int8;
240+
g: MagicGlobalModifier = ();
241+
n: int8;
242+
}
243+
225244

226245
@noinline
227246
fun contract.getFakeData(seqno: int): Cell<WalletStorage> {
@@ -951,6 +970,17 @@ fun demo157() {
951970
return d.i2 == (1 << 200);
952971
}
953972

973+
@method_id(158)
974+
fun demo158() {
975+
val m1 = lazy WithGlobalModifier.fromSlice(stringHexToSlice("01FF"));
976+
__expect_lazy("[m1] skip (bits8), load g");
977+
m1.g; // modifies gModByCustom by a custom unpack function
978+
val after1 = gModByCustom;
979+
val m2 = lazy WithGlobalModifier.fromSlice(stringHexToSlice("010A02"));
980+
__expect_lazy("[m2] skip (bits8) (MagicGlobalModifier), load n");
981+
return (m2.n, gModByCustom, after1); // also modifies by skipping (unpack called)
982+
}
983+
954984

955985
@method_id(200)
956986
fun demo200() {
@@ -1638,6 +1668,7 @@ fun main() {
16381668
@testcase | 156 | x{0110} | 16
16391669
@testcase | 156 | x{FF10} | -100
16401670
@testcase | 157 | | -1
1671+
@testcase | 158 | | 2 10 255
16411672

16421673
@testcase | 200 | | 1
16431674
@testcase | 201 | | 1 2 -1
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// encoded as TL/B `len: (## 8) data: (bits (len*8))`
2+
type TelegramString = slice;
3+
4+
fun TelegramString.packToBuilder(self, mutate b: builder) {
5+
val bytes = self.remainingBitsCount() / 8;
6+
b.storeUint(bytes, 8);
7+
b.storeSlice(self);
8+
}
9+
10+
fun TelegramString.unpackFromSlice(mutate s: slice) {
11+
val bytes = s.loadUint(8);
12+
return s.loadBits(bytes * 8);
13+
}
14+
15+
type Custom8 = int;
16+
17+
fun Custom8.packToBuilder(self, mutate b: builder) {
18+
b.storeUint(self, 8)
19+
}
20+
21+
fun Custom8.unpackFromSlice(mutate s: slice) {
22+
return s.loadUint(8)
23+
}
24+
25+
struct StorWithStr {
26+
a: int32;
27+
str: TelegramString;
28+
b: int32;
29+
}
30+
31+
struct PointWithCustomInt {
32+
a: Custom8;
33+
b: int8;
34+
}
35+
36+
37+
type MyBorderedInt = int;
38+
39+
fun MyBorderedInt.packToBuilder(self, mutate b: builder) {
40+
if (self > 10) { b.storeUint(1, 4) }
41+
else if (self > 0) { b.storeUint(2, 4) }
42+
else { b.storeUint(3, 4) }
43+
}
44+
45+
fun MyBorderedInt.unpackFromSlice(mutate s: slice) {
46+
return match (s.loadUint(4)) {
47+
1 => 10,
48+
2 => 0,
49+
3 => -1,
50+
else => throw 123
51+
}
52+
}
53+
54+
struct WithMyBorder {
55+
a: int8;
56+
b: MyBorderedInt;
57+
}
58+
59+
type MyCustomNothing = ();
60+
61+
struct WithFakeWriter {
62+
a: int8;
63+
fake: MyCustomNothing = ();
64+
b: int8;
65+
}
66+
67+
fun MyCustomNothing.packToBuilder(self, mutate b: builder) {
68+
b.storeUint(123, 32);
69+
b.storeRef(createEmptyCell());
70+
}
71+
72+
global gModByCustom: int;
73+
74+
type MagicGlobalModifier = ();
75+
76+
fun MagicGlobalModifier.packToBuilder(self, mutate b: builder) {
77+
b.storeUint(gModByCustom, 8);
78+
}
79+
80+
fun MagicGlobalModifier.unpackFromSlice(mutate s: slice) {
81+
gModByCustom = s.loadUint(8);
82+
return ();
83+
}
84+
85+
struct WithGlobalModifier {
86+
a: int8;
87+
g: MagicGlobalModifier = ();
88+
n: int8;
89+
}
90+
91+
type Tensor3Skipping1 = (int, int, int);
92+
93+
fun Tensor3Skipping1.unpackFromSlice(mutate s: slice): Tensor3Skipping1 {
94+
val e0 = s.loadUint(8);
95+
val e2 = s.loadUint(8);
96+
return (e0, 0, e2);
97+
}
98+
99+
fun Tensor3Skipping1.packToBuilder(self, mutate b: builder) {
100+
b.storeUint(self.0, 8).storeUint(self.2, 8);
101+
}
102+
103+
104+
@method_id(101)
105+
fun test1() {
106+
var t: StorWithStr = { a: 10, str: "abc", b: 20 };
107+
var c = t.toCell();
108+
var back = c.load();
109+
return ((t.b == back.b) & (t.str.bitsEqual(back.str)), back.str.remainingBitsCount(), c.hash() & 0xFFFF);
110+
}
111+
112+
@method_id(102)
113+
fun test2() {
114+
var s = ("" as TelegramString, "" as TelegramString);
115+
return beginCell().storeAny(s).endCell().beginParse().remainingBitsCount();
116+
}
117+
118+
@method_id(103)
119+
fun test3() {
120+
return PointWithCustomInt.fromSlice(stringHexToSlice("0102"));
121+
}
122+
123+
@method_id(104)
124+
fun test4(initialInt: int) {
125+
var c = WithMyBorder { a: 0, b: initialInt }.toCell();
126+
return c.load().b;
127+
}
128+
129+
@method_id(105)
130+
fun test5() {
131+
var f: WithFakeWriter = { a: 10, b: 20 };
132+
var s = beginCell().storeAny(f).endCell().beginParse();
133+
val size = s.remainingBitsAndRefsCount();
134+
return (s.skipBits(8).loadUint(32), size.1);
135+
}
136+
137+
@method_id(106)
138+
fun test6() {
139+
gModByCustom = 6;
140+
val m1: WithGlobalModifier = { a: 8, n: 16 };
141+
val c1 = m1.toCell();
142+
val r2 = WithGlobalModifier.fromSlice(stringHexToSlice("01FF02"));
143+
val gAfter2 = gModByCustom;
144+
WithGlobalModifier.fromSlice(stringHexToSlice("010002")); // not deleted, sets 0
145+
val gAfter3 = gModByCustom;
146+
var s = stringHexToSlice("010902FF");
147+
s.skipAny<WithGlobalModifier>(); // custom loader also called
148+
return (c1.beginParse().skipBits(8).loadUint(8), r2.n, gAfter2, gAfter3, s.remainingBitsCount(), gModByCustom);
149+
}
150+
151+
@method_id(107)
152+
fun test7() {
153+
val t = (1, 2, 3) as Tensor3Skipping1;
154+
__expect_type(t.toCell(), "Cell<Tensor3Skipping1>");
155+
return t.toCell().load();
156+
}
157+
158+
fun main() {
159+
__expect_type("" as TelegramString, "TelegramString");
160+
}
161+
162+
/**
163+
@testcase | 101 | | -1 24 5203
164+
@testcase | 102 | | 16
165+
@testcase | 103 | | 1 2
166+
@testcase | 104 | 55 | 10
167+
@testcase | 104 | 8 | 0
168+
@testcase | 104 | -5 | -1
169+
@testcase | 105 | | 123 1
170+
@testcase | 106 | | 6 2 255 0 8 9
171+
@testcase | 107 | | 1 0 3
172+
173+
@fif_codegen
174+
"""
175+
test3() PROC:<{
176+
x{0102} PUSHSLICE
177+
8 LDU
178+
8 LDI
179+
DROP
180+
}>
181+
"""
182+
*/

tolk/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ set(TOLK_SOURCE
2020
pipe-refine-lvalue-for-mutate.cpp
2121
pipe-check-rvalue-lvalue.cpp
2222
pipe-check-pure-impure.cpp
23-
pipe-check-serialized-fields.cpp
2423
pipe-constant-folding.cpp
2524
pipe-optimize-boolean-expr.cpp
2625
pipe-detect-inline-in-place.cpp
26+
pipe-check-serialized-fields.cpp
2727
pipe-lazy-load-insertions.cpp
2828
pipe-transform-on-message.cpp
2929
pipe-ast-to-legacy.cpp

0 commit comments

Comments
 (0)