1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[Tolk] Nullable types T? and null safety

This commit introduces nullable types `T?` that are
distinct from non-nullable `T`.
Example: `int?` (int or null) and `int` are different now.
Previously, `null` could be assigned to any primitive type.
Now, it can be assigned only to `T?`.

A non-null assertion operator `!` was also introduced,
similar to `!` in TypeScript and `!!` in Kotlin.

If `int?` still occupies 1 stack slot, `(int,int)?` and
other nullable tensors occupy N+1 slots, the last for
"null precedence". `v == null` actually compares that slot.
Assigning `(int,int)` to `(int,int)?` implicitly creates
a null presence slot. Assigning `null` to `(int,int)?` widens
this null value to 3 slots. This is called "type transitioning".

All stdlib functions prototypes have been updated to reflect
whether they return/accept a nullable or a strict value.

This commit also contains refactoring from `const FunctionData*`
to `FunctionPtr` and similar.
This commit is contained in:
tolk-vm 2025-02-24 20:13:36 +03:00
parent 1389ff6789
commit f3e620f48c
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
62 changed files with 2031 additions and 702 deletions

View file

@ -2,7 +2,7 @@ import "@stdlib/tvm-lowlevel"
fun pair_first<X, Y>(p: [X, Y]): X asm "FIRST";
fun one(dummy: tuple) {
fun one(dummy: tuple?) {
return 1;
}

View file

@ -206,9 +206,9 @@ fun test116() {
fun main(value: int) {
var (x: int, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
var (x: int?, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
if (x == null && y == null) { return null; }
return x == null || y == null ? -1 : x + y;
return x == null || y == null ? -1 : x! + y!;
}
/**

View file

@ -8,7 +8,7 @@ fun unnamed_args(_: int, _: slice, _: int) {
return true;
}
fun main(x: int, y: int, z: int): bool {
fun main(x: int, y: int, z: int): bool? {
op = `_+_`;
if (0) { return null; }
return check_assoc(x, y, z);

View file

@ -32,7 +32,8 @@ fun test1(): [int,int,int,int,int] {
fun test2(): [int,int,int] {
var b: builder = beginCell().myStoreInt(1, 32);
b = b.myStoreInt(2, 32);
b.myStoreInt(3, 32);
// operator ! here and below is used just for testing purposes, it doesn't affect the result
b!.myStoreInt(3, 32);
var cs: slice = b.endCell().beginParse();
var one: int = cs.myLoadInt(32);
@ -43,14 +44,14 @@ fun test2(): [int,int,int] {
@method_id(103)
fun test3(ret: int): int {
val same: int = beginCell().storeUint(ret,32).endCell().beginParse().loadUint(32);
val same: int = beginCell()!.storeUint(ret,32).endCell().beginParse().loadUint(32);
return same;
}
@method_id(104)
fun test4(): [int,int] {
var b: builder = beginCell().myStoreInt(1, 32);
b = b.storeInt(2, 32).storeInt(3, 32);
var b: builder = (beginCell() as builder).myStoreInt(1, 32);
b = b!.storeInt(2, 32)!.storeInt(3, 32);
var cs: slice = b.endCell().beginParse();
var (one, _, three) = (cs.getFirstBits(32).loadUint(32), cs.skipBits(64), cs.load_u32());
@ -116,7 +117,7 @@ fun test10() {
fun test11() {
var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeInt(3, 32).storeInt(4, 32).storeInt(5, 32).storeInt(6, 32).storeInt(7, 32).endCell().beginParse();
var size1 = getRemainingBitsCount(s);
s.skipBits(32);
s!.skipBits(32);
var s1: slice = s.getFirstBits(64);
var n1 = s1.loadInt(32);
var size2 = getRemainingBitsCount(s);

View file

@ -1,15 +1,15 @@
import "@stdlib/tvm-dicts"
fun addIntToIDict(mutate self: cell, key: int, number: int): void {
fun addIntToIDict(mutate self: cell?, key: int, number: int): void {
return self.iDictSetBuilder(32, key, beginCell().storeInt(number, 32));
}
fun calculateDictLen(d: cell) {
fun calculateDictLen(d: cell?) {
var len = 0;
var (k, v, f) = d.uDictGetFirst(32);
while (f) {
len += 1;
(k, v, f) = d.uDictGetNext(32, k);
(k, v, f) = d.uDictGetNext(32, k!);
}
return len;
}
@ -25,13 +25,13 @@ fun loadTwoDigitNumberFromSlice(mutate self: slice): int {
fun test101(getK1: int, getK2: int, getK3: int) {
var dict = createEmptyDict();
dict.uDictSetBuilder(32, 1, beginCell().storeUint(1, 32));
var (old1: slice, found1) = dict.uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse());
var (old2: slice, found2) = dict.uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse());
var (cur3: slice, found3) = dict.uDictGet(32, getK3);
var (old1: slice?, found1) = dict.uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse());
var (old2: slice?, found2) = dict.uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse());
var (cur3: slice?, found3) = dict.uDictGet(32, getK3);
return (
found1 ? old1.loadUint(32) : -1,
found2 ? old2.loadUint(32) : -1,
found3 ? cur3.loadUint(32) : -1
found1 ? old1!.loadUint(32) : -1,
found2 ? old2!.loadUint(32) : -1,
found3 ? cur3!.loadUint(32) : -1
);
}
@ -47,7 +47,7 @@ fun test102() {
while (!shouldBreak) {
var (kDel, kVal, wasDel) = dict.iDictDeleteLastAndGet(32);
if (wasDel) {
deleted.tuplePush([kDel, kVal.loadInt(32)]);
deleted.tuplePush([kDel, kVal!.loadInt(32)]);
} else {
shouldBreak = true;
}
@ -82,14 +82,14 @@ fun test104() {
var (old2, _) = dict.sDictDeleteAndGet(32, "key1");
var (restK, restV, _) = dict.sDictGetFirst(32);
var (restK1, restV1, _) = dict.sDictDeleteLastAndGet(32);
assert (restK.isSliceBitsEqual(restK1)) throw 123;
assert (restV.isSliceBitsEqual(restV1)) throw 123;
assert (restK!.isSliceBitsEqual(restK1!)) throw 123;
assert (restV!.isSliceBitsEqual(restV1!)) throw 123;
return (
old1.loadTwoDigitNumberFromSlice(),
old2.loadTwoDigitNumberFromSlice(),
restV.loadTwoDigitNumberFromSlice(),
restK.loadTwoDigitNumberFromSlice(),
restK.loadTwoDigitNumberFromSlice()
old1!.loadTwoDigitNumberFromSlice(),
old2!.loadTwoDigitNumberFromSlice(),
restV!.loadTwoDigitNumberFromSlice(),
restK!.loadTwoDigitNumberFromSlice(),
restK!.loadTwoDigitNumberFromSlice()
);
}

View file

@ -49,17 +49,17 @@ fun manyEq<T1, T2, T3>(a: T1, b: T2, c: T3): [T1, T2, T3] {
fun test104(f: int) {
var result = (
manyEq(1 ? 1 : 1, f ? 0 : null, !f ? getTwo() as int : null),
manyEq(f ? null as int : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool(), eq4(f))
manyEq(f ? null as int? : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool(), eq4(f))
);
__expect_type(result, "([int, int, int], [int, bool, int])");
__expect_type(result, "([int, int?, int?], [int?, bool, int])");
return result;
}
fun calcSum<X>(x: X, y: X) { return x + y; }
fun calcSum<X>(x: X, y: X) { return x! + y!; }
@method_id(105)
fun test105() {
if (0) { calcSum(((0)), null); }
if (0) { calcSum(((0 as int?)), null); }
return (calcSum(1, 2));
}

View file

@ -1,21 +1,21 @@
import "@stdlib/tvm-dicts"
fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell {
var dict: cell = createEmptyDict();
fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell? {
var dict: cell? = createEmptyDict();
dict.iDictSetBuilder(32, 3, beginCell().storeInt(30, 32));
dict.iDictSetBuilder(32, 4, beginCell().storeInt(40, 32));
dict.iDictSetBuilder(32, 5, beginCell().storeInt(valueAt5, 32));
return dict;
}
fun lookupIdxByValue(idict32: cell, value: int): int {
var cur_key = -1;
fun lookupIdxByValue(idict32: cell?, value: int): int {
var cur_key: int? = -1;
do {
var (cur_key redef, cs: slice, found: bool) = idict32.iDictGetNext(32, cur_key);
var (cur_key redef, cs: slice?, found: bool) = idict32.iDictGetNext(32, cur_key!);
// one-line condition (via &) doesn't work, since right side is calculated immediately
if (found) {
if (cs.loadInt(32) == value) {
return cur_key;
if (cs!.loadInt(32) == value) {
return cur_key!;
}
}
} while (found);

View file

@ -86,8 +86,8 @@ fun test104() {
}
@method_id(105)
fun test105(x: int, y: int): (tuple, int, (int, int), int, int) {
var ab = (createEmptyTuple(), (x, y), tupleSize);
fun test105(x: int, y: int): (tuple, int, (int?, int), int, int) {
var ab = (createEmptyTuple(), (x as int?, y), tupleSize);
ab.0.tuplePush(1);
tuplePush(mutate ab.0, 2);
ab.1.0 = null;
@ -98,7 +98,7 @@ fun test105(x: int, y: int): (tuple, int, (int, int), int, int) {
@method_id(106)
fun test106(x: int, y: int) {
var ab = [createEmptyTuple(), [x, y], tupleSize];
var ab = [createEmptyTuple(), [x as int?, y], tupleSize];
ab.0.tuplePush(1);
tuplePush(mutate ab.0, 2);
ab.1.0 = null;
@ -233,6 +233,25 @@ fun test121(zero: int) {
return t;
}
fun isFirstComponentGt0<T1,T2>(t: (T1, T2)): bool {
return t.0 > 0;
}
@method_id(122)
fun test122(x: (int, int)) {
return (
isFirstComponentGt0(x), isFirstComponentGt0((2, beginCell())), isFirstComponentGt0<int,slice?>((0, null)),
x.isFirstComponentGt0(), (2, beginCell()).isFirstComponentGt0(), (0, null).isFirstComponentGt0<int,slice?>()
);
}
@method_id(123)
fun test123() {
var t = [[10, 20]] as [[int,int]]?;
t!.0.0 = t!.0.1 = 100;
return t;
}
fun main(){}
@ -258,6 +277,8 @@ fun main(){}
@testcase | 119 | 1 2 3 4 | 4 1 3
@testcase | 120 | | 3 4 [ 5 6 ]
@testcase | 121 | 0 | [ 3 ]
@testcase | 122 | 1 2 | -1 -1 0 -1 -1 0
@testcase | 123 | | [ [ 100 100 ] ]
@fif_codegen
"""

View file

@ -18,10 +18,12 @@ fun test1(x: int, y: int) {
__expect_type(random() ? x : y, "int");
__expect_type(eq(x), "int");
__expect_type(eq<int>(x), "int");
__expect_type(eq<int>(null), "int");
__expect_type(eq<int?>(null), "int?");
__expect_type(x as int, "int");
__expect_type(+x, "int");
__expect_type(~x, "int");
__expect_type(x!, "int");
__expect_type(x!!!, "int");
{
var x: slice = beginCell().endCell().beginParse();
__expect_type(x, "slice");
@ -62,9 +64,9 @@ fun test5(x: int) {
__expect_type([], "[]");
__expect_type([x], "[int]");
__expect_type([x, x >= 1], "[int, bool]");
__expect_type([x, x >= 1, null as slice], "[int, bool, slice]");
__expect_type([x, x >= 1, null as slice?], "[int, bool, slice?]");
__expect_type((x, [x], [[x], x]), "(int, [int], [[int], int])");
__expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell]");
__expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell?]");
}
fun test6() {

View file

@ -6,5 +6,5 @@ fun failCantDeduceWithoutArgument() {
/**
@compilation_should_fail
@stderr can not deduce X for generic function `f<X>`
@stderr too few arguments in call to `f`, expected 2, have 1
*/

View file

@ -0,0 +1,11 @@
fun calcSum<X>(x: X, y: X) { return x + y; }
fun cantApplyPlusOnNullable() {
return calcSum(((0 as int?)), null);
}
/**
@compilation_should_fail
@stderr while instantiating generic function `calcSum<int?>`
@stderr can not apply operator `+` to `int?` and `int?`
*/

View file

@ -0,0 +1,10 @@
fun getNullableTuple(): tuple? { return createEmptyTuple(); }
fun cantUseLValueUnwrappedNotNull() {
tuplePush(mutate getNullableTuple()!, 1);
}
/**
@compilation_should_fail
@stderr function call can not be used as lvalue
*/

View file

@ -0,0 +1,10 @@
fun getNullableTuple(): tuple? { return createEmptyTuple(); }
fun cantUseLValueUnwrappedNotNull() {
tuplePush(mutate getNullableTuple()!, 1);
}
/**
@compilation_should_fail
@stderr function call can not be used as lvalue
*/

View file

@ -0,0 +1,13 @@
fun acceptMutateNullableTensor(mutate self: (int, int)?) {
}
fun cantModifyTupleIndexWithTypeTransition() {
var t = [1, null];
t.1.acceptMutateNullableTensor();
}
/**
@compilation_should_fail
@stderr can not call method for mutate `(int, int)?` with object of type `null`
@stderr because mutation is not type compatible
*/

View file

@ -0,0 +1,14 @@
fun autoGetIntOrNull() {
if (random()) { return 1; }
return null;
}
fun testAutoInferredIntOrNull() {
var b: builder = autoGetIntOrNull() as builder;
}
/**
@compilation_should_fail
@stderr type `int?` can not be cast to `builder`
*/

View file

@ -0,0 +1,13 @@
fun getNullable4(): int? {
return 4;
}
fun testCantSumNullable() {
return 1 + getNullable4();
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int` and `int?`
*/

View file

@ -0,0 +1,13 @@
@pure
fun myDictDeleteStrict(mutate self: cell, keyLen: int, key: int): bool
asm(key self keyLen) "DICTIDEL";
fun testCantCallDictMethodsOnNullable(c: cell) {
c.beginParse().loadDict().myDictDeleteStrict(16, 1);
}
/**
@compilation_should_fail
@stderr can not call method for `cell` with object of type `cell?`
*/

View file

@ -0,0 +1,10 @@
fun testCantUseNullableAsCondition(x: int?) {
if (x) { return 1; }
return 0;
}
/**
@compilation_should_fail
@stderr can not use `int?` as a boolean condition
*/

View file

@ -0,0 +1,16 @@
fun incrementOrSetNull(mutate x: int?) {
if (random()) { x! += 1; }
else { x = null; }
}
fun cantCallMutateMethodNotNullable() {
var x = 1;
incrementOrSetNull(mutate x);
return x;
}
/**
@compilation_should_fail
@stderr can not pass `int` to mutate `int?`
@stderr because mutation is not type compatible
*/

View file

@ -53,9 +53,8 @@ fun testDict(last: int) {
}
@method_id(105)
fun testNotNull(x: int) {
// return [x == null, null == x, !(x == null), null == null, +(null != null)];
return [x == null, null == x, !(x == null)];
fun testNotNull(x: int?) {
return [x == null, null == x, !(x == null), null == null, (null != null) as int];
}
@method_id(106)
@ -170,8 +169,8 @@ fun main() {
@testcase | 104 | 50 | 3 5 -1
@testcase | 104 | 100 | 3 5 5
@testcase | 104 | 0 | 3 -1 5
@testcase | 105 | 0 | [ 0 0 -1 ]
@testcase | 105 | null | [ -1 -1 0 ]
@testcase | 105 | 0 | [ 0 0 -1 -1 0 ]
@testcase | 105 | null | [ -1 -1 0 -1 0 ]
@testcase | 106 | | [ 0 0 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ 0 -1 ]
@testcase | 107 | | [ -1 -1 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ -1 0 ]
@testcase | 108 | 1 2 | -1

View file

@ -2,13 +2,13 @@ import "@stdlib/lisp-lists"
@method_id(101)
fun test1() {
var numbers: tuple = createEmptyList();
var numbers: tuple? = createEmptyList();
numbers = listPrepend(1, numbers);
numbers = listPrepend(2, numbers);
numbers = listPrepend(3, numbers);
numbers = listPrepend(4, numbers);
var (h: int, numbers redef) = listSplit(numbers);
h += listGetHead(numbers);
var (h: int, numbers redef) = listSplit(numbers!);
h += listGetHead(numbers!);
_ = null;
(_, _) = (null, null);
@ -22,22 +22,22 @@ fun test1() {
}
@method_id(102)
fun test2(x: int) {
fun test2(x: int?) {
if (null != x) {
var y: int = null;
var y: int? = null;
if (y != null) { return 10; }
return y;
}
try {
return x + 10; // will throw, since not a number
return x! + 10; // will throw, since not a number
} catch {
return -1;
}
return 100;
}
fun myIsNull(x: int): int {
return x == null ? -1 : x;
fun myIsNull(x: int?): int {
return x == null ? -1 : x!;
}
@method_id(103)
@ -64,21 +64,28 @@ fun test4(): null {
@method_id(105)
fun test5() {
var n: slice = getUntypedNull();
return !(null == n) ? n.loadInt(32) : 100;
var n: slice? = getUntypedNull();
return !(null == n) ? n!.loadInt(32) : 100;
}
@method_id(107)
fun test7() {
var b = beginCell().storeMaybeRef(null);
var s = b.endCell().beginParse();
var b = beginCell().storeMaybeRef(null) as builder?;
var s = b!.endCell().beginParse();
var c = s.loadMaybeRef();
return (null == c) as int * 10 + (b != null) as int;
}
fun test8() {
__expect_type(null, "null");
__expect_type([[null]], "[[null]]");
__expect_type(null as tuple?, "tuple?");
__expect_type(null as [int]?, "[int]?");
__expect_type(((null)) as (int, int)?, "(int, int)?");
}
fun main() {
// now, the compiler doesn't optimize this at compile-time, fif codegen contains ifs
var i: int = null;
var i: int? = null;
if (i == null) {
return 1;
}
@ -120,12 +127,12 @@ fun main() {
"""
main PROC:<{
//
PUSHNULL // i
ISNULL // '2
IFJMP:<{ //
1 PUSHINT // '3=1
}> //
10 PUSHINT // '4=10
PUSHNULL // i
ISNULL // '2
IFJMP:<{ //
1 PUSHINT // '3=1
}> //
10 PUSHINT // '4=10
}>
"""
@ -139,8 +146,8 @@ fun main() {
10 MULCONST // b '13
SWAP // '13 b
ISNULL // '13 '14
NOT // '13 '15
ADD // '16
NOT // '13 '14
ADD // '15
}>
"""
*/

View file

@ -0,0 +1,474 @@
fun getNullableInt(): int? { return 5; }
fun sumOfNullableTensorComponents(t: (int, int)?): int {
if (t == null) { return 0; }
return t!.0 + t!.1;
}
fun isTensorNull(t: (int, int)?) {
return t == null;
}
fun incrementNullableTensorComponents(mutate self: (int, int)?): self {
if (self != null) {
self!.0 += 1;
self!.1 += 1;
}
return self;
}
fun incrementTensorComponents(mutate self: (int, int)): self {
self.0 += 1;
self.1 += 1;
return self;
}
fun assignFirstComponent(mutate t: (int, int), first: int) {
t!.0 = first;
}
fun assignFirstComponentNullable(mutate t: (int, int)?, first: int) {
if (t == null) {
t = (first, 0);
} else {
t!.0 = first;
}
}
fun getNullableTensor(firstComponent: int?): (int, int)? {
return firstComponent == null ? null : (firstComponent!, 2);
}
fun sumOfTensor(x: (int, int)) {
return x.0 + x.1;
}
fun assignNullTo<T>(mutate x: T?) {
x = null;
}
fun getTensor12() {
return (1,2);
}
@method_id(101)
fun test101(): (int, int)? {
return (1, 2);
}
@method_id(102)
fun test102(): ((int, int)?, (int, int)?) {
var t = (1, 2);
return (t, null);
}
@method_id(103)
fun test103(t: (int, int)) {
var t2: (int, int)? = t;
return (sumOfNullableTensorComponents(t), sumOfNullableTensorComponents(t2), sumOfNullableTensorComponents(null), t2);
}
@method_id(104)
fun test104() {
var t1_1: (int, int)? = (1, 2);
var t1_2: (int, int)? = t1_1;
var t1_3: (int, int)? = t1_1!;
var t2_1: (int, int)? = null;
var t2_2 = t2_1;
return (t1_3, t2_2);
}
@method_id(105)
fun test105() {
return (null as (int, slice, cell)?, (1, 2, 3) as (int, int, int)?);
}
@method_id(106)
fun test106() {
var t: (int?, int?)? = (((((1, 2))) as (int, int)));
return t;
}
@method_id(107)
fun test107() {
var ab = (1, 2);
var ab2: (int, int)? = ab;
return (isTensorNull(ab), isTensorNull(ab2), isTensorNull(null), ab.isTensorNull(), ab2.isTensorNull(), null.isTensorNull());
}
@method_id(108)
fun test108(x1: (int, int)) {
incrementTensorComponents(mutate x1);
x1.incrementTensorComponents();
var x2: (int, int)? = x1;
x2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x2);
var x3: (int, int)? = null;
x3.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x3);
return (x1, x2, x3);
}
fun isTensorNullGen<T1, T2>(t: (T1, T2)?) {
return t == null;
}
@method_id(109)
fun test109() {
var x1 = (1, 2);
var x2: (int, int)? = x1;
var x3: (int, int)? = x1.1 > 10 ? (1, 2) : null;
return (
isTensorNullGen(x1), isTensorNullGen(x2), isTensorNullGen<int,int>(null),
isTensorNullGen<int,int>(x1), isTensorNullGen<int,int>(x3),
x1.isTensorNullGen(), x2.isTensorNullGen(), x3.isTensorNullGen(), null.isTensorNullGen<int,int>()
);
}
global g110_1: (int, int);
global g110_2: (int, int)?;
@method_id(110)
fun test110() {
g110_1 = getNullableTensor(1)!;
incrementTensorComponents(mutate g110_1);
g110_1.incrementTensorComponents();
g110_2 = g110_1;
g110_2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate g110_2);
var tmp = g110_2;
g110_2 = null;
g110_2.incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate g110_2);
return (g110_1, g110_2, tmp);
}
@method_id(111)
fun test111() {
var x = (1, 2);
assignFirstComponent(mutate x, 50);
var x2: (int, int)? = null;
var x3 = x2;
assignFirstComponentNullable(mutate x2, 30);
assignFirstComponentNullable(mutate x3, 70);
g110_1 = (1, 2);
g110_2 = null;
assignFirstComponent(mutate g110_1, 90);
assignFirstComponentNullable(mutate g110_2, 100);
return (x.0, x2!.0, x3!.0, g110_1.0, g110_2!.0);
}
@method_id(112)
fun test112() {
var x: (int, int)? = (10, 20);
incrementTensorComponents(mutate x!);
x!.incrementTensorComponents();
return x;
}
@method_id(113)
fun test113() {
var t = [1, null]; // t.1 is always null
return isTensorNull(t.1);
}
@method_id(114)
fun test114(): ((slice, (cell, [int, slice, tuple]))?, slice?, (int?, bool?)?) {
var t = [[null]];
return (t.0.0, t.0.0, t.0.0);
}
@method_id(115)
fun test115() {
var tt = getNullableTensor(null);
assignFirstComponentNullable(mutate tt, 5);
return (
getNullableTensor(1)!.incrementTensorComponents(),
sumOfNullableTensorComponents(getNullableTensor(1).incrementNullableTensorComponents().incrementNullableTensorComponents()),
getNullableTensor(null).incrementNullableTensorComponents(),
tt,
sumOfNullableTensorComponents(getNullableTensor(null))
);
}
@method_id(116)
fun test116(returnNull: bool) {
var t1: (int, int)? = returnNull ? null : getTensor12();
var t2 = returnNull ? null as (int, int)? : getTensor12() as (int, int)?;
returnNull ? null : (1, 2);
return (t1, t2);
}
@method_id(117)
fun test117() {
var (a, b: (int, int)?, c) = (1, null, 3);
return (b, a, c);
}
fun autoInferNullableTensor(a: int?, b: int) {
if (a != null) {
return (a!, b);
}
return null;
}
@method_id(118)
fun test118(a: int?) {
return autoInferNullableTensor(a, 10);
}
@method_id(119)
fun test119() {
var x: (int, int)? = (1, 2);
x = null;
var tt: (int, (int, int)?) = (0, (1, 2));
tt.1 = null;
var third: (int, (int, int)?, int) = (0, (1, 2), 3);
third.2 = 100;
return (x, tt.1, third.1, third.2);
}
@method_id(120)
fun test120(setNull: bool) {
var x: (int, int)? = (1, 2);
if (setNull) {
assignNullTo(mutate x);
}
return x;
}
@method_id(121)
fun test121() {
var t: [int?, [int?, int?]?] = [1, [2, 3]];
t.1 = [3, 4];
return t;
}
@method_id(122)
fun test122(setNull: bool) {
var t: [int?, [int?, int?]?, int?, [int?, int?]?]? = [1, [2, 3], 4, null];
if (setNull) {
assignNullTo(mutate t!.1);
} else {
var rhs = [3, 4];
t!!.1 = rhs;
}
return t;
}
@method_id(123)
fun test123() {
var t: (int?, (int?, int?)?) = (1, (2, 3));
t.1 = (3, 4);
return t;
}
@method_id(124)
fun test124(setNull: bool) {
var t: (int?, (int?, int?)?, int?, (int?, int?)?)? = (1, (2, 3), 4, null);
if (setNull) {
assignNullTo(mutate t!.1);
} else {
var rhs = (3, 4);
t!!.1 = rhs;
}
return t;
}
global g125: int;
fun getT125(): (int, (int, int)?, (int?, int)?) { return (g125 += 1, null, null); }
@method_id(125)
fun test125() {
g125 = 0;
getT125().1 = null;
getT125().2 = (1, 2);
(getT125()!! as (int, (int, int)?, (int?, int)?)).2 = null;
// test that nothing left on a stack
return g125;
}
@method_id(126)
fun test126() {
var tt1: (int, null, int) = (1, null, 2);
var (a: int, b: (int, int)?, c: int) = tt1;
return (a, b, c);
}
@method_id(127)
fun test127(choice: int) {
var tt1: (int, null, int) = (1, null, 2);
var tt2: (int, (int, int), int) = (1, (2, 3), 4);
var tt3: (int, (int, int)?, int) = (1, null, 5);
var abc: (int, (int, int)?, int) = choice == 1 ? tt1 : choice == 2 ? tt2 : tt3;
return abc;
}
fun get128_1() { return (1, null, 2); }
fun get128_2() { return null; }
fun get128_3() { return (1, (2, 3), 4); }
fun takeT128(abc: (int, (int, int)?, int)?) { return abc; }
@method_id(128)
fun test128(choice: int) {
if (choice == 1) {
return takeT128(get128_1())!;
}
if (choice == 2) {
return takeT128(get128_2());
}
return takeT128(get128_3());
}
@method_id(129)
fun test129(setNull: bool) {
var t: (int?, int?) = (getNullableInt(), getNullableInt());
var r1 = (t, t == null, t != null);
t = (setNull ? null : 1, setNull ? null : 2);
var r2 = (t, t == null, t != null);
return (r1, r2);
}
@method_id(130)
fun test130(setNull: bool) {
var os: (int, (int, int)?) = (1, setNull ? null : (2, 3));
return os;
}
fun getEmptyNullableTensor(getNull: bool): ()? {
return getNull ? null : ();
}
@method_id(131)
fun test131() {
var nonNullEmptyT = getEmptyNullableTensor(false);
var nullEmptyT = getEmptyNullableTensor(true);
var emptyT = nonNullEmptyT!;
__expect_type(emptyT, "()");
var doubleNulls1 = (null, null) as (()?, ()?);
var doubleNulls2 = ((), ()) as (()?, ()?);
var doubleNulls3 = ((), ()) as (()?, ()?)?;
var stillEmpty = ((), ());
return (nonNullEmptyT, 777, nullEmptyT, 777, emptyT, 777, nullEmptyT!, 777, doubleNulls1, doubleNulls2, 777, doubleNulls3, 777, stillEmpty);
}
@method_id(132)
fun test132() {
var doubleNulls: (()?, ()?) = (getEmptyNullableTensor(true), getEmptyNullableTensor(false));
var result = ((null as ()?) == null, (() as ()?) == null, doubleNulls.0 == null, doubleNulls.1 == null);
var aln1: int? = (doubleNulls.1 = null);
var aln2: null = (doubleNulls.1 = null);
return (result, 777, aln1, aln2, doubleNulls.1 == null, doubleNulls);
}
fun getNormalNullableTensorWidth1(vLess100: int?): ([int?], ())? {
if (vLess100 != null && vLess100! >= 100) {
return null;
}
return ([vLess100], ()); // such a nullable tensor can store NULL in the same slot
}
fun getTrickyNullableTensorWidth1(vLess100: int?): (int?, ())? {
if (vLess100 != null && vLess100! >= 100) {
return null;
}
return (vLess100, ()); // such a nullable tensor requires an extra stack slot for null presence
}
fun getEvenTrickierNullableWidth1(vLess100: int?): ((), (int?, ()), ())? {
if (vLess100 != null && vLess100! >= 100) {
return null;
}
return ((), (vLess100, ()), ());
}
@method_id(135)
fun test135() {
var n1 = getNormalNullableTensorWidth1(10); // ([10], ())
var n2 = getNormalNullableTensorWidth1(null); // ([null], ())
var n3 = getNormalNullableTensorWidth1(100); // null
var t1 = getTrickyNullableTensorWidth1(10); // (10, ())
var t2 = getTrickyNullableTensorWidth1(null); // (null, ())
var t3 = getTrickyNullableTensorWidth1(100); // null
var e1 = getEvenTrickierNullableWidth1(10); // ((), (10, ()), ())
var e2 = getEvenTrickierNullableWidth1(null); // ((), (null, (), ())
var e3 = getEvenTrickierNullableWidth1(100); // null
return (n1, n2, n3, 777, t1, t2, t3, 777, e1, e2, e3, 777,
n1 == null, n2 == null, n3 == null, t1 == null, t2 == null, t3 == null, e1 == null, e2 == null, e3 == null, 777,
t1!.0 == null, t2!.0 == null, e1!.1.0 == null, e1!.1.1 == null, e2!.1.0 == null, e2!.1.1 == null);
}
fun main(){}
/**
@testcase | 101 | | 1 2 -1
@testcase | 102 | | 1 2 -1 (null) (null) 0
@testcase | 103 | 1 2 | 3 3 0 1 2 -1
@testcase | 104 | | 1 2 -1 (null) (null) 0
@testcase | 105 | | (null) (null) (null) 0 1 2 3 -1
@testcase | 106 | | 1 2 -1
@testcase | 107 | | 0 0 -1 0 0 -1
@testcase | 108 | 5 6 | 7 8 10 11 -1 (null) (null) 0
@testcase | 109 | | 0 0 -1 0 -1 0 0 -1 -1
@testcase | 110 | | 3 4 (null) (null) 0 6 7 -1
@testcase | 111 | | 50 30 70 90 100
@testcase | 112 | | 12 22 -1
@testcase | 113 | | -1
@testcase | 114 | | (null) (null) (null) 0 (null) (null) (null) 0
@testcase | 115 | | 2 3 7 (null) (null) 0 5 0 -1 0
@testcase | 116 | -1 | (null) (null) 0 (null) (null) 0
@testcase | 116 | 0 | 1 2 -1 1 2 -1
@testcase | 117 | | (null) (null) 0 1 3
@testcase | 118 | 5 | 5 10 -1
@testcase | 118 | null | (null) (null) 0
@testcase | 119 | | (null) (null) 0 (null) (null) 0 1 2 -1 100
@testcase | 120 | -1 | (null) (null) 0
@testcase | 120 | 0 | 1 2 -1
@testcase | 121 | | [ 1 [ 3 4 ] ]
@testcase | 122 | 0 | [ 1 [ 3 4 ] 4 (null) ]
@testcase | 122 | -1 | [ 1 (null) 4 (null) ]
@testcase | 123 | | 1 3 4 -1
@testcase | 124 | 0 | 1 3 4 -1 4 (null) (null) 0 -1
@testcase | 124 | -1 | 1 (null) (null) 0 4 (null) (null) 0 -1
@testcase | 125 | | 3
@testcase | 126 | | 1 (null) (null) 0 2
@testcase | 127 | 1 | 1 (null) (null) 0 2
@testcase | 127 | 2 | 1 2 3 -1 4
@testcase | 127 | 3 | 1 (null) (null) 0 5
@testcase | 128 | 1 | 1 (null) (null) 0 2 -1
@testcase | 128 | 2 | (null) (null) (null) (null) (null) 0
@testcase | 128 | 3 | 1 2 3 -1 4 -1
@testcase | 129 | 0 | 5 5 0 -1 1 2 0 -1
@testcase | 129 | -1 | 5 5 0 -1 (null) (null) 0 -1
@testcase | 130 | 0 | 1 2 3 -1
@testcase | 130 | -1 | 1 (null) (null) 0
@testcase | 131 | | -1 777 0 777 777 777 0 0 -1 -1 777 -1 -1 -1 777
@testcase | 132 | | -1 0 -1 0 777 (null) (null) -1 0 0
@testcase | 135 | | [ 10 ] [ (null) ] (null) 777 10 -1 (null) -1 (null) 0 777 10 -1 (null) -1 (null) 0 777 0 0 -1 0 0 -1 0 0 -1 777 0 -1 0 0 -1 0
@fif_codegen
"""
isTensorNull PROC:<{
// t.0 t.1 t.NNFlag
2 1 BLKDROP2 // t.NNFlag
0 EQINT // '3
}>
"""
@fif_codegen
"""
test113 PROC:<{
//
1 PUSHINT // '2=1
PUSHNULL // '2=1 '3
PAIR // t
1 INDEX // '5
PUSHNULL // '5 '6
0 PUSHINT // '5 '6 '7=0
isTensorNull CALLDICT // '8
}>
"""
*/

View file

@ -0,0 +1,109 @@
fun getNullable4(): int? { return 4; }
fun getNullableIntNull(): int? asm "PUSHNULL";
fun eqInt(x: int) { return x; }
fun eq<T>(x: T) { return x; }
fun unwrap<T>(x: T?): T { return x!; }
fun intOr0(x: int?): int { return null == x ? 0 : x!; }
@method_id(101)
fun test101(x: int) {
var re = x == 0 ? null : 100;
return re == null ? re : 200 + getNullable4()!;
}
@method_id(102)
fun test102(a: int) {
try {
throw (123, a > 10 ? null : a);
return 0;
} catch (excno, arg) {
var i = arg as int?;
return excno + (i != null ? i!!!!! : -100);
}
}
@method_id(103)
fun test103(x: int?): (bool, bool, int) {
var x_gt_0 = x != null && eqInt(x!) > 0;
var x_lt_0 = x != null && eq(x)! < 0;
if (x == null) {
return (x_gt_0, x_lt_0, 0);
}
return (x_gt_0, x_lt_0, x!);
}
@method_id(104)
fun test104(x: int?) {
var x2 = eq(x = 10);
var ab = (x2, getNullableIntNull());
return (unwrap(ab.0) + (ab.1 == null ? -100 : ab.1!), ab.1);
}
@method_id(105)
fun test105() {
var xy: (int?, int?) = (5, null);
var ab = [1 ? [xy.0, xy.1] : null];
ab.0!.0 = intOr0(ab.0!.0);
ab.0!.1 = intOr0(ab.0!.1);
return ab.0!.0! + ab.0!.1!;
}
global gTup106: tuple?;
global gInt106: int?;
@method_id(106)
fun test106() {
gInt106 = 0;
gInt106! += 5;
var int106: int? = 0;
var gTup106 = createEmptyTuple();
gTup106!.tuplePush(createEmptyTuple());
(gTup106!.0 as tuple?)!.tuplePush(0 as int?);
tuplePush(mutate gTup106!, gInt106);
tuplePush(mutate gTup106!.0, int106! += 1);
return (gTup106 == null, null != gTup106, gTup106, gTup106!.0 as tuple?);
}
@method_id(107)
fun test107() {
var b: builder? = beginCell();
b!.storeInt(1, 32).storeInt(2, 32);
b = b!.storeInt(3, 32);
storeInt(mutate b!, 4, 32);
(b! as builder).storeInt(5, 32);
return b!.getBuilderBitsCount();
}
@method_id(108)
fun test108() {
var (a, b: cell?, c) = (1, beginCell().endCell(), 3);
b = null;
return a + (b == null ? 0 : b!.beginParse().loadInt(32)) + c;
}
@method_id(109)
fun test109() {
var a = getNullable4();
var b = getNullable4();
return ([a, b] = [3, 4], a, b);
}
fun main(x: int?, y: int?) {
}
/**
@testcase | 101 | 0 | (null)
@testcase | 101 | -1 | 204
@testcase | 102 | 5 | 128
@testcase | 102 | 15 | 23
@testcase | 103 | 10 | -1 0 10
@testcase | 104 | 8 | -90 (null)
@testcase | 105 | | 5
@testcase | 106 | | 0 -1 [ [ 0 1 ] 5 ] [ 0 1 ]
@testcase | 107 | | 160
@testcase | 108 | | 4
@testcase | 109 | | [ 3 4 ] 3 4
*/

View file

@ -27,8 +27,8 @@ fun test1(): int {
var demo_var: int = demo_10;
var demo_slice: int = demo_20;
if (demo_var > 0) {
var demo_var: tuple = null;
var demo_slice: tuple = null;
var demo_var: tuple? = null;
var demo_slice: tuple? = null;
}
return demo_var + demo_slice;
}

View file

@ -138,6 +138,43 @@ fun testIndexedAccessApply() {
return functions2.0(functions1.1(b)).loadInt(32);
}
fun getNullable4(): int? { return 4; }
fun myBeginCell(): builder? asm "NEWC";
@method_id(108)
fun testCallingNotNull() {
var n4: () -> int? = getNullable4;
var creator: (() -> builder?)? = myBeginCell;
var end2: [int, (builder -> cell)?] = [0, endCell];
var c: cell = end2.1!((creator!()!)!.storeInt(getNullable4()!, 32));
return c.beginParse().loadInt(32);
}
fun sumOfTensorIfNotNull(t: (int, int)?) {
if (t == null) { return 0; }
return t!.0 + t!.1;
}
@method_id(109)
fun testTypeTransitionOfVarCall() {
var summer = sumOfTensorIfNotNull;
var hh1 = [1, null];
var tt1 = (3, 4);
return (summer(null), summer((1,2)), summer(hh1.1), summer(tt1));
}
fun makeTensor(x1: int, x2: int, x3: int, x4: int, x5: int) {
return (x1, x2, x3, x4, x5);
}
fun eq<T>(x: T): T { return x; }
@method_id(110)
fun testVarsModificationInsideVarCall(x: int) {
var cb = makeTensor;
return x > 3 ? cb(x, x += 5, eq(x *= x), x, eq(x)) : null;
}
fun main() {}
/**
@ -148,4 +185,8 @@ fun main() {}
@testcase | 105 | | 1
@testcase | 106 | | 1 1 [ 2 ] [ 2 ]
@testcase | 107 | | 65537
@testcase | 108 | | 4
@testcase | 109 | | 0 3 0 7
@testcase | 110 | 5 | 5 10 100 100 100 -1
@testcase | 110 | 0 | (null) (null) (null) (null) (null) 0
*/