Skip to content

Commit 249badf

Browse files
committed
[Tolk] Union types T1 | T2 | ... and pattern matching
Union types allow a variable to hold multiple possible types. They are key to message parsing and routing in future struct-based designs. At the TVM level, they are tagged unions, like enums in Rust. Pattern matching is the only way of handling union types: > match (result) { > slice => { /* result is smart-casted to slice */ } > UserId => { /* result is smart-casted to UserId */ } > } `match` must cover all union cases (should be exhaustive). It can also be used as an expression. Variable declaration inside `match` is allowed. Besides `match`, you can test a union type by `is`: > if (v is cell) { > // v is smart-casted to cell
1 parent e745696 commit 249badf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+3361
-706
lines changed

tolk-tester/tests/generics-1.tolk

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ fun test102(): (int, int, int, [int, int]) {
2424
var b = getTwo() as int;
2525
var c: int = 1 ? getTwo() : getTwo();
2626
var c redef = getTwo();
27+
var d: int = match (1 as int|slice|builder) { int => getTwo(), slice => getTwo(), builder => getTwo() };
2728
var ab_tens = (0, (1, 2));
2829
ab_tens.0 = getTwo();
2930
ab_tens.1.1 = getTwo();
@@ -169,6 +170,29 @@ fun test111() {
169170
return (isSomeNullableNull(i1), isSomeNullableNull(i2), isSomeNullableNull<int8>(i1), isSomeNullableNull<int?>(i1));
170171
}
171172

173+
@method_id(112)
174+
fun test112(v: int | slice?) {
175+
return (eq1(v), eq4(v == null));
176+
}
177+
178+
fun makeNullable<T1, T2>(a: T1 | T2): T1 | T2 | null {
179+
return a;
180+
}
181+
182+
@method_id(113)
183+
fun test113(a: int | slice) {
184+
var b: int | (int, int) = match (a) {
185+
int => a,
186+
slice => (a.loadInt(32), -1)
187+
};
188+
__expect_type(makeNullable(b), "int | (int, int) | null");
189+
return (
190+
makeNullable(a), -100, makeNullable(b), -100, makeNullable<int, null>(9), -100, makeNullable<slice, null>(null), -100,
191+
makeNullable<int, (int, int) | builder>(b), -100, makeNullable<int, (int, int, int)?>(null)
192+
);
193+
}
194+
195+
172196
fun main(x: int): (int, [Tup2Int]) {
173197
__expect_type(test110, "() -> (Tensor2Int, Tensor2Int)");
174198

@@ -190,12 +214,16 @@ fun main(x: int): (int, [Tup2Int]) {
190214
@testcase | 109 | 5 | 5 (null) (null)
191215
@testcase | 110 | | 2 2 5 2
192216
@testcase | 111 | | 0 0 0 0
217+
@testcase | 112 | 5 1 | 5 1 0
218+
@testcase | 112 | 0 0 | 0 0 -1
219+
@testcase | 113 | 5 1 | 5 1 -100 (null) 5 1 -100 9 -100 (null) -100 (null) 5 1 -100 (null) (null) (null) 0
193220

194221
@fif_codegen DECLPROC eq1<int>
195222
@fif_codegen DECLPROC eq1<tuple>
196223
@fif_codegen DECLPROC eq1<(int,int)>
197224
@fif_codegen DECLPROC eq1<[int,int]>
198225
@fif_codegen DECLPROC eq1<MInt?>
226+
@fif_codegen DECLPROC eq1<int|slice|null>
199227
@fif_codegen DECLPROC getTwo<int>
200228

201229
@fif_codegen_avoid DECLPROC eq1

tolk-tester/tests/intN-tests.tolk

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ fun autoInferUInt16Opt(x: uint16) {
2323
return null;
2424
}
2525

26-
fun autoInferInt_v1() {
26+
fun inferInt_v1(): int {
2727
if (random()) { return 0; }
2828
else if (random()) { return 0 as uint16; }
2929
else { return 0 as int8; }
3030
}
3131

32-
fun autoInferInt_v2() {
32+
fun inferInt_v2(): int {
3333
if (random()) { return 0 as uint1; }
3434
else if (random()) { return 0 as int123; }
3535
else { return 0 as int8; }
@@ -87,8 +87,8 @@ fun test3(op: int32, qid: uint64) {
8787

8888
__expect_type(autoInferInt8, "(int8) -> int8");
8989
__expect_type(autoInferUInt16Opt(0), "uint16?");
90-
__expect_type(autoInferInt_v1(), "int");
91-
__expect_type(autoInferInt_v2(), "int");
90+
__expect_type(inferInt_v1(), "int");
91+
__expect_type(inferInt_v2(), "int");
9292

9393
var amount: uint100 = 1000;
9494
var percent: uint8 = 50;
@@ -124,7 +124,7 @@ fun test5() {
124124
fun test6() {
125125
var x: int11 = ~(0 as int22);
126126
while (x) {}
127-
return x ? x : 0;
127+
return x ? x as int : 0;
128128
}
129129

130130
fun test7() {
@@ -211,7 +211,7 @@ fun test13(x1: int?, x2: int8?, x3: int, x4: int8): (int8?, int8?, int8?, int8?,
211211

212212
@method_id(114)
213213
fun test14(firstComponent: int8?): (int, int16)? {
214-
return firstComponent! < 10 ? null : (firstComponent!, 2);
214+
return firstComponent! < 10 ? null : (firstComponent! as int, 2 as int16);
215215
}
216216

217217
fun assign0<T>(mutate v: T) { v = 0; }
@@ -232,7 +232,7 @@ fun main() {
232232
__expect_type(0 as coins, "coins");
233233
__expect_type(someN_coins as coins?, "coins?");
234234
__expect_type(someN_coins as int8?, "int8?");
235-
__expect_type(10>3 ? 0 as uint8 : 0 as uint16, "int");
235+
__expect_type(10>3 ? (0 as uint8) as uint8 | uint16 : 0 as uint16, "uint8 | uint16");
236236
return t;
237237
}
238238

@@ -245,7 +245,7 @@ fun main() {
245245
@testcase | 110 | | 50000000 50000100 1234000000 51000000
246246
@testcase | 111 | | [ 1000000000 1000000000 1000000000 -321123456789 321123456789 1100000000 ]
247247
@testcase | 114 | 5 | (null) (null) 0
248-
@testcase | 114 | 15 | 15 2 -1
248+
@testcase | 114 | 15 | 15 2 130
249249

250250
@fif_codegen DECLPROC assign0<int8>
251251
@fif_codegen DECLPROC assign0<uint16>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
fun okWithJustInt(a: int8, b: int16): int {
2+
if (a > 10) {
3+
return a;
4+
}
5+
return b;
6+
}
7+
8+
fun okWithManualUnion(a: int8, b: int16): int16 | int8 {
9+
if (a > 10) {
10+
return a;
11+
}
12+
return b;
13+
}
14+
15+
fun autoInferUnionType(a: int8, b: int16) {
16+
if (a > 10) {
17+
return a;
18+
}
19+
return b;
20+
}
21+
22+
/**
23+
@compilation_should_fail
24+
@stderr function `autoInferUnionType` calculated return type is `int8 | int16`; probably, it's not what you expected; declare `fun (...): <return_type>` manually
25+
@stderr fun autoInferUnionType
26+
*/
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
fun getIntOrSlice() { return 5 as int | slice; }
2+
3+
fun autoInferUnionType(a: int | slice | null) {
4+
var cc = match (a) {
5+
int => a = getIntOrSlice(),
6+
slice => return beginCell(),
7+
null => return -20
8+
};
9+
return cc;
10+
}
11+
12+
/**
13+
@compilation_should_fail
14+
@stderr function `autoInferUnionType` calculated return type is `builder | int | slice`; probably, it's not what you expected
15+
@stderr fun autoInferUnionType
16+
*/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
fun increment(mutate x: int) {
2+
x += 1;
3+
}
4+
5+
fun cantModifyValDeclaredInMatch() {
6+
match (var a = (0 + 10 + 90) as int | slice) {
7+
int => increment(mutate a),
8+
slice => {},
9+
};
10+
match (val b = (0 + 10 + 90) as int | slice) {
11+
int => increment(mutate b),
12+
slice => {},
13+
};
14+
}
15+
16+
/**
17+
@compilation_should_fail
18+
@stderr modifying immutable variable `b`
19+
@stderr in function `cantModifyValDeclaredInMatch`
20+
@stderr increment(mutate b)
21+
*/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
fun main(x: int | slice) {
2+
match (var a = x) { // a variable declared in `match` subject exists only within a match
3+
int => a + 0,
4+
slice => a.loadInt(32),
5+
}
6+
return a;
7+
}
8+
9+
/**
10+
@compilation_should_fail
11+
@stderr undefined symbol `a`
12+
@stderr return a;
13+
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
fun main() {
3+
match (10) {
4+
asdf => 1,
5+
};
6+
}
7+
8+
/**
9+
@compilation_should_fail
10+
@stderr unknown type name `asdf`
11+
@stderr asdf => 1
12+
*/
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
fun main(a: int | slice) {
2+
match (a) {
3+
int => {
4+
var cc: int = a;
5+
},
6+
slice => {
7+
return cc;
8+
},
9+
};
10+
}
11+
12+
/**
13+
@compilation_should_fail
14+
@stderr undefined symbol `cc`
15+
@stderr return cc;
16+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
fun main(r: int | slice) {
3+
// after `is` we expect a type, parse `int?`, and expression becomes incorrect
4+
return r is int ? 10 : 20;
5+
}
6+
7+
/**
8+
@compilation_should_fail
9+
@stderr expected `;`, got `10`
10+
*/
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
fun main(): int {
2-
;; here is not a comment
2+
;; here not a comment
33
}
44

55
/**
66
@compilation_should_fail
7-
@stderr error: expected `;`, got `is`
7+
@stderr error: expected `;`, got `not`
88
*/

0 commit comments

Comments
 (0)