diff --git a/crypto/smartcont/tolk-stdlib/common.tolk b/crypto/smartcont/tolk-stdlib/common.tolk index 5311ec2f..ba1e6c14 100644 --- a/crypto/smartcont/tolk-stdlib/common.tolk +++ b/crypto/smartcont/tolk-stdlib/common.tolk @@ -139,7 +139,7 @@ fun getMyOriginalBalance(): int /// `int` — balance in nanotoncoins; /// `cell` — a dictionary with 32-bit keys representing the balance of "extra currencies". @pure -fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell] +fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell?] asm "BALANCE"; /// Returns the logical time of the current transaction. @@ -154,7 +154,7 @@ fun getCurrentBlockLogicalTime(): int /// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. @pure -fun getBlockchainConfigParam(x: int): cell +fun getBlockchainConfigParam(x: int): cell? asm "CONFIGOPTPARAM"; /// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. @@ -291,7 +291,7 @@ fun calculateSliceSizeStrict(s: slice, maxCells: int): (int, int, int) /// otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. /// If [c] is a `null` instead of a cell, returns zero. @pure -fun getCellDepth(c: cell): int +fun getCellDepth(c: cell?): int asm "CDEPTH"; /// Returns the depth of `slice` [s]. @@ -417,12 +417,12 @@ fun getLastBits(self: slice, len: int): slice /// Loads a dictionary (TL HashMapE structure, represented as TVM cell) from a slice. /// Returns `null` if `nothing` constructor is used. @pure -fun loadDict(mutate self: slice): cell +fun loadDict(mutate self: slice): cell? asm( -> 1 0) "LDDICT"; /// Preloads a dictionary (cell) from a slice. @pure -fun preloadDict(self: slice): cell +fun preloadDict(self: slice): cell? asm "PLDDICT"; /// Loads a dictionary as [loadDict], but returns only the remainder of the slice. @@ -433,12 +433,12 @@ fun skipDict(mutate self: slice): self /// Loads (Maybe ^Cell) from a slice. /// In other words, loads 1 bit: if it's true, loads the first ref, otherwise returns `null`. @pure -fun loadMaybeRef(mutate self: slice): cell +fun loadMaybeRef(mutate self: slice): cell? asm( -> 1 0) "LDOPTREF"; /// Preloads (Maybe ^Cell) from a slice. @pure -fun preloadMaybeRef(self: slice): cell +fun preloadMaybeRef(self: slice): cell? asm "PLDOPTREF"; /// Loads (Maybe ^Cell), but returns only the remainder of the slice. @@ -497,13 +497,13 @@ fun storeBool(mutate self: builder, x: bool): self /// Stores dictionary (represented by TVM `cell` or `null`) into a builder. /// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. @pure -fun storeDict(mutate self: builder, c: cell): self +fun storeDict(mutate self: builder, c: cell?): self asm(c self) "STDICT"; /// Stores (Maybe ^Cell) into a builder. /// In other words, if cell is `null`, store '0' bit; otherwise, store '1' and a ref to [c]. @pure -fun storeMaybeRef(mutate self: builder, c: cell): self +fun storeMaybeRef(mutate self: builder, c: cell?): self asm(c self) "STOPTREF"; /// Concatenates two builders. @@ -661,7 +661,7 @@ fun reserveToncoinsOnBalance(nanoTonCoins: int, reserveMode: int): void /// Similar to [reserveToncoinsOnBalance], but also accepts a dictionary extraAmount (represented by a cell or null) /// with extra currencies. In this way currencies other than Toncoin can be reserved. -fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell, reserveMode: int): void +fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell?, reserveMode: int): void asm "RAWRESERVEX"; diff --git a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk index 0cb17841..af8b6bd7 100644 --- a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk +++ b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk @@ -14,17 +14,18 @@ fun createEmptyList(): tuple /// Adds an element to the beginning of lisp-style list. /// Note, that it does not mutate the list: instead, it returns a new one (it's a lisp pattern). @pure -fun listPrepend(head: X, tail: tuple): tuple +fun listPrepend(head: X, tail: tuple?): tuple asm "CONS"; /// Extracts the head and the tail of lisp-style list. @pure -fun listSplit(list: tuple): (X, tuple) +fun listSplit(list: tuple): (X, tuple?) asm "UNCONS"; /// Extracts the tail and the head of lisp-style list. +/// After extracting the last element, tuple is assigned to null. @pure -fun listNext(mutate self: tuple): X +fun listNext(mutate self: tuple?): X asm( -> 1 0) "UNCONS"; /// Returns the head of lisp-style list. @@ -34,5 +35,5 @@ fun listGetHead(list: tuple): X /// Returns the tail of lisp-style list. @pure -fun listGetTail(list: tuple): tuple +fun listGetTail(list: tuple): tuple? asm "CDR"; diff --git a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk index 5c436239..4b9d5c81 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk @@ -9,288 +9,289 @@ tolk 0.8 - uDict* - dicts with unsigned integer keys - sDict* - dicts with arbitrary slice keys When accessing a dict element, you should not only provide a key, but provide keyLen, - since for optimization, for optimization, key length is not stored in the dictionary itself. + since for optimization, key length is not stored in the dictionary itself. + Every dictionary object (`self` parameter) can be null. TVM NULL is essentially "empty dictionary". */ /// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL @pure -fun createEmptyDict(): cell +fun createEmptyDict(): cell? asm "NEWDICT"; /// Checks whether a dictionary is empty. @pure -fun dictIsEmpty(self: cell): bool +fun dictIsEmpty(self: cell?): bool asm "DICTEMPTY"; @pure -fun iDictGet(self: cell, keyLen: int, key: int): (slice, bool) +fun iDictGet(self: cell?, keyLen: int, key: int): (slice?, bool) asm(key self keyLen) "DICTIGET" "NULLSWAPIFNOT"; @pure -fun uDictGet(self: cell, keyLen: int, key: int): (slice, bool) +fun uDictGet(self: cell?, keyLen: int, key: int): (slice?, bool) asm(key self keyLen) "DICTUGET" "NULLSWAPIFNOT"; @pure -fun sDictGet(self: cell, keyLen: int, key: slice): (slice, bool) +fun sDictGet(self: cell?, keyLen: int, key: slice): (slice?, bool) asm(key self keyLen) "DICTGET" "NULLSWAPIFNOT"; @pure -fun iDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void +fun iDictSet(mutate self: cell?, keyLen: int, key: int, value: slice): void asm(value key self keyLen) "DICTISET"; @pure -fun uDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void +fun uDictSet(mutate self: cell?, keyLen: int, key: int, value: slice): void asm(value key self keyLen) "DICTUSET"; @pure -fun sDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): void +fun sDictSet(mutate self: cell?, keyLen: int, key: slice, value: slice): void asm(value key self keyLen) "DICTSET"; @pure -fun iDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void +fun iDictSetRef(mutate self: cell?, keyLen: int, key: int, value: cell): void asm(value key self keyLen) "DICTISETREF"; @pure -fun uDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void +fun uDictSetRef(mutate self: cell?, keyLen: int, key: int, value: cell): void asm(value key self keyLen) "DICTUSETREF"; @pure -fun sDictSetRef(mutate self: cell, keyLen: int, key: slice, value: cell): void +fun sDictSetRef(mutate self: cell?, keyLen: int, key: slice, value: cell): void asm(value key self keyLen) "DICTSETREF"; @pure -fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool +fun iDictSetIfNotExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool asm(value key self keyLen) "DICTIADD"; @pure -fun uDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool +fun uDictSetIfNotExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool asm(value key self keyLen) "DICTUADD"; @pure -fun iDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool +fun iDictSetIfExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool asm(value key self keyLen) "DICTIREPLACE"; @pure -fun uDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool +fun uDictSetIfExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool asm(value key self keyLen) "DICTUREPLACE"; @pure -fun iDictGetRef(self: cell, keyLen: int, key: int): (cell, bool) +fun iDictGetRef(self: cell?, keyLen: int, key: int): (cell?, bool) asm(key self keyLen) "DICTIGETREF" "NULLSWAPIFNOT"; @pure -fun uDictGetRef(self: cell, keyLen: int, key: int): (cell, bool) +fun uDictGetRef(self: cell?, keyLen: int, key: int): (cell?, bool) asm(key self keyLen) "DICTUGETREF" "NULLSWAPIFNOT"; @pure -fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, bool) +fun sDictGetRef(self: cell?, keyLen: int, key: slice): (cell?, bool) asm(key self keyLen) "DICTGETREF" "NULLSWAPIFNOT"; @pure -fun iDictGetRefOrNull(self: cell, keyLen: int, key: int): cell +fun iDictGetRefOrNull(self: cell?, keyLen: int, key: int): cell? asm(key self keyLen) "DICTIGETOPTREF"; @pure -fun uDictGetRefOrNull(self: cell, keyLen: int, key: int): cell +fun uDictGetRefOrNull(self: cell?, keyLen: int, key: int): cell? asm(key self keyLen) "DICTUGETOPTREF"; @pure -fun sDictGetRefOrNull(self: cell, keyLen: int, key: slice): cell +fun sDictGetRefOrNull(self: cell?, keyLen: int, key: slice): cell? asm(key self keyLen) "DICTGETOPTREF"; @pure -fun iDictDelete(mutate self: cell, keyLen: int, key: int): bool +fun iDictDelete(mutate self: cell?, keyLen: int, key: int): bool asm(key self keyLen) "DICTIDEL"; @pure -fun uDictDelete(mutate self: cell, keyLen: int, key: int): bool +fun uDictDelete(mutate self: cell?, keyLen: int, key: int): bool asm(key self keyLen) "DICTUDEL"; @pure -fun sDictDelete(mutate self: cell, keyLen: int, key: slice): bool +fun sDictDelete(mutate self: cell?, keyLen: int, key: slice): bool asm(key self keyLen) "DICTDEL"; @pure -fun iDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool) +fun iDictSetAndGet(mutate self: cell?, keyLen: int, key: int, value: slice): (slice?, bool) asm(value key self keyLen) "DICTISETGET" "NULLSWAPIFNOT"; @pure -fun uDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool) +fun uDictSetAndGet(mutate self: cell?, keyLen: int, key: int, value: slice): (slice?, bool) asm(value key self keyLen) "DICTUSETGET" "NULLSWAPIFNOT"; @pure -fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, bool) +fun sDictSetAndGet(mutate self: cell?, keyLen: int, key: slice, value: slice): (slice?, bool) asm(value key self keyLen) "DICTSETGET" "NULLSWAPIFNOT"; @pure -fun iDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell +fun iDictSetAndGetRefOrNull(mutate self: cell?, keyLen: int, key: int, value: cell): cell? asm(value key self keyLen) "DICTISETGETOPTREF"; @pure -fun uDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell +fun uDictSetAndGetRefOrNull(mutate self: cell?, keyLen: int, key: int, value: cell): cell? asm(value key self keyLen) "DICTUSETGETOPTREF"; @pure -fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool) +fun iDictDeleteAndGet(mutate self: cell?, keyLen: int, key: int): (slice?, bool) asm(key self keyLen) "DICTIDELGET" "NULLSWAPIFNOT"; @pure -fun uDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool) +fun uDictDeleteAndGet(mutate self: cell?, keyLen: int, key: int): (slice?, bool) asm(key self keyLen) "DICTUDELGET" "NULLSWAPIFNOT"; @pure -fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, bool) +fun sDictDeleteAndGet(mutate self: cell?, keyLen: int, key: slice): (slice?, bool) asm(key self keyLen) "DICTDELGET" "NULLSWAPIFNOT"; @pure -fun iDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void +fun iDictSetBuilder(mutate self: cell?, keyLen: int, key: int, value: builder): void asm(value key self keyLen) "DICTISETB"; @pure -fun uDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void +fun uDictSetBuilder(mutate self: cell?, keyLen: int, key: int, value: builder): void asm(value key self keyLen) "DICTUSETB"; @pure -fun sDictSetBuilder(mutate self: cell, keyLen: int, key: slice, value: builder): void +fun sDictSetBuilder(mutate self: cell?, keyLen: int, key: slice, value: builder): void asm(value key self keyLen) "DICTSETB"; @pure -fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool +fun iDictSetBuilderIfNotExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool asm(value key self keyLen) "DICTIADDB"; @pure -fun uDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool +fun uDictSetBuilderIfNotExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool asm(value key self keyLen) "DICTUADDB"; @pure -fun iDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool +fun iDictSetBuilderIfExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool asm(value key self keyLen) "DICTIREPLACEB"; @pure -fun uDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool +fun uDictSetBuilderIfExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool asm(value key self keyLen) "DICTUREPLACEB"; @pure -fun iDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool) +fun iDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; @pure -fun uDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool) +fun uDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; @pure -fun sDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (slice, slice, bool) +fun sDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (slice?, slice?, bool) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; @pure -fun iDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool) +fun iDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; @pure -fun uDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool) +fun uDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; @pure -fun sDictDeleteLastAndGet(mutate self: cell, keyLen: int): (slice, slice, bool) +fun sDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (slice?, slice?, bool) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; @pure -fun iDictGetFirst(self: cell, keyLen: int): (int, slice, bool) +fun iDictGetFirst(self: cell?, keyLen: int): (int?, slice?, bool) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; @pure -fun uDictGetFirst(self: cell, keyLen: int): (int, slice, bool) +fun uDictGetFirst(self: cell?, keyLen: int): (int?, slice?, bool) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; @pure -fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, bool) +fun sDictGetFirst(self: cell?, keyLen: int): (slice?, slice?, bool) asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2"; @pure -fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool) +fun iDictGetFirstAsRef(self: cell?, keyLen: int): (int?, cell?, bool) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; @pure -fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool) +fun uDictGetFirstAsRef(self: cell?, keyLen: int): (int?, cell?, bool) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; @pure -fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, bool) +fun sDictGetFirstAsRef(self: cell?, keyLen: int): (slice?, cell?, bool) asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2"; @pure -fun iDictGetLast(self: cell, keyLen: int): (int, slice, bool) +fun iDictGetLast(self: cell?, keyLen: int): (int?, slice?, bool) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; @pure -fun uDictGetLast(self: cell, keyLen: int): (int, slice, bool) +fun uDictGetLast(self: cell?, keyLen: int): (int?, slice?, bool) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; @pure -fun sDictGetLast(self: cell, keyLen: int): (slice, slice, bool) +fun sDictGetLast(self: cell?, keyLen: int): (slice?, slice?, bool) asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2"; @pure -fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool) +fun iDictGetLastAsRef(self: cell?, keyLen: int): (int?, cell?, bool) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; @pure -fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool) +fun uDictGetLastAsRef(self: cell?, keyLen: int): (int?, cell?, bool) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; @pure -fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, bool) +fun sDictGetLastAsRef(self: cell?, keyLen: int): (slice?, cell?, bool) asm (-> 1 0 2) "DICTMAXREF" "NULLSWAPIFNOT2"; @pure -fun iDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun iDictGetNext(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; @pure -fun uDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun uDictGetNext(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; @pure -fun iDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun iDictGetNextOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; @pure -fun uDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun uDictGetNextOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; @pure -fun iDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun iDictGetPrev(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; @pure -fun uDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun uDictGetPrev(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; @pure -fun iDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun iDictGetPrevOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; @pure -fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool) +fun uDictGetPrevOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool) asm(pivot self keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; @@ -299,13 +300,13 @@ fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool) */ @pure -fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, bool) +fun prefixDictGet(self: cell?, keyLen: int, key: slice): (slice, slice?, slice?, bool) asm(key self keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2"; @pure -fun prefixDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): bool +fun prefixDictSet(mutate self: cell?, keyLen: int, key: slice, value: slice): bool asm(value key self keyLen) "PFXDICTSET"; @pure -fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): bool +fun prefixDictDelete(mutate self: cell?, keyLen: int, key: slice): bool asm(key self keyLen) "PFXDICTDEL"; diff --git a/tolk-tester/tests/a10.tolk b/tolk-tester/tests/a10.tolk index 031e29c9..9d24f38d 100644 --- a/tolk-tester/tests/a10.tolk +++ b/tolk-tester/tests/a10.tolk @@ -2,7 +2,7 @@ import "@stdlib/tvm-lowlevel" fun pair_first(p: [X, Y]): X asm "FIRST"; -fun one(dummy: tuple) { +fun one(dummy: tuple?) { return 1; } diff --git a/tolk-tester/tests/assignment-tests.tolk b/tolk-tester/tests/assignment-tests.tolk index 34dd3e84..bb647652 100644 --- a/tolk-tester/tests/assignment-tests.tolk +++ b/tolk-tester/tests/assignment-tests.tolk @@ -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!; } /** diff --git a/tolk-tester/tests/c2.tolk b/tolk-tester/tests/c2.tolk index 257aba5b..bcbc6c93 100644 --- a/tolk-tester/tests/c2.tolk +++ b/tolk-tester/tests/c2.tolk @@ -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); diff --git a/tolk-tester/tests/cells-slices.tolk b/tolk-tester/tests/cells-slices.tolk index 19e2e215..772812eb 100644 --- a/tolk-tester/tests/cells-slices.tolk +++ b/tolk-tester/tests/cells-slices.tolk @@ -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); diff --git a/tolk-tester/tests/dicts-demo.tolk b/tolk-tester/tests/dicts-demo.tolk index 291bd2ea..606318cb 100644 --- a/tolk-tester/tests/dicts-demo.tolk +++ b/tolk-tester/tests/dicts-demo.tolk @@ -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() ); } diff --git a/tolk-tester/tests/generics-1.tolk b/tolk-tester/tests/generics-1.tolk index 453ec282..ca310927 100644 --- a/tolk-tester/tests/generics-1.tolk +++ b/tolk-tester/tests/generics-1.tolk @@ -49,17 +49,17 @@ fun manyEq(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, y: X) { return x + y; } +fun calcSum(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)); } diff --git a/tolk-tester/tests/imports/use-dicts.tolk b/tolk-tester/tests/imports/use-dicts.tolk index c9d5dcfe..2daaf2b1 100644 --- a/tolk-tester/tests/imports/use-dicts.tolk +++ b/tolk-tester/tests/imports/use-dicts.tolk @@ -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); diff --git a/tolk-tester/tests/indexed-access.tolk b/tolk-tester/tests/indexed-access.tolk index ab7995cf..7915536e 100644 --- a/tolk-tester/tests/indexed-access.tolk +++ b/tolk-tester/tests/indexed-access.tolk @@ -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(t: (T1, T2)): bool { + return t.0 > 0; +} + +@method_id(122) +fun test122(x: (int, int)) { + return ( + isFirstComponentGt0(x), isFirstComponentGt0((2, beginCell())), isFirstComponentGt0((0, null)), + x.isFirstComponentGt0(), (2, beginCell()).isFirstComponentGt0(), (0, null).isFirstComponentGt0() + ); +} + +@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 """ diff --git a/tolk-tester/tests/inference-tests.tolk b/tolk-tester/tests/inference-tests.tolk index 3d451581..96bf8b1a 100644 --- a/tolk-tester/tests/inference-tests.tolk +++ b/tolk-tester/tests/inference-tests.tolk @@ -18,10 +18,12 @@ fun test1(x: int, y: int) { __expect_type(random() ? x : y, "int"); __expect_type(eq(x), "int"); __expect_type(eq(x), "int"); - __expect_type(eq(null), "int"); + __expect_type(eq(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() { diff --git a/tolk-tester/tests/invalid-generics-1.tolk b/tolk-tester/tests/invalid-generics-1.tolk index c8ff7fec..0bbdeee6 100644 --- a/tolk-tester/tests/invalid-generics-1.tolk +++ b/tolk-tester/tests/invalid-generics-1.tolk @@ -6,5 +6,5 @@ fun failCantDeduceWithoutArgument() { /** @compilation_should_fail -@stderr can not deduce X for generic function `f` +@stderr too few arguments in call to `f`, expected 2, have 1 */ diff --git a/tolk-tester/tests/invalid-generics-13.tolk b/tolk-tester/tests/invalid-generics-13.tolk new file mode 100644 index 00000000..7574bde7 --- /dev/null +++ b/tolk-tester/tests/invalid-generics-13.tolk @@ -0,0 +1,11 @@ +fun calcSum(x: X, y: X) { return x + y; } + +fun cantApplyPlusOnNullable() { + return calcSum(((0 as int?)), null); +} + +/** +@compilation_should_fail +@stderr while instantiating generic function `calcSum` +@stderr can not apply operator `+` to `int?` and `int?` + */ diff --git a/tolk-tester/tests/invalid-mutate-18.tolk b/tolk-tester/tests/invalid-mutate-18.tolk new file mode 100644 index 00000000..bb8cde05 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-18.tolk @@ -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 + */ diff --git a/tolk-tester/tests/invalid-mutate-19.tolk b/tolk-tester/tests/invalid-mutate-19.tolk new file mode 100644 index 00000000..bb8cde05 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-19.tolk @@ -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 + */ diff --git a/tolk-tester/tests/invalid-mutate-20.tolk b/tolk-tester/tests/invalid-mutate-20.tolk new file mode 100644 index 00000000..f6eb2f9f --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-20.tolk @@ -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 + */ diff --git a/tolk-tester/tests/invalid-typing-14.tolk b/tolk-tester/tests/invalid-typing-14.tolk new file mode 100644 index 00000000..657ab5f4 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-14.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-typing-15.tolk b/tolk-tester/tests/invalid-typing-15.tolk new file mode 100644 index 00000000..fbcff8a2 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-15.tolk @@ -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?` + */ diff --git a/tolk-tester/tests/invalid-typing-16.tolk b/tolk-tester/tests/invalid-typing-16.tolk new file mode 100644 index 00000000..1dca7822 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-16.tolk @@ -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?` + */ diff --git a/tolk-tester/tests/invalid-typing-17.tolk b/tolk-tester/tests/invalid-typing-17.tolk new file mode 100644 index 00000000..b7302684 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-17.tolk @@ -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 + */ diff --git a/tolk-tester/tests/invalid-typing-18.tolk b/tolk-tester/tests/invalid-typing-18.tolk new file mode 100644 index 00000000..cf985add --- /dev/null +++ b/tolk-tester/tests/invalid-typing-18.tolk @@ -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 + */ diff --git a/tolk-tester/tests/logical-operators.tolk b/tolk-tester/tests/logical-operators.tolk index 29cd1d10..700f2a3c 100644 --- a/tolk-tester/tests/logical-operators.tolk +++ b/tolk-tester/tests/logical-operators.tolk @@ -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 diff --git a/tolk-tester/tests/null-keyword.tolk b/tolk-tester/tests/null-keyword.tolk index eb02b624..3ea0aaa2 100644 --- a/tolk-tester/tests/null-keyword.tolk +++ b/tolk-tester/tests/null-keyword.tolk @@ -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 }> """ */ diff --git a/tolk-tester/tests/nullable-tensors.tolk b/tolk-tester/tests/nullable-tensors.tolk new file mode 100644 index 00000000..4008482f --- /dev/null +++ b/tolk-tester/tests/nullable-tensors.tolk @@ -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(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(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(null), + isTensorNullGen(x1), isTensorNullGen(x3), + x1.isTensorNullGen(), x2.isTensorNullGen(), x3.isTensorNullGen(), null.isTensorNullGen() + ); +} + +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 + }> +""" +*/ diff --git a/tolk-tester/tests/nullable-types.tolk b/tolk-tester/tests/nullable-types.tolk new file mode 100644 index 00000000..ebabb80d --- /dev/null +++ b/tolk-tester/tests/nullable-types.tolk @@ -0,0 +1,109 @@ + +fun getNullable4(): int? { return 4; } +fun getNullableIntNull(): int? asm "PUSHNULL"; + +fun eqInt(x: int) { return x; } +fun eq(x: T) { return x; } + +fun unwrap(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 + */ diff --git a/tolk-tester/tests/use-before-declare.tolk b/tolk-tester/tests/use-before-declare.tolk index d3e6b165..2a0e0e7f 100644 --- a/tolk-tester/tests/use-before-declare.tolk +++ b/tolk-tester/tests/use-before-declare.tolk @@ -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; } diff --git a/tolk-tester/tests/var-apply.tolk b/tolk-tester/tests/var-apply.tolk index 16863560..d189430f 100644 --- a/tolk-tester/tests/var-apply.tolk +++ b/tolk-tester/tests/var-apply.tolk @@ -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(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 */ diff --git a/tolk/abscode.cpp b/tolk/abscode.cpp index b465b72b..72da0ac8 100644 --- a/tolk/abscode.cpp +++ b/tolk/abscode.cpp @@ -402,7 +402,7 @@ void CodeBlob::print(std::ostream& os, int flags) const { std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name) { std::vector ir_idx; - int stack_w = var_type->calc_width_on_stack(); + int stack_w = var_type->get_width_on_stack(); ir_idx.reserve(stack_w); if (const TypeDataTensor* t_tensor = var_type->try_as()) { for (int i = 0; i < t_tensor->size(); ++i) { @@ -410,6 +410,10 @@ std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, s std::vector nested = create_var(t_tensor->items[i], loc, std::move(sub_name)); ir_idx.insert(ir_idx.end(), nested.begin(), nested.end()); } + } else if (const TypeDataNullable* t_nullable = var_type->try_as(); t_nullable && stack_w != 1) { + std::string null_flag_name = name.empty() ? name : name + ".NNFlag"; + ir_idx = create_var(t_nullable->inner, loc, std::move(name)); + ir_idx.emplace_back(create_var(TypeDataBool::create(), loc, std::move(null_flag_name))[0]); } else if (var_type != TypeDataVoid::create()) { #ifdef TOLK_DEBUG tolk_assert(stack_w == 1); diff --git a/tolk/ast-from-tokens.cpp b/tolk/ast-from-tokens.cpp index f5855bc1..fcaa1157 100644 --- a/tolk/ast-from-tokens.cpp +++ b/tolk/ast-from-tokens.cpp @@ -111,23 +111,16 @@ static void diagnose_addition_in_bitshift(SrcLocation loc, std::string_view bits } } -// replace (a == null) and similar to isNull(a) (call of a built-in function) -static AnyExprV maybe_replace_eq_null_with_isNull_call(V v) { +// replace (a == null) and similar to ast_is_null_check(a) (special AST vertex) +static AnyExprV maybe_replace_eq_null_with_isNull_check(V v) { bool has_null = v->get_lhs()->type == ast_null_keyword || v->get_rhs()->type == ast_null_keyword; bool replace = has_null && (v->tok == tok_eq || v->tok == tok_neq); if (!replace) { return v; } - auto v_ident = createV(v->loc, "__isNull"); // built-in function - auto v_ref = createV(v->loc, v_ident, nullptr); - AnyExprV v_null = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs(); - AnyExprV v_arg = createV(v->loc, v_null, false); - AnyExprV v_isNull = createV(v->loc, v_ref, createV(v->loc, {v_arg})); - if (v->tok == tok_neq) { - v_isNull = createV(v->loc, "!", tok_logical_not, v_isNull); - } - return v_isNull; + AnyExprV v_nullable = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs(); + return createV(v->loc, v_nullable, v->tok == tok_neq); } @@ -372,16 +365,31 @@ static AnyExprV parse_expr100(Lexer& lex) { } } -// parse E(...) (left-to-right) +// parse E(...) and E! having parsed E already (left-to-right) +static AnyExprV parse_fun_call_postfix(Lexer& lex, AnyExprV lhs) { + while (true) { + if (lex.tok() == tok_oppar) { + lhs = createV(lhs->loc, lhs, parse_argument_list(lex)); + } else if (lex.tok() == tok_logical_not) { + lex.next(); + lhs = createV(lhs->loc, lhs); + } else { + break; + } + } + return lhs; +} + +// parse E(...) and E! (left-to-right) static AnyExprV parse_expr90(Lexer& lex) { AnyExprV res = parse_expr100(lex); - while (lex.tok() == tok_oppar) { - res = createV(res->loc, res, parse_argument_list(lex)); + if (lex.tok() == tok_oppar || lex.tok() == tok_logical_not) { + res = parse_fun_call_postfix(lex, res); } return res; } -// parse E.field and E.method(...) (left-to-right) +// parse E.field and E.method(...) and E.field! (left-to-right) static AnyExprV parse_expr80(Lexer& lex) { AnyExprV lhs = parse_expr90(lex); while (lex.tok() == tok_dot) { @@ -402,8 +410,8 @@ static AnyExprV parse_expr80(Lexer& lex) { lex.unexpected("method name"); } lhs = createV(loc, lhs, v_ident, v_instantiationTs); - while (lex.tok() == tok_oppar) { - lhs = createV(lex.cur_location(), lhs, parse_argument_list(lex)); + if (lex.tok() == tok_oppar || lex.tok() == tok_logical_not) { + lhs = parse_fun_call_postfix(lex, lhs); } } return lhs; @@ -491,7 +499,7 @@ static AnyExprV parse_expr15(Lexer& lex) { AnyExprV rhs = parse_expr17(lex); lhs = createV(loc, operator_name, t, lhs, rhs); if (t == tok_eq || t == tok_neq) { - lhs = maybe_replace_eq_null_with_isNull_call(lhs->as()); + lhs = maybe_replace_eq_null_with_isNull_check(lhs->as()); } } return lhs; diff --git a/tolk/ast-replacer.h b/tolk/ast-replacer.h index c8350747..5103cc92 100644 --- a/tolk/ast-replacer.h +++ b/tolk/ast-replacer.h @@ -108,6 +108,8 @@ protected: virtual AnyExprV replace(V v) { return replace_children(v); } virtual AnyExprV replace(V v) { return replace_children(v); } virtual AnyExprV replace(V v) { return replace_children(v); } + virtual AnyExprV replace(V v) { return replace_children(v); } + virtual AnyExprV replace(V v) { return replace_children(v); } // statements virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } @@ -144,6 +146,8 @@ protected: case ast_binary_operator: return replace(v->as()); case ast_ternary_operator: return replace(v->as()); case ast_cast_as_operator: return replace(v->as()); + case ast_not_null_operator: return replace(v->as()); + case ast_is_null_check: return replace(v->as()); default: throw UnexpectedASTNodeType(v, "ASTReplacerInFunctionBody::replace"); } @@ -174,20 +178,20 @@ protected: } public: - virtual bool should_visit_function(const FunctionData* fun_ref) = 0; + virtual bool should_visit_function(FunctionPtr fun_ref) = 0; - void start_replacing_in_function(const FunctionData* fun_ref, V v_function) { + void start_replacing_in_function(FunctionPtr fun_ref, V v_function) { replace(v_function->get_body()); } }; -const std::vector& get_all_not_builtin_functions(); +const std::vector& get_all_not_builtin_functions(); template void replace_ast_of_all_functions() { BodyReplacerT visitor; - for (const FunctionData* fun_ref : get_all_not_builtin_functions()) { + for (FunctionPtr fun_ref : get_all_not_builtin_functions()) { if (visitor.should_visit_function(fun_ref)) { visitor.start_replacing_in_function(fun_ref, fun_ref->ast_root->as()); } diff --git a/tolk/ast-replicator.h b/tolk/ast-replicator.h index 02198adb..16bbbeb8 100644 --- a/tolk/ast-replicator.h +++ b/tolk/ast-replicator.h @@ -121,6 +121,12 @@ protected: virtual V clone(V v) { return createV(v->loc, clone(v->get_expr()), clone(v->cast_to_type)); } + virtual V clone(V v) { + return createV(v->loc, clone(v->get_expr())); + } + virtual V clone(V v) { + return createV(v->loc, clone(v->get_expr()), v->is_negated); + } // statements @@ -200,6 +206,8 @@ protected: case ast_binary_operator: return clone(v->as()); case ast_ternary_operator: return clone(v->as()); case ast_cast_as_operator: return clone(v->as()); + case ast_not_null_operator: return clone(v->as()); + case ast_is_null_check: return clone(v->as()); default: throw UnexpectedASTNodeType(v, "ASTReplicatorFunction::clone"); } diff --git a/tolk/ast-stringifier.h b/tolk/ast-stringifier.h index 1211d63f..a7f260de 100644 --- a/tolk/ast-stringifier.h +++ b/tolk/ast-stringifier.h @@ -56,6 +56,8 @@ class ASTStringifier final : public ASTVisitor { {ast_binary_operator, "ast_binary_operator"}, {ast_ternary_operator, "ast_ternary_operator"}, {ast_cast_as_operator, "ast_cast_as_operator"}, + {ast_not_null_operator, "ast_not_null_operator"}, + {ast_is_null_check, "ast_is_null_check"}, // statements {ast_empty_statement, "ast_empty_statement"}, {ast_sequence, "ast_sequence"}, @@ -268,6 +270,8 @@ public: case ast_binary_operator: return handle_vertex(v->as()); case ast_ternary_operator: return handle_vertex(v->as()); case ast_cast_as_operator: return handle_vertex(v->as()); + case ast_not_null_operator: return handle_vertex(v->as()); + case ast_is_null_check: return handle_vertex(v->as()); // statements case ast_empty_statement: return handle_vertex(v->as()); case ast_sequence: return handle_vertex(v->as()); diff --git a/tolk/ast-visitor.h b/tolk/ast-visitor.h index a54cb13b..d697aa82 100644 --- a/tolk/ast-visitor.h +++ b/tolk/ast-visitor.h @@ -109,6 +109,8 @@ protected: virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } // statements virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } @@ -146,6 +148,8 @@ protected: case ast_binary_operator: return visit(v->as()); case ast_ternary_operator: return visit(v->as()); case ast_cast_as_operator: return visit(v->as()); + case ast_not_null_operator: return visit(v->as()); + case ast_is_null_check: return visit(v->as()); // statements case ast_empty_statement: return visit(v->as()); case ast_sequence: return visit(v->as()); @@ -167,20 +171,20 @@ protected: } public: - virtual bool should_visit_function(const FunctionData* fun_ref) = 0; + virtual bool should_visit_function(FunctionPtr fun_ref) = 0; - virtual void start_visiting_function(const FunctionData* fun_ref, V v_function) { + virtual void start_visiting_function(FunctionPtr fun_ref, V v_function) { visit(v_function->get_body()); } }; -const std::vector& get_all_not_builtin_functions(); +const std::vector& get_all_not_builtin_functions(); template void visit_ast_of_all_functions() { BodyVisitorT visitor; - for (const FunctionData* fun_ref : get_all_not_builtin_functions()) { + for (FunctionPtr fun_ref : get_all_not_builtin_functions()) { if (visitor.should_visit_function(fun_ref)) { visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); } diff --git a/tolk/ast.cpp b/tolk/ast.cpp index 092260ff..8f1aa98f 100644 --- a/tolk/ast.cpp +++ b/tolk/ast.cpp @@ -121,7 +121,7 @@ void Vertex::assign_sym(const Symbol* sym) { this->sym = sym; } -void Vertex::assign_fun_ref(const FunctionData* fun_ref) { +void Vertex::assign_fun_ref(FunctionPtr fun_ref) { this->fun_maybe = fun_ref; } @@ -129,7 +129,7 @@ void Vertex::assign_resolved_type(TypePtr cast_to_type) { this->cast_to_type = cast_to_type; } -void Vertex::assign_var_ref(const GlobalVarData* var_ref) { +void Vertex::assign_var_ref(GlobalVarPtr var_ref) { this->var_ref = var_ref; } @@ -137,7 +137,7 @@ void Vertex::assign_resolved_type(TypePtr declared_t this->declared_type = declared_type; } -void Vertex::assign_const_ref(const GlobalConstData* const_ref) { +void Vertex::assign_const_ref(GlobalConstPtr const_ref) { this->const_ref = const_ref; } @@ -149,7 +149,7 @@ void Vertex::assign_resolved_type(TypePtr substituted_t this->substituted_type = substituted_type; } -void Vertex::assign_param_ref(const LocalVarData* param_ref) { +void Vertex::assign_param_ref(LocalVarPtr param_ref) { this->param_ref = param_ref; } @@ -157,23 +157,27 @@ void Vertex::assign_resolved_type(TypePtr declared_type) { this->declared_type = declared_type; } -void Vertex::assign_fun_ref(const FunctionData* fun_ref) { +void Vertex::assign_fun_ref(FunctionPtr fun_ref) { this->fun_ref = fun_ref; } -void Vertex::assign_fun_ref(const FunctionData* fun_ref) { +void Vertex::assign_fun_ref(FunctionPtr fun_ref) { this->fun_ref = fun_ref; } -void Vertex::assign_fun_ref(const FunctionData* fun_ref) { +void Vertex::assign_fun_ref(FunctionPtr fun_ref) { this->fun_ref = fun_ref; } +void Vertex::assign_is_negated(bool is_negated) { + this->is_negated = is_negated; +} + void Vertex::assign_target(const DotTarget& target) { this->target = target; } -void Vertex::assign_fun_ref(const FunctionData* fun_ref) { +void Vertex::assign_fun_ref(FunctionPtr fun_ref) { this->fun_ref = fun_ref; } @@ -181,7 +185,7 @@ void Vertex::assign_resolved_type(TypePtr declared_ret this->declared_return_type = declared_return_type; } -void Vertex::assign_var_ref(const LocalVarData* var_ref) { +void Vertex::assign_var_ref(LocalVarPtr var_ref) { this->var_ref = var_ref; } diff --git a/tolk/ast.h b/tolk/ast.h index d2db49f8..cd410187 100644 --- a/tolk/ast.h +++ b/tolk/ast.h @@ -88,6 +88,8 @@ enum ASTNodeType { ast_binary_operator, ast_ternary_operator, ast_cast_as_operator, + ast_not_null_operator, + ast_is_null_check, // statements ast_empty_statement, ast_sequence, @@ -408,7 +410,7 @@ private: V identifier; public: - const LocalVarData* var_ref = nullptr; // filled on resolve identifiers; for `redef` points to declared above; for underscore, name is empty + LocalVarPtr var_ref = nullptr; // filled on resolve identifiers; for `redef` points to declared above; for underscore, name is empty TypePtr declared_type; // not null for `var x: int = rhs`, otherwise nullptr bool is_immutable; // declared via 'val', not 'var' bool marked_as_redef; // var (existing_var redef, new_var: int) = ... @@ -417,7 +419,7 @@ public: std::string_view get_name() const { return identifier->name; } // empty for underscore Vertex* mutate() const { return const_cast(this); } - void assign_var_ref(const LocalVarData* var_ref); + void assign_var_ref(LocalVarPtr var_ref); void assign_resolved_type(TypePtr declared_type); Vertex(SrcLocation loc, V identifier, TypePtr declared_type, bool is_immutable, bool marked_as_redef) @@ -530,12 +532,12 @@ private: public: typedef std::variant< - const FunctionData*, // for `t.tupleAt` target is `tupleAt` global function + FunctionPtr, // for `t.tupleAt` target is `tupleAt` global function int // for `t.0` target is "indexed access" 0 > DotTarget; DotTarget target = static_cast(nullptr); // filled at type inferring - bool is_target_fun_ref() const { return std::holds_alternative(target); } + bool is_target_fun_ref() const { return std::holds_alternative(target); } bool is_target_indexed_access() const { return std::holds_alternative(target); } AnyExprV get_obj() const { return child; } @@ -560,7 +562,7 @@ template<> // example: `getF()()` then callee is another func call (which type is TypeDataFunCallable) // example: `obj.method()` then callee is dot access (resolved while type inferring) struct Vertex final : ASTExprBinary { - const FunctionData* fun_maybe = nullptr; // filled while type inferring for `globalF()` / `obj.f()`; remains nullptr for `local_var()` / `getF()()` + FunctionPtr fun_maybe = nullptr; // filled while type inferring for `globalF()` / `obj.f()`; remains nullptr for `local_var()` / `getF()()` AnyExprV get_callee() const { return lhs; } bool is_dot_call() const { return lhs->type == ast_dot_access; } @@ -570,7 +572,7 @@ struct Vertex final : ASTExprBinary { auto get_arg(int i) const { return rhs->as()->get_arg(i); } Vertex* mutate() const { return const_cast(this); } - void assign_fun_ref(const FunctionData* fun_ref); + void assign_fun_ref(FunctionPtr fun_ref); Vertex(SrcLocation loc, AnyExprV lhs_f, V arguments) : ASTExprBinary(ast_function_call, loc, lhs_f, arguments) {} @@ -603,7 +605,7 @@ template<> // ast_set_assign represents assignment-and-set operation "lhs = rhs" // examples: `a += 4` / `b <<= c` struct Vertex final : ASTExprBinary { - const FunctionData* fun_ref = nullptr; // filled at type inferring, points to `_+_` built-in for += + FunctionPtr fun_ref = nullptr; // filled at type inferring, points to `_+_` built-in for += std::string_view operator_name; // without equal sign, "+" for operator += TokenType tok; // tok_set_* @@ -611,7 +613,7 @@ struct Vertex final : ASTExprBinary { AnyExprV get_rhs() const { return rhs; } Vertex* mutate() const { return const_cast(this); } - void assign_fun_ref(const FunctionData* fun_ref); + void assign_fun_ref(FunctionPtr fun_ref); Vertex(SrcLocation loc, std::string_view operator_name, TokenType tok, AnyExprV lhs, AnyExprV rhs) : ASTExprBinary(ast_set_assign, loc, lhs, rhs) @@ -622,14 +624,14 @@ template<> // ast_unary_operator is "some operator over one expression" // examples: `-1` / `~found` struct Vertex final : ASTExprUnary { - const FunctionData* fun_ref = nullptr; // filled at type inferring, points to some built-in function + FunctionPtr fun_ref = nullptr; // filled at type inferring, points to some built-in function std::string_view operator_name; TokenType tok; AnyExprV get_rhs() const { return child; } Vertex* mutate() const { return const_cast(this); } - void assign_fun_ref(const FunctionData* fun_ref); + void assign_fun_ref(FunctionPtr fun_ref); Vertex(SrcLocation loc, std::string_view operator_name, TokenType tok, AnyExprV rhs) : ASTExprUnary(ast_unary_operator, loc, rhs) @@ -641,7 +643,7 @@ template<> // examples: `a + b` / `x & true` / `(a, b) << g()` // note, that `a = b` is NOT a binary operator, it's ast_assign, also `a += b`, it's ast_set_assign struct Vertex final : ASTExprBinary { - const FunctionData* fun_ref = nullptr; // filled at type inferring, points to some built-in function + FunctionPtr fun_ref = nullptr; // filled at type inferring, points to some built-in function std::string_view operator_name; TokenType tok; @@ -649,7 +651,7 @@ struct Vertex final : ASTExprBinary { AnyExprV get_rhs() const { return rhs; } Vertex* mutate() const { return const_cast(this); } - void assign_fun_ref(const FunctionData* fun_ref); + void assign_fun_ref(FunctionPtr fun_ref); Vertex(SrcLocation loc, std::string_view operator_name, TokenType tok, AnyExprV lhs, AnyExprV rhs) : ASTExprBinary(ast_binary_operator, loc, lhs, rhs) @@ -684,6 +686,32 @@ struct Vertex final : ASTExprUnary { , cast_to_type(cast_to_type) {} }; +template<> +// ast_not_null_operator is non-null assertion: like TypeScript ! or Kotlin !! +// examples: `nullableInt!` / `getNullableBuilder()!` +struct Vertex final : ASTExprUnary { + AnyExprV get_expr() const { return child; } + + Vertex(SrcLocation loc, AnyExprV expr) + : ASTExprUnary(ast_not_null_operator, loc, expr) {} +}; + +template<> +// ast_is_null_check is an artificial vertex for "expr == null" / "expr != null" / same but null on the left +// it's created instead of a general binary expression to emphasize its purpose +struct Vertex final : ASTExprUnary { + bool is_negated; + + AnyExprV get_expr() const { return child; } + + Vertex* mutate() const { return const_cast(this); } + void assign_is_negated(bool is_negated); + + Vertex(SrcLocation loc, AnyExprV expr, bool is_negated) + : ASTExprUnary(ast_is_null_check, loc, expr) + , is_negated(is_negated) {} +}; + // // --------------------------------------------------------- @@ -892,7 +920,7 @@ template<> // ast_parameter is a parameter of a function in its declaration // example: `fun f(a: int, mutate b: slice)` has 2 parameters struct Vertex final : ASTOtherLeaf { - const LocalVarData* param_ref = nullptr; // filled on resolve identifiers + LocalVarPtr param_ref = nullptr; // filled on resolve identifiers std::string_view param_name; TypePtr declared_type; bool declared_as_mutate; // declared as `mutate param_name` @@ -900,7 +928,7 @@ struct Vertex final : ASTOtherLeaf { bool is_underscore() const { return param_name.empty(); } Vertex* mutate() const { return const_cast(this); } - void assign_param_ref(const LocalVarData* param_ref); + void assign_param_ref(LocalVarPtr param_ref); void assign_resolved_type(TypePtr declared_type); Vertex(SrcLocation loc, std::string_view param_name, TypePtr declared_type, bool declared_as_mutate) @@ -951,7 +979,7 @@ struct Vertex final : ASTOtherVararg { auto get_param(int i) const { return children.at(1)->as()->get_param(i); } AnyV get_body() const { return children.at(2); } // ast_sequence / ast_asm_body - const FunctionData* fun_ref = nullptr; // filled after register + FunctionPtr fun_ref = nullptr; // filled after register TypePtr declared_return_type; // filled at ast parsing; if unspecified (nullptr), means "auto infer" V genericsT_list; // for non-generics it's nullptr td::RefInt256 method_id; // specified via @method_id annotation @@ -962,7 +990,7 @@ struct Vertex final : ASTOtherVararg { bool is_builtin_function() const { return children.at(2)->type == ast_empty_statement; } Vertex* mutate() const { return const_cast(this); } - void assign_fun_ref(const FunctionData* fun_ref); + void assign_fun_ref(FunctionPtr fun_ref); void assign_resolved_type(TypePtr declared_return_type); Vertex(SrcLocation loc, V name_identifier, V parameters, AnyV body, TypePtr declared_return_type, V genericsT_list, td::RefInt256 method_id, int flags) @@ -975,13 +1003,13 @@ template<> // example: `global g: int;` // note, that globals don't have default values, since there is no single "entrypoint" for a contract struct Vertex final : ASTOtherVararg { - const GlobalVarData* var_ref = nullptr; // filled after register + GlobalVarPtr var_ref = nullptr; // filled after register TypePtr declared_type; // filled always, typing globals is mandatory auto get_identifier() const { return children.at(0)->as(); } Vertex* mutate() const { return const_cast(this); } - void assign_var_ref(const GlobalVarData* var_ref); + void assign_var_ref(GlobalVarPtr var_ref); void assign_resolved_type(TypePtr declared_type); Vertex(SrcLocation loc, V name_identifier, TypePtr declared_type) @@ -993,14 +1021,14 @@ template<> // ast_constant_declaration is declaring a global constant, outside a function // example: `const op = 0x123;` struct Vertex final : ASTOtherVararg { - const GlobalConstData* const_ref = nullptr; // filled after register + GlobalConstPtr const_ref = nullptr; // filled after register TypePtr declared_type; // not null for `const op: int = ...` auto get_identifier() const { return children.at(0)->as(); } AnyExprV get_init_value() const { return child_as_expr(1); } Vertex* mutate() const { return const_cast(this); } - void assign_const_ref(const GlobalConstData* const_ref); + void assign_const_ref(GlobalConstPtr const_ref); void assign_resolved_type(TypePtr declared_type); Vertex(SrcLocation loc, V name_identifier, TypePtr declared_type, AnyExprV init_value) diff --git a/tolk/codegen.cpp b/tolk/codegen.cpp index ad61b8a5..5b2c50cc 100644 --- a/tolk/codegen.cpp +++ b/tolk/codegen.cpp @@ -348,9 +348,9 @@ bool Op::generate_code_step(Stack& stack) { std::vector args0, res; int w_arg = 0; for (const LocalVarData& param : f_sym->parameters) { - w_arg += param.declared_type->calc_width_on_stack(); + w_arg += param.declared_type->get_width_on_stack(); } - int w_ret = f_sym->inferred_return_type->calc_width_on_stack(); + int w_ret = f_sym->inferred_return_type->get_width_on_stack(); tolk_assert(w_ret >= 0 && w_arg >= 0); for (int i = 0; i < w_ret; i++) { res.emplace_back(0); diff --git a/tolk/compiler-state.cpp b/tolk/compiler-state.cpp index 66fad844..95a7e6a5 100644 --- a/tolk/compiler-state.cpp +++ b/tolk/compiler-state.cpp @@ -66,7 +66,7 @@ void CompilerSettings::parse_experimental_options_cmd_arg(const std::string& cmd } } -const std::vector& get_all_not_builtin_functions() { +const std::vector& get_all_not_builtin_functions() { return G.all_functions; } diff --git a/tolk/compiler-state.h b/tolk/compiler-state.h index d33eec81..1d166a3a 100644 --- a/tolk/compiler-state.h +++ b/tolk/compiler-state.h @@ -95,10 +95,10 @@ struct CompilerState { GlobalSymbolTable symtable; PersistentHeapAllocator persistent_mem; - std::vector all_functions; // all user-defined (not built-in) functions, with generic instantiations - std::vector all_get_methods; - std::vector all_global_vars; - std::vector all_constants; + std::vector all_functions; // all user-defined (not built-in) functions, with generic instantiations + std::vector all_get_methods; + std::vector all_global_vars; + std::vector all_constants; AllRegisteredSrcFiles all_src_files; bool is_verbosity(int gt_eq) const { return settings.verbosity >= gt_eq; } diff --git a/tolk/constant-evaluator.cpp b/tolk/constant-evaluator.cpp index 9ad27381..4d11b922 100644 --- a/tolk/constant-evaluator.cpp +++ b/tolk/constant-evaluator.cpp @@ -255,7 +255,7 @@ struct ConstantEvaluator { if (!sym) { v->error("undefined symbol `" + static_cast(name) + "`"); } - const GlobalConstData* const_ref = sym->try_as(); + GlobalConstPtr const_ref = sym->try_as(); if (!const_ref) { v->error("symbol `" + static_cast(name) + "` is not a constant"); } diff --git a/tolk/fwd-declarations.h b/tolk/fwd-declarations.h index e3599f36..8d3b24a8 100644 --- a/tolk/fwd-declarations.h +++ b/tolk/fwd-declarations.h @@ -32,6 +32,11 @@ struct FunctionData; struct GlobalVarData; struct GlobalConstData; +using LocalVarPtr = const LocalVarData*; +using FunctionPtr = const FunctionData*; +using GlobalVarPtr = const GlobalVarData*; +using GlobalConstPtr = const GlobalConstData*; + class TypeData; using TypePtr = const TypeData*; diff --git a/tolk/generics-helpers.cpp b/tolk/generics-helpers.cpp index 7a2dd83f..86cdf82b 100644 --- a/tolk/generics-helpers.cpp +++ b/tolk/generics-helpers.cpp @@ -37,12 +37,38 @@ static TypePtr replace_genericT_with_deduced(TypePtr orig, const GenericsDeclara if (idx == -1) { throw Fatal("can not replace generic " + asT->nameT); } + if (substitutionTs[idx] == nullptr) { + throw GenericDeduceError("can not deduce " + asT->nameT); + } return substitutionTs[idx]; } return child; }); } +GenericSubstitutionsDeduceForCall::GenericSubstitutionsDeduceForCall(FunctionPtr fun_ref) + : fun_ref(fun_ref) { + substitutionTs.resize(fun_ref->genericTs->size()); // filled with nullptr (nothing deduced) +} + +void GenericSubstitutionsDeduceForCall::provide_deducedT(const std::string& nameT, TypePtr deduced) { + if (deduced == TypeDataNullLiteral::create() || deduced->has_unknown_inside()) { + return; // just 'null' doesn't give sensible info + } + + int idx = fun_ref->genericTs->find_nameT(nameT); + if (substitutionTs[idx] == nullptr) { + substitutionTs[idx] = deduced; + } else if (substitutionTs[idx] != deduced) { + throw GenericDeduceError(nameT + " is both " + substitutionTs[idx]->as_human_readable() + " and " + deduced->as_human_readable()); + } +} + +void GenericSubstitutionsDeduceForCall::provide_manually_specified(std::vector&& substitutionTs) { + this->substitutionTs = std::move(substitutionTs); + this->manually_specified = true; +} + // purpose: having `f(value: T)` and call `f(5)`, deduce T = int // generally, there may be many generic Ts for declaration, and many arguments // for every argument, `consider_next_condition()` is called @@ -51,71 +77,67 @@ static TypePtr replace_genericT_with_deduced(TypePtr orig, const GenericsDeclara // - next condition: param_type = `T1`, arg_type = `int`, deduce T1 = int // - next condition: param_type = `(T1, T2)`, arg_type = `(int, slice)`, deduce T1 = int, T2 = slice // for call `f(6, cs, (8, cs))` T1 will be both `slice` and `int`, fired an error -class GenericSubstitutionsDeduceForFunctionCall final { - const FunctionData* fun_ref; - std::vector substitutions; - - void provideDeducedT(const std::string& nameT, TypePtr deduced) { - if (deduced == TypeDataNullLiteral::create() || deduced->has_unknown_inside()) { - return; // just 'null' doesn't give sensible info +void GenericSubstitutionsDeduceForCall::consider_next_condition(TypePtr param_type, TypePtr arg_type) { + if (const auto* asT = param_type->try_as()) { + // `(arg: T)` called as `f([1, 2])` => T is [int, int] + provide_deducedT(asT->nameT, arg_type); + } else if (const auto* p_nullable = param_type->try_as()) { + // `arg: T?` called as `f(nullableInt)` => T is int + if (const auto* a_nullable = arg_type->try_as()) { + consider_next_condition(p_nullable->inner, a_nullable->inner); } - - int idx = fun_ref->genericTs->find_nameT(nameT); - if (substitutions[idx] == nullptr) { - substitutions[idx] = deduced; - } else if (substitutions[idx] != deduced) { - throw std::runtime_error(nameT + " is both " + substitutions[idx]->as_human_readable() + " and " + deduced->as_human_readable()); + // `arg: T?` called as `f(int)` => T is int + else { + consider_next_condition(p_nullable->inner, arg_type); } - } - -public: - explicit GenericSubstitutionsDeduceForFunctionCall(const FunctionData* fun_ref) - : fun_ref(fun_ref) { - substitutions.resize(fun_ref->genericTs->size()); // filled with nullptr (nothing deduced) - } - - void consider_next_condition(TypePtr param_type, TypePtr arg_type) { - if (const auto* asT = param_type->try_as()) { - // `(arg: T)` called as `f([1, 2])` => T is [int, int] - provideDeducedT(asT->nameT, arg_type); - } else if (const auto* p_tensor = param_type->try_as()) { - // `arg: (int, T)` called as `f((5, cs))` => T is slice - if (const auto* a_tensor = arg_type->try_as(); a_tensor && a_tensor->size() == p_tensor->size()) { - for (int i = 0; i < a_tensor->size(); ++i) { - consider_next_condition(p_tensor->items[i], a_tensor->items[i]); - } - } - } else if (const auto* p_tuple = param_type->try_as()) { - // `arg: [int, T]` called as `f([5, cs])` => T is slice - if (const auto* a_tuple = arg_type->try_as(); a_tuple && a_tuple->size() == p_tuple->size()) { - for (int i = 0; i < a_tuple->size(); ++i) { - consider_next_condition(p_tuple->items[i], a_tuple->items[i]); - } - } - } else if (const auto* p_callable = param_type->try_as()) { - // `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int - if (const auto* a_callable = arg_type->try_as(); a_callable && a_callable->params_size() == p_callable->params_size()) { - for (int i = 0; i < a_callable->params_size(); ++i) { - consider_next_condition(p_callable->params_types[i], a_callable->params_types[i]); - } - consider_next_condition(p_callable->return_type, a_callable->return_type); + } else if (const auto* p_tensor = param_type->try_as()) { + // `arg: (int, T)` called as `f((5, cs))` => T is slice + if (const auto* a_tensor = arg_type->try_as(); a_tensor && a_tensor->size() == p_tensor->size()) { + for (int i = 0; i < a_tensor->size(); ++i) { + consider_next_condition(p_tensor->items[i], a_tensor->items[i]); } } - } - - int get_first_not_deduced_idx() const { - for (int i = 0; i < static_cast(substitutions.size()); ++i) { - if (substitutions[i] == nullptr) { - return i; + } else if (const auto* p_tuple = param_type->try_as()) { + // `arg: [int, T]` called as `f([5, cs])` => T is slice + if (const auto* a_tuple = arg_type->try_as(); a_tuple && a_tuple->size() == p_tuple->size()) { + for (int i = 0; i < a_tuple->size(); ++i) { + consider_next_condition(p_tuple->items[i], a_tuple->items[i]); } } - return -1; + } else if (const auto* p_callable = param_type->try_as()) { + // `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int + if (const auto* a_callable = arg_type->try_as(); a_callable && a_callable->params_size() == p_callable->params_size()) { + for (int i = 0; i < a_callable->params_size(); ++i) { + consider_next_condition(p_callable->params_types[i], a_callable->params_types[i]); + } + consider_next_condition(p_callable->return_type, a_callable->return_type); + } } +} - std::vector flush() { - return {std::move(substitutions)}; +TypePtr GenericSubstitutionsDeduceForCall::replace_by_manually_specified(TypePtr param_type) const { + return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs); +} + +TypePtr GenericSubstitutionsDeduceForCall::auto_deduce_from_argument(SrcLocation loc, TypePtr param_type, TypePtr arg_type) { + try { + if (!manually_specified) { + consider_next_condition(param_type, arg_type); + } + return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs); + } catch (const GenericDeduceError& ex) { + throw ParseError(loc, ex.message + " for generic function `" + fun_ref->as_human_readable() + "`; instantiate it manually with `" + fun_ref->name + "<...>()`"); } -}; +} + +int GenericSubstitutionsDeduceForCall::get_first_not_deduced_idx() const { + for (int i = 0; i < static_cast(substitutionTs.size()); ++i) { + if (substitutionTs[i] == nullptr) { + return i; + } + } + return -1; +} // clone the body of `f` replacing T everywhere with a substitution // before: `fun f(v: T) { var cp: [T] = [v]; }` @@ -175,7 +197,7 @@ int GenericsDeclaration::find_nameT(std::string_view nameT) const { // after creating a deep copy of `f` like `f`, its new and fresh body needs the previous pipeline to run // for example, all local vars need to be registered as symbols, etc. -static void run_pipeline_for_instantiated_function(const FunctionData* inst_fun_ref) { +static void run_pipeline_for_instantiated_function(FunctionPtr inst_fun_ref) { // these pipes are exactly the same as in tolk.cpp — all preceding (and including) type inferring pipeline_resolve_identifiers_and_assign_symbols(inst_fun_ref); pipeline_calculate_rvalue_lvalue(inst_fun_ref); @@ -198,34 +220,12 @@ std::string generate_instantiated_name(const std::string& orig_name, const std:: return name; } -td::Result> deduce_substitutionTs_on_generic_func_call(const FunctionData* called_fun, std::vector&& arg_types, TypePtr return_hint) { - try { - GenericSubstitutionsDeduceForFunctionCall deducing(called_fun); - for (const LocalVarData& param : called_fun->parameters) { - if (param.declared_type->has_genericT_inside() && param.param_idx < static_cast(arg_types.size())) { - deducing.consider_next_condition(param.declared_type, arg_types[param.param_idx]); - } - } - int idx = deducing.get_first_not_deduced_idx(); - if (idx != -1 && return_hint && called_fun->declared_return_type->has_genericT_inside()) { - deducing.consider_next_condition(called_fun->declared_return_type, return_hint); - idx = deducing.get_first_not_deduced_idx(); - } - if (idx != -1) { - return td::Status::Error(td::Slice{"can not deduce " + called_fun->genericTs->get_nameT(idx)}); - } - return deducing.flush(); - } catch (const std::runtime_error& ex) { - return td::Status::Error(td::Slice{ex.what()}); - } -} - -const FunctionData* instantiate_generic_function(SrcLocation loc, const FunctionData* fun_ref, const std::string& inst_name, std::vector&& substitutionTs) { +FunctionPtr instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, const std::string& inst_name, std::vector&& substitutionTs) { tolk_assert(fun_ref->genericTs); // if `f` was earlier instantiated, return it if (const auto* existing = lookup_global_symbol(inst_name)) { - const FunctionData* inst_ref = existing->try_as(); + FunctionPtr inst_ref = existing->try_as(); tolk_assert(inst_ref); return inst_ref; } diff --git a/tolk/generics-helpers.h b/tolk/generics-helpers.h index 2a304f55..893bd98c 100644 --- a/tolk/generics-helpers.h +++ b/tolk/generics-helpers.h @@ -57,8 +57,46 @@ struct GenericsInstantiation { } }; +// this class helps to deduce Ts on the fly +// purpose: having `f(value: T)` and call `f(5)`, deduce T = int +// while analyzing a call, arguments are handled one by one, by `auto_deduce_from_argument()` +// this class also handles manually specified substitutions like `f(5)` +class GenericSubstitutionsDeduceForCall { + FunctionPtr fun_ref; + std::vector substitutionTs; + bool manually_specified = false; + + void provide_deducedT(const std::string& nameT, TypePtr deduced); + void consider_next_condition(TypePtr param_type, TypePtr arg_type); + +public: + explicit GenericSubstitutionsDeduceForCall(FunctionPtr fun_ref); + + bool is_manually_specified() const { + return manually_specified; + } + + void provide_manually_specified(std::vector&& substitutionTs); + TypePtr replace_by_manually_specified(TypePtr param_type) const; + TypePtr auto_deduce_from_argument(SrcLocation loc, TypePtr param_type, TypePtr arg_type); + int get_first_not_deduced_idx() const; + + std::vector&& flush() { + return std::move(substitutionTs); + } +}; + +struct GenericDeduceError final : std::exception { + std::string message; + explicit GenericDeduceError(std::string message) + : message(std::move(message)) { } + + const char* what() const noexcept override { + return message.c_str(); + } +}; + std::string generate_instantiated_name(const std::string& orig_name, const std::vector& substitutions); -td::Result> deduce_substitutionTs_on_generic_func_call(const FunctionData* called_fun, std::vector&& arg_types, TypePtr return_hint); -const FunctionData* instantiate_generic_function(SrcLocation loc, const FunctionData* fun_ref, const std::string& inst_name, std::vector&& substitutionTs); +FunctionPtr instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, const std::string& inst_name, std::vector&& substitutionTs); } // namespace tolk diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index 867c05ec..269c5fe1 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -32,12 +32,22 @@ * So, if execution reaches this pass, the input is (almost) correct, and code generation should succeed. * (previously, there was a check for one variable modified twice like `(t.0, t.0) = rhs`, but after changing * execution order of assignment to "first lhs, then lhs", it was removed for several reasons) +* + * A noticeable property for IR generation is "target_type" used to extend/shrink stack. + * Example: `var a: (int,int)? = null`. This `null` has inferred_type "null literal", but target_type "nullable tensor", + * and when it's assigned, it's "expanded" from 1 stack slot to 3 (int + int + null flag). + * Example: `fun analyze(t: (int,int)?)` and a call `analyze((1,2))`. `(1,2)` is `(int,int)` (2 stack slots), + * and when passed to target (3 slots, one for null flag), this null flag is implicitly added (zero value). + * Example: `nullableInt!`; for `nullableInt` inferred_type is `int?`, and target_type is `int` + * (this doesn't lead to stack reorganization, but in case `nullableTensor!` does) + * (inferred_type of `nullableInt!` is `int`, and its target_type depends on its usage). + * The same mechanism will work for union types in the future. */ namespace tolk { class LValContext; -std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValContext* lval_ctx = nullptr); +std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, TypePtr target_type = nullptr, LValContext* lval_ctx = nullptr); std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValContext* lval_ctx); void process_any_statement(AnyV v, CodeBlob& code); @@ -101,8 +111,8 @@ class LValContext { // every global variable used as lvalue is registered here // example: `globalInt = 9`, implicit var is created `$tmp = 9`, and `SetGlob "globalInt" $tmp` is done after struct ModifiedGlobal { - const GlobalVarData* glob_ref; - std::vector lval_ir_idx; // typically 1, generally calc_width_on_stack() of global var (tensors) + GlobalVarPtr glob_ref; + std::vector lval_ir_idx; // typically 1, generally get_width_on_stack() of global var (tensors) // for 1-slot globals int/cell/slice, assigning to them is just SETGLOB // same for tensors, if they are fully rewritten in an expression: `gTensor = (5,6)` @@ -139,13 +149,13 @@ class LValContext { void apply(CodeBlob& code, SrcLocation loc) const { LValContext local_lval; local_lval.enter_rval_inside_lval(); - std::vector obj_ir_idx = pre_compile_expr(tensor_obj, code, &local_lval); + std::vector obj_ir_idx = pre_compile_expr(tensor_obj, code, nullptr, &local_lval); const TypeDataTensor* t_tensor = tensor_obj->inferred_type->try_as(); tolk_assert(t_tensor); - int stack_width = t_tensor->items[index_at]->calc_width_on_stack(); + int stack_width = t_tensor->items[index_at]->get_width_on_stack(); int stack_offset = 0; for (int i = 0; i < index_at; ++i) { - stack_offset += t_tensor->items[i]->calc_width_on_stack(); + stack_offset += t_tensor->items[i]->get_width_on_stack(); } std::vector field_ir_idx = {obj_ir_idx.begin() + stack_offset, obj_ir_idx.begin() + stack_offset + stack_width}; tolk_assert(field_ir_idx.size() == lval_ir_idx.size()); @@ -167,12 +177,12 @@ class LValContext { void apply(CodeBlob& code, SrcLocation loc) const { LValContext local_lval; local_lval.enter_rval_inside_lval(); - std::vector tuple_ir_idx = pre_compile_expr(tuple_obj, code, &local_lval); + std::vector tuple_ir_idx = pre_compile_expr(tuple_obj, code, nullptr, &local_lval); std::vector index_ir_idx = code.create_tmp_var(TypeDataInt::create(), loc, "(tuple-idx)"); code.emplace_back(loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at)); vars_modification_watcher.trigger_callbacks(tuple_ir_idx, loc); - const FunctionData* builtin_sym = lookup_global_symbol("tupleSetAt")->as(); + FunctionPtr builtin_sym = lookup_global_symbol("tupleSetAt")->try_as(); code.emplace_back(loc, Op::_Call, std::vector{tuple_ir_idx}, std::vector{tuple_ir_idx[0], lval_ir_idx[0], index_ir_idx[0]}, builtin_sym); local_lval.after_let(std::move(tuple_ir_idx), code, loc); } @@ -195,7 +205,7 @@ public: void exit_rval_inside_lval() { level_rval_inside_lval--; } bool is_rval_inside_lval() const { return level_rval_inside_lval > 0; } - void capture_global_modification(const GlobalVarData* glob_ref, std::vector lval_ir_idx) { + void capture_global_modification(GlobalVarPtr glob_ref, std::vector lval_ir_idx) { modifications.emplace_back(ModifiedGlobal{glob_ref, std::move(lval_ir_idx)}); } @@ -245,29 +255,39 @@ public: } }; +// given `{some_expr}!`, return some_expr +static AnyExprV unwrap_not_null_operator(AnyExprV v) { + while (auto v_notnull = v->try_as()) { + v = v_notnull->get_expr(); + } + return v; +} + // given `{some_expr}.{i}`, check it for pattern `some_var.0` / `some_var.0.1` / etc. // return some_var if satisfies (it may be a local or a global var, a tensor or a tuple) // return nullptr otherwise: `f().0` / `(v = rhs).0` / `some_var.method().0` / etc. static V calc_sink_leftmost_obj(V v) { - AnyExprV leftmost_obj = v->get_obj(); + AnyExprV leftmost_obj = unwrap_not_null_operator(v->get_obj()); while (auto v_dot = leftmost_obj->try_as()) { if (!v_dot->is_target_indexed_access()) { break; } - leftmost_obj = v_dot->get_obj(); + leftmost_obj = unwrap_not_null_operator(v_dot->get_obj()); } return leftmost_obj->type == ast_reference ? leftmost_obj->as() : nullptr; } static std::vector> pre_compile_tensor_inner(CodeBlob& code, const std::vector& args, - LValContext* lval_ctx) { + const TypeDataTensor* tensor_target_type, LValContext* lval_ctx) { const int n = static_cast(args.size()); if (n == 0) { // just `()` return {}; } + tolk_assert(!tensor_target_type || tensor_target_type->size() == n); if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments - return {pre_compile_expr(args[0], code, lval_ctx)}; + TypePtr child_target_type = tensor_target_type ? tensor_target_type->items[0] : nullptr; + return {pre_compile_expr(args[0], code, child_target_type, lval_ctx)}; } // the purpose is to handle such cases: `return (x, x += y, x)` @@ -321,7 +341,8 @@ static std::vector> pre_compile_tensor_inner(CodeBlob& co WatchingVarList watched_vars(n); for (int arg_idx = 0; arg_idx < n; ++arg_idx) { - std::vector vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, lval_ctx); + TypePtr child_target_type = tensor_target_type ? tensor_target_type->items[arg_idx] : nullptr; + std::vector vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, child_target_type, lval_ctx); watched_vars.add_and_watch_modifications(std::move(vars_of_ith_arg), code); } return watched_vars.clear_and_stop_watching(); @@ -329,7 +350,13 @@ static std::vector> pre_compile_tensor_inner(CodeBlob& co static std::vector pre_compile_tensor(CodeBlob& code, const std::vector& args, LValContext* lval_ctx = nullptr) { - std::vector> res_lists = pre_compile_tensor_inner(code, args, lval_ctx); + std::vector types_list; + types_list.reserve(args.size()); + for (AnyExprV item : args) { + types_list.push_back(item->inferred_type); + } + const TypeDataTensor* tensor_target_type = TypeDataTensor::create(std::move(types_list))->try_as(); + std::vector> res_lists = pre_compile_tensor_inner(code, args, tensor_target_type, lval_ctx); std::vector res; for (const std::vector& list : res_lists) { res.insert(res.end(), list.cbegin(), list.cend()); @@ -340,6 +367,7 @@ static std::vector pre_compile_tensor(CodeBlob& code, const std::vect static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyExprV rhs, SrcLocation loc) { // [lhs] = [rhs]; since type checking is ok, it's the same as "lhs = rhs" if (lhs->type == ast_typed_tuple && rhs->type == ast_typed_tuple) { + // note: there are no type transitions (adding nullability flag, etc.), since only 1-slot elements allowed in tuples LValContext local_lval; std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &local_lval); vars_modification_watcher.trigger_callbacks(left, loc); @@ -355,7 +383,7 @@ static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE LValContext local_lval; std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &local_lval); vars_modification_watcher.trigger_callbacks(left, loc); - std::vector right = pre_compile_expr(rhs, code); + std::vector right = pre_compile_expr(rhs, code, nullptr); const TypeDataTypedTuple* inferred_tuple = rhs->inferred_type->try_as(); std::vector types_list = inferred_tuple->items; std::vector rvect = code.create_tmp_var(TypeDataTensor::create(std::move(types_list)), rhs->loc, "(unpack-tuple)"); @@ -365,25 +393,25 @@ static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE return right; } // small optimization: `var x = rhs` or `local_var = rhs` (90% cases), LValContext not needed actually - if (lhs->type == ast_local_var_lhs || (lhs->type == ast_reference && lhs->as()->sym->try_as())) { - std::vector left = pre_compile_expr(lhs, code); // effectively, local_var->ir_idx + if (lhs->type == ast_local_var_lhs || (lhs->type == ast_reference && lhs->as()->sym->try_as())) { + std::vector left = pre_compile_expr(lhs, code, nullptr); // effectively, local_var->ir_idx vars_modification_watcher.trigger_callbacks(left, loc); - std::vector right = pre_compile_expr(rhs, code); + std::vector right = pre_compile_expr(rhs, code, lhs->inferred_type); code.emplace_back(loc, Op::_Let, std::move(left), right); return right; } // lhs = rhs LValContext local_lval; - std::vector left = pre_compile_expr(lhs, code, &local_lval); + std::vector left = pre_compile_expr(lhs, code, nullptr, &local_lval); vars_modification_watcher.trigger_callbacks(left, loc); - std::vector right = pre_compile_expr(rhs, code); + std::vector right = pre_compile_expr(rhs, code, lhs->inferred_type); code.emplace_back(loc, Op::_Let, left, right); local_lval.after_let(std::move(left), code, loc); return right; } static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcLocation loc, - std::vector&& args_vars, const FunctionData* fun_ref, const char* debug_desc) { + std::vector&& args_vars, FunctionPtr fun_ref, const char* debug_desc) { std::vector rvect = code.create_tmp_var(ret_type, loc, debug_desc); Op& op = code.emplace_back(loc, Op::_Call, rvect, std::move(args_vars), fun_ref); if (!fun_ref->is_marked_as_pure()) { @@ -392,9 +420,161 @@ static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcL return rvect; } +// "Transition to target (runtime) type" is the following process. +// Imagine `fun analyze(t: (int,int)?)` and a call `analyze((1,2))`. +// `(1,2)` (inferred_type) is 2 stack slots, but `t` (target_type) is 3 (one for null-flag). +// So, this null flag should be implicitly added (non-zero, since a variable is not null). +// Another example: `var t: (int, int)? = null`. +// `null` (inferred_type) is 1 stack slots, but target_type is 3, we should add 2 nulls. +// Another example: `var t1 = (1, null); var t2: (int, (int,int)?) = t1;`. +// Then t1's rvect is 2 vars (1 and null), but t1's `null` should be converted to 3 stack slots (resulting in 4 total). +// The same mechanism will work for union types in the future. +// Here rvect is a list of IR vars for inferred_type, probably patched due to target_type. +GNU_ATTRIBUTE_NOINLINE +static std::vector transition_expr_to_runtime_type_impl(std::vector&& rvect, CodeBlob& code, TypePtr original_type, TypePtr target_type, SrcLocation loc) { + // pass `T` to `T` + // could occur for passing tensor `(..., T, ...)` to `(..., T, ...)` while traversing tensor's components + if (target_type == original_type) { + return rvect; + } + + int target_w = target_type->get_width_on_stack(); + const TypeDataNullable* t_nullable = target_type->try_as(); + const TypeDataNullable* o_nullable = original_type->try_as(); + + // pass `null` to `T?` + // for primitives like `int?`, no changes in rvect, null occupies the same TVM slot + // for tensors like `(int,int)?`, `null` is represented as N nulls + 1 null flag, insert N nulls + if (t_nullable && original_type == TypeDataNullLiteral::create()) { + tolk_assert(rvect.size() == 1); + if (target_w == 1 && !t_nullable->is_primitive_nullable()) { // `null` to `()?` + rvect = code.create_tmp_var(TypeDataInt::create(), loc, "(NNFlag)"); + code.emplace_back(loc, Op::_IntConst, rvect, td::make_refint(0)); + } + if (target_w > 1) { + FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as(); + rvect.reserve(target_w + 1); + for (int i = 1; i < target_w - 1; ++i) { + std::vector ith_null = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null-literal)"); + code.emplace_back(loc, Op::_Call, ith_null, std::vector{}, builtin_sym); + rvect.push_back(ith_null[0]); + } + std::vector null_flag_ir = code.create_tmp_var(TypeDataInt::create(), loc, "(NNFlag)"); + var_idx_t null_flag_ir_idx = null_flag_ir[0]; + code.emplace_back(loc, Op::_IntConst, std::move(null_flag_ir), td::make_refint(0)); + rvect.push_back(null_flag_ir_idx); + } + return rvect; + } + // pass `T` to `T?` + // for primitives like `int?`, no changes in rvect: `int` and `int?` occupy the same TVM slot (null is represented as NULL TVM value) + // for passing `(int, int)` to `(int, int)?` / `(int, null)` to `(int, (int,int)?)?`, add a null flag equals to 0 + if (t_nullable && !o_nullable) { + if (!t_nullable->is_primitive_nullable()) { + rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, original_type, t_nullable->inner, loc); + tolk_assert(target_w == static_cast(rvect.size() + 1)); + std::vector null_flag_ir = code.create_tmp_var(TypeDataInt::create(), loc, "(NNFlag)"); + var_idx_t null_flag_ir_idx = null_flag_ir[0]; + code.emplace_back(loc, Op::_IntConst, std::move(null_flag_ir), td::make_refint(-1)); + rvect.push_back(null_flag_ir_idx); + } + return rvect; + } + // pass `T1?` to `T2?` + // for example, `int8?` to `int16?` + // transition inner types, leaving nullable flag unchanged for tensors + if (t_nullable && o_nullable) { + if (target_w > 1) { + var_idx_t null_flag_ir_idx = rvect.back(); + rvect.pop_back(); + rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, o_nullable->inner, t_nullable->inner, loc); + rvect.push_back(null_flag_ir_idx); + } + return rvect; + } + // pass `T?` to `null` + if (target_type == TypeDataNullLiteral::create() && original_type->can_rhs_be_assigned(target_type)) { + tolk_assert(o_nullable || original_type == TypeDataUnknown::create()); + if (o_nullable && !o_nullable->is_primitive_nullable()) { + FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as(); + rvect = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null-literal)"); + code.emplace_back(loc, Op::_Call, rvect, std::vector{}, builtin_sym); + } + return rvect; + } + // pass `T?` to `T` + // it may occur due to operator `!` or smart cast + // for primitives like `int?`, no changes in rvect + // for passing `(int, int)?` to `(int, int)`, drop the null flag from the tail + if (!t_nullable && o_nullable) { + if (!o_nullable->is_primitive_nullable()) { + rvect.pop_back(); + rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, original_type->try_as()->inner, target_type, loc); + } + return rvect; + } + // pass `bool` to `int` + // in code, it's done via `as` operator, like `boolVar as int` + // no changes in rvect, boolVar is guaranteed to be -1 or 0 at TVM level + if (target_type == TypeDataInt::create() && original_type == TypeDataBool::create()) { + return rvect; + } + // pass something to `unknown` + // probably, it comes from `_ = rhs`, type of `_` is unknown, it's target_type of rhs + // no changes in rvect + if (target_type == TypeDataUnknown::create()) { + return rvect; + } + // pass `unknown` to something + // probably, it comes from `arg` in exception, it's inferred as `unknown` and could be cast to any value + if (original_type == TypeDataUnknown::create()) { + tolk_assert(rvect.size() == 1); + return rvect; + } + // pass tensor to tensor, e.g. `(1, null)` to `(int, slice?)` / `(1, null)` to `(int, (int,int)?)` + // every element of rhs tensor should be transitioned + if (target_type->try_as() && original_type->try_as()) { + const TypeDataTensor* target_tensor = target_type->try_as(); + const TypeDataTensor* inferred_tensor = original_type->try_as(); + tolk_assert(target_tensor->size() == inferred_tensor->size()); + tolk_assert(inferred_tensor->get_width_on_stack() == static_cast(rvect.size())); + std::vector result_rvect; + result_rvect.reserve(target_w); + int stack_offset = 0; + for (int i = 0; i < inferred_tensor->size(); ++i) { + int ith_w = inferred_tensor->items[i]->get_width_on_stack(); + std::vector rvect_i{rvect.begin() + stack_offset, rvect.begin() + stack_offset + ith_w}; + std::vector result_i = transition_expr_to_runtime_type_impl(std::move(rvect_i), code, inferred_tensor->items[i], target_tensor->items[i], loc); + result_rvect.insert(result_rvect.end(), result_i.begin(), result_i.end()); + stack_offset += ith_w; + } + return result_rvect; + } + // pass tuple to tuple, e.g. `[1, null]` to `[int, int?]` / `[1, null]` to `[int, [int?,int?]?]` + // to changes to rvect, since tuples contain only 1-slot elements + if (target_type->try_as() && original_type->try_as()) { + tolk_assert(target_type->get_width_on_stack() == original_type->get_width_on_stack()); + return rvect; + } + + throw Fatal("unhandled transition_expr_to_runtime_type_impl() combination"); +} + +// invoke the function above only if potentially needed to +// (if an expression is targeted to another type) +#ifndef TOLK_DEBUG +GNU_ATTRIBUTE_ALWAYS_INLINE +#endif +static std::vector transition_to_target_type(std::vector&& rvect, CodeBlob& code, TypePtr target_type, AnyExprV v) { + if (target_type != nullptr && target_type != v->inferred_type) { + rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, v->inferred_type, target_type, v->loc); + } + return rvect; +} + std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValContext* lval_ctx) { - if (const auto* glob_ref = sym->try_as()) { + if (GlobalVarPtr glob_ref = sym->try_as()) { // handle `globalVar = rhs` / `mutate globalVar` if (lval_ctx && !lval_ctx->is_rval_inside_lval()) { std::vector lval_ir_idx = code.create_tmp_var(glob_ref->declared_type, loc, "(lval-glob)"); @@ -410,7 +590,7 @@ std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, Co } return local_ir_idx; } - if (const auto* const_ref = sym->try_as()) { + if (GlobalConstPtr const_ref = sym->try_as()) { if (const_ref->is_int_const()) { std::vector rvect = code.create_tmp_var(TypeDataInt::create(), loc, "(glob-const)"); code.emplace_back(loc, Op::_IntConst, rvect, const_ref->as_int_const()); @@ -421,44 +601,59 @@ std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, Co return rvect; } } - if (const auto* fun_ref = sym->try_as()) { + if (FunctionPtr fun_ref = sym->try_as()) { std::vector rvect = code.create_tmp_var(fun_ref->inferred_full_type, loc, "(glob-var-fun)"); code.emplace_back(loc, Op::_GlobVar, rvect, std::vector{}, fun_ref); return rvect; } - if (const auto* var_ref = sym->try_as()) { + if (LocalVarPtr var_ref = sym->try_as()) { #ifdef TOLK_DEBUG - tolk_assert(static_cast(var_ref->ir_idx.size()) == var_ref->declared_type->calc_width_on_stack()); + tolk_assert(static_cast(var_ref->ir_idx.size()) == var_ref->declared_type->get_width_on_stack()); #endif return var_ref->ir_idx; } throw Fatal("pre_compile_symbol"); } -static std::vector process_assignment(V v, CodeBlob& code) { +static std::vector process_reference(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + std::vector rvect = pre_compile_symbol(v->loc, v->sym, code, lval_ctx); + return transition_to_target_type(std::move(rvect), code, target_type, v); +} + +static std::vector process_assignment(V v, CodeBlob& code, TypePtr target_type) { if (auto lhs_decl = v->get_lhs()->try_as()) { - return pre_compile_let(code, lhs_decl->get_expr(), v->get_rhs(), v->loc); + std::vector rvect = pre_compile_let(code, lhs_decl->get_expr(), v->get_rhs(), v->loc); + return transition_to_target_type(std::move(rvect), code, target_type, v); } else { - return pre_compile_let(code, v->get_lhs(), v->get_rhs(), v->loc); + std::vector rvect = pre_compile_let(code, v->get_lhs(), v->get_rhs(), v->loc); + // now rvect contains rhs IR vars constructed to fit lhs (for correct assignment, lhs type was target_type for rhs) + // but the type of `lhs = rhs` is RHS (see type inferring), so rvect now should fit rhs->inferred_type (= v->inferred_type) + // example: `t1 = t2 = null`, we're at `t2 = null`, earlier declared t1: `int?`, t2: `(int,int)?` + // currently "null" matches t2 (3 null slots), but type of this assignment is "plain null" (1 slot) assigned later to t1 + rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, v->get_lhs()->inferred_type, v->inferred_type, v->loc); + return transition_to_target_type(std::move(rvect), code, target_type, v); } } -static std::vector process_set_assign(V v, CodeBlob& code) { +static std::vector process_set_assign(V v, CodeBlob& code, TypePtr target_type) { // for "a += b", emulate "a = a + b" // seems not beautiful, but it works; probably, this transformation should be done at AST level in advance std::string_view calc_operator = v->operator_name; // "+" for operator += auto v_apply = createV(v->loc, calc_operator, static_cast(v->tok - 1), v->get_lhs(), v->get_rhs()); v_apply->assign_inferred_type(v->inferred_type); v_apply->assign_fun_ref(v->fun_ref); - return pre_compile_let(code, v->get_lhs(), v_apply, v->loc); + + std::vector rvect = pre_compile_let(code, v->get_lhs(), v_apply, v->loc); + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_binary_operator(V v, CodeBlob& code) { +static std::vector process_binary_operator(V v, CodeBlob& code, TypePtr target_type) { TokenType t = v->tok; if (v->fun_ref) { // almost all operators, fun_ref was assigned at type inferring std::vector args_vars = pre_compile_tensor(code, {v->get_lhs(), v->get_rhs()}); - return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(binary-op)"); + std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(binary-op)"); + return transition_to_target_type(std::move(rvect), code, target_type, v); } if (t == tok_logical_and || t == tok_logical_or) { // do the following transformations: @@ -470,43 +665,86 @@ static std::vector process_binary_operator(V v, v_1->mutate()->assign_inferred_type(TypeDataInt::create()); auto v_b_ne_0 = createV(v->loc, "!=", tok_neq, v->get_rhs(), v_0); v_b_ne_0->mutate()->assign_inferred_type(TypeDataInt::create()); - v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->as()); - std::vector cond = pre_compile_expr(v->get_lhs(), code); + v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->try_as()); + std::vector cond = pre_compile_expr(v->get_lhs(), code, nullptr); tolk_assert(cond.size() == 1); - std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)"); + std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(ternary)"); Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); - code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code)); + code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code, nullptr)); code.close_pop_cur(v->loc); code.push_set_cur(if_op.block1); - code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_0 : v_b_ne_0, code)); + code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_0 : v_b_ne_0, code, nullptr)); code.close_pop_cur(v->loc); - return rvect; + return transition_to_target_type(std::move(rvect), code, target_type, v); } throw UnexpectedASTNodeType(v, "process_binary_operator"); } -static std::vector process_unary_operator(V v, CodeBlob& code) { - std::vector args_vars = pre_compile_tensor(code, {v->get_rhs()}); - return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(unary-op)"); +static std::vector process_unary_operator(V v, CodeBlob& code, TypePtr target_type) { + std::vector rhs_vars = pre_compile_expr(v->get_rhs(), code, nullptr); + std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, std::move(rhs_vars), v->fun_ref, "(unary-op)"); + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_ternary_operator(V v, CodeBlob& code) { - std::vector cond = pre_compile_expr(v->get_cond(), code); +static std::vector process_ternary_operator(V v, CodeBlob& code, TypePtr target_type) { + std::vector cond = pre_compile_expr(v->get_cond(), code, nullptr); tolk_assert(cond.size() == 1); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)"); Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); - code.emplace_back(v->get_when_true()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_true(), code)); + code.emplace_back(v->get_when_true()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_true(), code, v->inferred_type)); code.close_pop_cur(v->get_when_true()->loc); code.push_set_cur(if_op.block1); - code.emplace_back(v->get_when_false()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_false(), code)); + code.emplace_back(v->get_when_false()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_false(), code, v->inferred_type)); code.close_pop_cur(v->get_when_false()->loc); - return rvect; + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_dot_access(V v, CodeBlob& code, LValContext* lval_ctx) { +static std::vector process_cast_as_operator(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + TypePtr child_target_type = v->cast_to_type; + std::vector rvect = pre_compile_expr(v->get_expr(), code, child_target_type, lval_ctx); + return transition_to_target_type(std::move(rvect), code, target_type, v); +} + +static std::vector process_not_null_operator(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + TypePtr child_target_type = v->get_expr()->inferred_type; + if (const auto* as_nullable = child_target_type->try_as()) { + child_target_type = as_nullable->inner; + } + std::vector rvect = pre_compile_expr(v->get_expr(), code, child_target_type, lval_ctx); + return transition_to_target_type(std::move(rvect), code, target_type, v); +} + +static std::vector process_is_null_check(V v, CodeBlob& code, TypePtr target_type) { + std::vector expr_ir_idx = pre_compile_expr(v->get_expr(), code, nullptr); + std::vector isnull_ir_idx = code.create_tmp_var(TypeDataBool::create(), v->loc, "(is-null)"); + TypePtr expr_type = v->get_expr()->inferred_type; + + if (const TypeDataNullable* t_nullable = expr_type->try_as()) { + if (!t_nullable->is_primitive_nullable()) { + std::vector zero_ir_idx = code.create_tmp_var(TypeDataInt::create(), v->loc, "(zero)"); + code.emplace_back(v->loc, Op::_IntConst, zero_ir_idx, td::make_refint(0)); + FunctionPtr eq_sym = lookup_global_symbol("_==_")->try_as(); + code.emplace_back(v->loc, Op::_Call, isnull_ir_idx, std::vector{expr_ir_idx.back(), zero_ir_idx[0]}, eq_sym); + } else { + FunctionPtr builtin_sym = lookup_global_symbol("__isNull")->try_as(); + code.emplace_back(v->loc, Op::_Call, isnull_ir_idx, expr_ir_idx, builtin_sym); + } + } else { + bool always_null = expr_type == TypeDataNullLiteral::create(); + code.emplace_back(v->loc, Op::_IntConst, isnull_ir_idx, td::make_refint(always_null ? -1 : 0)); + } + + if (v->is_negated) { + FunctionPtr not_sym = lookup_global_symbol("!b_")->try_as(); + code.emplace_back(v->loc, Op::_Call, isnull_ir_idx, std::vector{isnull_ir_idx}, not_sym); + } + return transition_to_target_type(std::move(isnull_ir_idx), code, target_type, v); +} + +static std::vector process_dot_access(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { // it's NOT a method call `t.tupleSize()` (since such cases are handled by process_function_call) // it's `t.0`, `getUser().id`, and `t.tupleSize` (as a reference, not as a call) if (!v->is_target_fun_ref()) { @@ -516,20 +754,21 @@ static std::vector process_dot_access(V v, CodeBlob& if (const auto* t_tensor = obj_type->try_as()) { // handle `tensorVar.0 = rhs` if tensors is a global, special case, then the global will be read on demand if (lval_ctx && !lval_ctx->is_rval_inside_lval()) { - if (auto sink = calc_sink_leftmost_obj(v); sink && sink->sym->try_as()) { + if (auto sink = calc_sink_leftmost_obj(v); sink && sink->sym->try_as()) { std::vector lval_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(lval-global-tensor)"); lval_ctx->capture_field_of_global_modification(v->get_obj(), index_at, lval_ir_idx); return lval_ir_idx; } } // since a tensor of N elems are N vars on a stack actually, calculate offset - std::vector lhs_vars = pre_compile_expr(v->get_obj(), code, lval_ctx); - int stack_width = t_tensor->items[index_at]->calc_width_on_stack(); + std::vector lhs_vars = pre_compile_expr(v->get_obj(), code, nullptr, lval_ctx); + int stack_width = t_tensor->items[index_at]->get_width_on_stack(); int stack_offset = 0; for (int i = 0; i < index_at; ++i) { - stack_offset += t_tensor->items[i]->calc_width_on_stack(); + stack_offset += t_tensor->items[i]->get_width_on_stack(); } - return {lhs_vars.begin() + stack_offset, lhs_vars.begin() + stack_offset + stack_width}; + std::vector rvect{lhs_vars.begin() + stack_offset, lhs_vars.begin() + stack_offset + stack_width}; + return transition_to_target_type(std::move(rvect), code, target_type, v); } // `tupleVar.0` if (obj_type->try_as() || obj_type->try_as()) { @@ -545,40 +784,52 @@ static std::vector process_dot_access(V v, CodeBlob& code.emplace_back(v->loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at)); std::vector field_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(tuple-field)"); tolk_assert(tuple_ir_idx.size() == 1 && field_ir_idx.size() == 1); // tuples contain only 1-slot values - const FunctionData* builtin_sym = lookup_global_symbol("tupleAt")->as(); + FunctionPtr builtin_sym = lookup_global_symbol("tupleAt")->try_as(); code.emplace_back(v->loc, Op::_Call, field_ir_idx, std::vector{tuple_ir_idx[0], index_ir_idx[0]}, builtin_sym); if (lval_ctx && calc_sink_leftmost_obj(v)) { // `tupleVar.0.1 = rhs`, then `tupleVar.0` is rval inside lval lval_ctx->capture_tuple_index_modification(v->get_obj(), index_at, field_ir_idx); } - return field_ir_idx; + // like tensor index, `tupleVar.1` also might be smart cast, for example we're in `if (tupleVar.1 != null)` + // but since tuple's elements are only 1-slot width (no tensors and unions), no stack transformations required + return transition_to_target_type(std::move(field_ir_idx), code, target_type, v); } tolk_assert(false); } // okay, v->target refs a function, like `obj.method`, filled at type inferring // (currently, nothing except a global function can be referenced, no object-scope methods exist) - const FunctionData* fun_ref = std::get(v->target); + FunctionPtr fun_ref = std::get(v->target); tolk_assert(fun_ref); - return pre_compile_symbol(v->loc, fun_ref, code, lval_ctx); + std::vector rvect = pre_compile_symbol(v->loc, fun_ref, code, lval_ctx); + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_function_call(V v, CodeBlob& code) { +static std::vector process_function_call(V v, CodeBlob& code, TypePtr target_type) { // v is `globalF(args)` / `globalF(args)` / `obj.method(args)` / `local_var(args)` / `getF()(args)` - const FunctionData* fun_ref = v->fun_maybe; + FunctionPtr fun_ref = v->fun_maybe; if (!fun_ref) { + // it's `local_var(args)`, treat args like a tensor: + // 1) when variables are modified like `local_var(x, x += 2, x)`, regular mechanism of watching automatically works + // 2) when `null` is passed to `(int, int)?`, or any other type transitions, it automatically works std::vector args; args.reserve(v->get_num_args()); for (int i = 0; i < v->get_num_args(); ++i) { args.push_back(v->get_arg(i)->get_expr()); } - std::vector args_vars = pre_compile_tensor(code, args); - std::vector tfunc = pre_compile_expr(v->get_callee(), code); + std::vector params_types = v->get_callee()->inferred_type->try_as()->params_types; + const TypeDataTensor* tensor_tt = TypeDataTensor::create(std::move(params_types))->try_as(); + std::vector> vars_per_arg = pre_compile_tensor_inner(code, args, tensor_tt, nullptr); + std::vector args_vars; + for (const std::vector& list : vars_per_arg) { + args_vars.insert(args_vars.end(), list.cbegin(), list.cend()); + } + std::vector tfunc = pre_compile_expr(v->get_callee(), code, nullptr); tolk_assert(tfunc.size() == 1); args_vars.push_back(tfunc[0]); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(call-ind)"); Op& op = code.emplace_back(v->loc, Op::_CallInd, rvect, std::move(args_vars)); op.set_impure_flag(); - return rvect; + return transition_to_target_type(std::move(rvect), code, target_type, v); } int delta_self = v->is_dot_call(); @@ -595,7 +846,11 @@ static std::vector process_function_call(V v, Code for (int i = 0; i < v->get_num_args(); ++i) { args.push_back(v->get_arg(i)->get_expr()); } - std::vector> vars_per_arg = pre_compile_tensor_inner(code, args, nullptr); + // the purpose of tensor_tt ("tensor target type") is to transition `null` to `(int, int)?` and so on + // the purpose of calling `pre_compile_tensor_inner` is to have 0-th IR vars to handle return self + std::vector params_types = fun_ref->inferred_full_type->try_as()->params_types; + const TypeDataTensor* tensor_tt = TypeDataTensor::create(std::move(params_types))->try_as(); + std::vector> vars_per_arg = pre_compile_tensor_inner(code, args, tensor_tt, nullptr); TypePtr op_call_type = v->inferred_type; TypePtr real_ret_type = v->inferred_type; @@ -609,7 +864,7 @@ static std::vector process_function_call(V v, Code std::vector types_list; for (int i = 0; i < delta_self + v->get_num_args(); ++i) { if (fun_ref->parameters[i].is_mutate_parameter()) { - types_list.push_back(args[i]->inferred_type); + types_list.push_back(fun_ref->parameters[i].declared_type); } } types_list.push_back(real_ret_type); @@ -630,7 +885,7 @@ static std::vector process_function_call(V v, Code AnyExprV arg_i = obj_leftmost && i == 0 ? obj_leftmost : args[i]; tolk_assert(arg_i->is_lvalue || i == 0); if (arg_i->is_lvalue) { - std::vector ith_var_idx = pre_compile_expr(arg_i, code, &local_lval); + std::vector ith_var_idx = pre_compile_expr(arg_i, code, nullptr, &local_lval); left.insert(left.end(), ith_var_idx.begin(), ith_var_idx.end()); } else { left.insert(left.end(), vars_per_arg[0].begin(), vars_per_arg[0].end()); @@ -647,36 +902,39 @@ static std::vector process_function_call(V v, Code if (obj_leftmost && fun_ref->does_return_self()) { if (obj_leftmost->is_lvalue) { // to handle if obj is global var, potentially re-assigned inside a chain - rvect_apply = pre_compile_expr(obj_leftmost, code); + rvect_apply = pre_compile_expr(obj_leftmost, code, nullptr); } else { // temporary object, not lvalue, pre_compile_expr rvect_apply = vars_per_arg[0]; } } - return rvect_apply; + return transition_to_target_type(std::move(rvect_apply), code, target_type, v); } -static std::vector process_tensor(V v, CodeBlob& code, LValContext* lval_ctx) { - return pre_compile_tensor(code, v->get_items(), lval_ctx); +static std::vector process_tensor(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + // tensor is compiled "as is", for example `(1, null)` occupies 2 slots + // and if assigned/passed to something other, like `(int, (int,int)?)`, a whole tensor is transitioned, it works + std::vector rvect = pre_compile_tensor(code, v->get_items(), lval_ctx); + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_typed_tuple(V v, CodeBlob& code, LValContext* lval_ctx) { +static std::vector process_typed_tuple(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { if (lval_ctx) { // todo some time, make "var (a, [b,c]) = (1, [2,3])" work v->error("[...] can not be used as lvalue here"); } std::vector left = code.create_tmp_var(v->inferred_type, v->loc, "(pack-tuple)"); std::vector right = pre_compile_tensor(code, v->get_items(), lval_ctx); code.emplace_back(v->loc, Op::_Tuple, left, std::move(right)); - return left; + return transition_to_target_type(std::move(left), code, target_type, v); } -static std::vector process_int_const(V v, CodeBlob& code) { +static std::vector process_int_const(V v, CodeBlob& code, TypePtr target_type) { std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(int-const)"); code.emplace_back(v->loc, Op::_IntConst, rvect, v->intval); - return rvect; + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_string_const(V v, CodeBlob& code) { +static std::vector process_string_const(V v, CodeBlob& code, TypePtr target_type) { ConstantValue value = eval_const_init_value(v); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(str-const)"); if (value.is_int()) { @@ -684,27 +942,31 @@ static std::vector process_string_const(V v, CodeBl } else { code.emplace_back(v->loc, Op::_SliceConst, rvect, value.as_slice()); } - return rvect; + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_bool_const(V v, CodeBlob& code) { - const FunctionData* builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->as(); - return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(bool-const)"); +static std::vector process_bool_const(V v, CodeBlob& code, TypePtr target_type) { + FunctionPtr builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->try_as(); + std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(bool-const)"); + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_null_keyword(V v, CodeBlob& code) { - const FunctionData* builtin_sym = lookup_global_symbol("__null")->as(); - return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(null-literal)"); +static std::vector process_null_keyword(V v, CodeBlob& code, TypePtr target_type) { + FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as(); + std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(null-literal)"); + return transition_to_target_type(std::move(rvect), code, target_type, v); } -static std::vector process_local_var(V v, CodeBlob& code) { +static std::vector process_local_var(V v, CodeBlob& code, TypePtr target_type) { if (v->marked_as_redef) { - return pre_compile_symbol(v->loc, v->var_ref, code, nullptr); + std::vector rvect = pre_compile_symbol(v->loc, v->var_ref, code, nullptr); + return transition_to_target_type(std::move(rvect), code, target_type, v); } tolk_assert(v->var_ref->ir_idx.empty()); v->var_ref->mutate()->assign_ir_idx(code.create_var(v->inferred_type, v->loc, v->var_ref->name)); - return v->var_ref->ir_idx; + std::vector rvect = v->var_ref->ir_idx; + return transition_to_target_type(std::move(rvect), code, target_type, v); } static std::vector process_local_vars_declaration(V, CodeBlob&) { @@ -718,42 +980,46 @@ static std::vector process_underscore(V v, CodeBlob& return code.create_tmp_var(v->inferred_type, v->loc, "(underscore)"); } -std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValContext* lval_ctx) { +std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { switch (v->type) { case ast_reference: - return pre_compile_symbol(v->loc, v->as()->sym, code, lval_ctx); + return process_reference(v->as(), code, target_type, lval_ctx); case ast_assign: - return process_assignment(v->as(), code); + return process_assignment(v->as(), code, target_type); case ast_set_assign: - return process_set_assign(v->as(), code); + return process_set_assign(v->as(), code, target_type); case ast_binary_operator: - return process_binary_operator(v->as(), code); + return process_binary_operator(v->as(), code, target_type); case ast_unary_operator: - return process_unary_operator(v->as(), code); + return process_unary_operator(v->as(), code, target_type); case ast_ternary_operator: - return process_ternary_operator(v->as(), code); + return process_ternary_operator(v->as(), code, target_type); case ast_cast_as_operator: - return pre_compile_expr(v->as()->get_expr(), code, lval_ctx); + return process_cast_as_operator(v->as(), code, target_type, lval_ctx); + case ast_not_null_operator: + return process_not_null_operator(v->as(), code, target_type, lval_ctx); + case ast_is_null_check: + return process_is_null_check(v->as(), code, target_type); case ast_dot_access: - return process_dot_access(v->as(), code, lval_ctx); + return process_dot_access(v->as(), code, target_type, lval_ctx); case ast_function_call: - return process_function_call(v->as(), code); + return process_function_call(v->as(), code, target_type); case ast_parenthesized_expression: - return pre_compile_expr(v->as()->get_expr(), code, lval_ctx); + return pre_compile_expr(v->as()->get_expr(), code, target_type, lval_ctx); case ast_tensor: - return process_tensor(v->as(), code, lval_ctx); + return process_tensor(v->as(), code, target_type, lval_ctx); case ast_typed_tuple: - return process_typed_tuple(v->as(), code, lval_ctx); + return process_typed_tuple(v->as(), code, target_type, lval_ctx); case ast_int_const: - return process_int_const(v->as(), code); + return process_int_const(v->as(), code, target_type); case ast_string_const: - return process_string_const(v->as(), code); + return process_string_const(v->as(), code, target_type); case ast_bool_const: - return process_bool_const(v->as(), code); + return process_bool_const(v->as(), code, target_type); case ast_null_keyword: - return process_null_keyword(v->as(), code); + return process_null_keyword(v->as(), code, target_type); case ast_local_var_lhs: - return process_local_var(v->as(), code); + return process_local_var(v->as(), code, target_type); case ast_local_vars_declaration: return process_local_vars_declaration(v->as(), code); case ast_underscore: @@ -784,14 +1050,14 @@ static void process_assert_statement(V v, CodeBlob& code) args[2]->mutate()->assign_inferred_type(TypeDataInt::create()); } - const FunctionData* builtin_sym = lookup_global_symbol("__throw_if_unless")->as(); + FunctionPtr builtin_sym = lookup_global_symbol("__throw_if_unless")->try_as(); std::vector args_vars = pre_compile_tensor(code, args); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } static void process_catch_variable(AnyExprV v_catch_var, CodeBlob& code) { if (auto v_ref = v_catch_var->try_as(); v_ref && v_ref->sym) { // not underscore - const LocalVarData* var_ref = v_ref->sym->as(); + LocalVarPtr var_ref = v_ref->sym->try_as(); tolk_assert(var_ref->ir_idx.empty()); var_ref->mutate()->assign_ir_idx(code.create_var(v_catch_var->inferred_type, v_catch_var->loc, var_ref->name)); } @@ -816,7 +1082,7 @@ static void process_try_catch_statement(V v, CodeBlob& } static void process_repeat_statement(V v, CodeBlob& code) { - std::vector tmp_vars = pre_compile_expr(v->get_cond(), code); + std::vector tmp_vars = pre_compile_expr(v->get_cond(), code, nullptr); Op& repeat_op = code.emplace_back(v->loc, Op::_Repeat, tmp_vars); code.push_set_cur(repeat_op.block0); process_any_statement(v->get_body(), code); @@ -824,7 +1090,7 @@ static void process_repeat_statement(V v, CodeBlob& code) } static void process_if_statement(V v, CodeBlob& code) { - std::vector tmp_vars = pre_compile_expr(v->get_cond(), code); + std::vector tmp_vars = pre_compile_expr(v->get_cond(), code, nullptr); Op& if_op = code.emplace_back(v->loc, Op::_If, std::move(tmp_vars)); code.push_set_cur(if_op.block0); process_any_statement(v->get_if_body(), code); @@ -869,19 +1135,21 @@ static void process_do_while_statement(V v, CodeBlob& co } until_cond->mutate()->assign_inferred_type(TypeDataInt::create()); if (auto v_bin = until_cond->try_as(); v_bin && !v_bin->fun_ref) { - v_bin->mutate()->assign_fun_ref(lookup_global_symbol("_" + static_cast(v_bin->operator_name) + "_")->as()); + v_bin->mutate()->assign_fun_ref(lookup_global_symbol("_" + static_cast(v_bin->operator_name) + "_")->try_as()); } else if (auto v_un = until_cond->try_as(); v_un && !v_un->fun_ref) { - v_un->mutate()->assign_fun_ref(lookup_global_symbol(static_cast(v_un->operator_name) + "_")->as()); + v_un->mutate()->assign_fun_ref(lookup_global_symbol(static_cast(v_un->operator_name) + "_")->try_as()); } - until_op.left = pre_compile_expr(until_cond, code); + until_op.left = pre_compile_expr(until_cond, code, nullptr); + tolk_assert(until_op.left.size() == 1); code.close_pop_cur(v->get_body()->loc_end); } static void process_while_statement(V v, CodeBlob& code) { Op& while_op = code.emplace_back(v->loc, Op::_While); code.push_set_cur(while_op.block0); - while_op.left = pre_compile_expr(v->get_cond(), code); + while_op.left = pre_compile_expr(v->get_cond(), code, nullptr); + tolk_assert(while_op.left.size() == 1); code.close_pop_cur(v->get_body()->loc); code.push_set_cur(while_op.block1); process_any_statement(v->get_body(), code); @@ -890,18 +1158,25 @@ static void process_while_statement(V v, CodeBlob& code) { static void process_throw_statement(V v, CodeBlob& code) { if (v->has_thrown_arg()) { - const FunctionData* builtin_sym = lookup_global_symbol("__throw_arg")->as(); + FunctionPtr builtin_sym = lookup_global_symbol("__throw_arg")->try_as(); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_arg(), v->get_thrown_code()}); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } else { - const FunctionData* builtin_sym = lookup_global_symbol("__throw")->as(); + FunctionPtr builtin_sym = lookup_global_symbol("__throw")->try_as(); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_code()}); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } } static void process_return_statement(V v, CodeBlob& code) { - std::vector return_vars = v->has_return_value() ? pre_compile_expr(v->get_return_value(), code) : std::vector{}; + std::vector return_vars; + if (v->has_return_value()) { + TypePtr child_target_type = code.fun_ref->inferred_return_type; + if (code.fun_ref->does_return_self()) { + child_target_type = code.fun_ref->parameters[0].declared_type; + } + return_vars = pre_compile_expr(v->get_return_value(), code, child_target_type); + } if (code.fun_ref->does_return_self()) { return_vars = {}; } @@ -953,18 +1228,18 @@ void process_any_statement(AnyV v, CodeBlob& code) { case ast_empty_statement: return; default: - pre_compile_expr(reinterpret_cast(v), code); + pre_compile_expr(reinterpret_cast(v), code, nullptr); } } -static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, FunctionBodyCode* code_body) { +static void convert_function_body_to_CodeBlob(FunctionPtr fun_ref, FunctionBodyCode* code_body) { auto v_body = fun_ref->ast_root->as()->get_body()->as(); CodeBlob* blob = new CodeBlob{fun_ref->name, fun_ref->loc, fun_ref}; std::vector rvect_import; int total_arg_width = 0; for (int i = 0; i < fun_ref->get_num_params(); ++i) { - total_arg_width += fun_ref->parameters[i].declared_type->calc_width_on_stack(); + total_arg_width += fun_ref->parameters[i].declared_type->get_width_on_stack(); } rvect_import.reserve(total_arg_width); @@ -990,9 +1265,9 @@ static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, Funct tolk_assert(vars_modification_watcher.empty()); } -static void convert_asm_body_to_AsmOp(const FunctionData* fun_ref, FunctionBodyAsm* asm_body) { +static void convert_asm_body_to_AsmOp(FunctionPtr fun_ref, FunctionBodyAsm* asm_body) { int cnt = fun_ref->get_num_params(); - int width = fun_ref->inferred_return_type->calc_width_on_stack(); + int width = fun_ref->inferred_return_type->get_width_on_stack(); std::vector asm_ops; for (AnyV v_child : fun_ref->ast_root->as()->get_body()->as()->get_asm_commands()) { std::string_view ops = v_child->as()->str_val; // \n\n... @@ -1023,15 +1298,15 @@ static void convert_asm_body_to_AsmOp(const FunctionData* fun_ref, FunctionBodyA class UpdateArgRetOrderConsideringStackWidth final { public: - static bool should_visit_function(const FunctionData* fun_ref) { + static bool should_visit_function(FunctionPtr fun_ref) { return !fun_ref->is_generic_function() && (!fun_ref->ret_order.empty() || !fun_ref->arg_order.empty()); } - static void start_visiting_function(const FunctionData* fun_ref, V v_function) { + static void start_visiting_function(FunctionPtr fun_ref, V v_function) { int total_arg_mutate_width = 0; bool has_arg_width_not_1 = false; for (const LocalVarData& param : fun_ref->parameters) { - int arg_width = param.declared_type->calc_width_on_stack(); + int arg_width = param.declared_type->get_width_on_stack(); has_arg_width_not_1 |= arg_width != 1; total_arg_mutate_width += param.is_mutate_parameter() * arg_width; } @@ -1045,7 +1320,7 @@ public: cum_arg_width.reserve(1 + fun_ref->get_num_params()); cum_arg_width.push_back(0); for (const LocalVarData& param : fun_ref->parameters) { - cum_arg_width.push_back(total_arg_width += param.declared_type->calc_width_on_stack()); + cum_arg_width.push_back(total_arg_width += param.declared_type->get_width_on_stack()); } std::vector arg_order; for (int i = 0; i < fun_ref->get_num_params(); ++i) { @@ -1062,7 +1337,7 @@ public: // ret_order is a shuffled range 0...N // validate N: a function should return value and mutated arguments onto a stack if (!fun_ref->ret_order.empty()) { - size_t expected_width = fun_ref->inferred_return_type->calc_width_on_stack() + total_arg_mutate_width; + size_t expected_width = fun_ref->inferred_return_type->get_width_on_stack() + total_arg_mutate_width; if (expected_width != fun_ref->ret_order.size()) { v_function->get_body()->error("ret_order (after ->) expected to contain " + std::to_string(expected_width) + " numbers"); } @@ -1072,11 +1347,11 @@ public: class ConvertASTToLegacyOpVisitor final { public: - static bool should_visit_function(const FunctionData* fun_ref) { + static bool should_visit_function(FunctionPtr fun_ref) { return !fun_ref->is_generic_function(); } - static void start_visiting_function(const FunctionData* fun_ref, V) { + static void start_visiting_function(FunctionPtr fun_ref, V) { tolk_assert(fun_ref->is_type_inferring_done()); if (fun_ref->is_code_function()) { convert_function_body_to_CodeBlob(fun_ref, std::get(fun_ref->body)); diff --git a/tolk/pipe-calc-rvalue-lvalue.cpp b/tolk/pipe-calc-rvalue-lvalue.cpp index 041aec89..1f374bc8 100644 --- a/tolk/pipe-calc-rvalue-lvalue.cpp +++ b/tolk/pipe-calc-rvalue-lvalue.cpp @@ -177,6 +177,18 @@ class CalculateRvalueLvalueVisitor final : public ASTVisitorFunctionBody { parent::visit(v->get_expr()); // leave lvalue state unchanged, for `mutate (t.0 as int)` both `t.0 as int` and `t.0` are lvalue } + void visit(V v) override { + mark_vertex_cur_or_rvalue(v); + parent::visit(v->get_expr()); // leave lvalue state unchanged, for `mutate x!` both `x!` and `x` are lvalue + } + + void visit(V v) override { + mark_vertex_cur_or_rvalue(v); + MarkingState saved = enter_state(MarkingState::RValue); + parent::visit(v->get_expr()); + restore_state(saved); + } + void visit(V v) override { tolk_assert(cur_state == MarkingState::LValue); mark_vertex_cur_or_rvalue(v); @@ -198,7 +210,7 @@ class CalculateRvalueLvalueVisitor final : public ASTVisitorFunctionBody { } public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } }; @@ -207,7 +219,7 @@ void pipeline_calculate_rvalue_lvalue() { visit_ast_of_all_functions(); } -void pipeline_calculate_rvalue_lvalue(const FunctionData* fun_ref) { +void pipeline_calculate_rvalue_lvalue(FunctionPtr fun_ref) { CalculateRvalueLvalueVisitor visitor; if (visitor.should_visit_function(fun_ref)) { visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); diff --git a/tolk/pipe-check-pure-impure.cpp b/tolk/pipe-check-pure-impure.cpp index 2b2e1e67..366ff160 100644 --- a/tolk/pipe-check-pure-impure.cpp +++ b/tolk/pipe-check-pure-impure.cpp @@ -34,7 +34,7 @@ static void fire_error_impure_operation_inside_pure_function(AnyV v) { class CheckImpureOperationsInPureFunctionVisitor final : public ASTVisitorFunctionBody { static void fire_if_global_var(AnyExprV v) { if (auto v_ident = v->try_as()) { - if (v_ident->sym->try_as()) { + if (v_ident->sym->try_as()) { fire_error_impure_operation_inside_pure_function(v); } } @@ -81,7 +81,7 @@ class CheckImpureOperationsInPureFunctionVisitor final : public ASTVisitorFuncti } public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { return fun_ref->is_code_function() && !fun_ref->is_generic_function() && fun_ref->is_marked_as_pure(); } }; diff --git a/tolk/pipe-check-rvalue-lvalue.cpp b/tolk/pipe-check-rvalue-lvalue.cpp index a824cc5d..3ec47a16 100644 --- a/tolk/pipe-check-rvalue-lvalue.cpp +++ b/tolk/pipe-check-rvalue-lvalue.cpp @@ -37,7 +37,7 @@ static void fire_error_cannot_be_used_as_lvalue(AnyV v, const std::string& detai } GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD -static void fire_error_modifying_immutable_variable(AnyExprV v, const LocalVarData* var_ref) { +static void fire_error_modifying_immutable_variable(AnyExprV v, LocalVarPtr var_ref) { if (var_ref->param_idx == 0 && var_ref->name == "self") { v->error("modifying `self`, which is immutable by default; probably, you want to declare `mutate self`"); } else { @@ -47,7 +47,7 @@ static void fire_error_modifying_immutable_variable(AnyExprV v, const LocalVarDa // validate a function used as rvalue, like `var cb = f` // it's not a generic function (ensured earlier at type inferring) and has some more restrictions -static void validate_function_used_as_noncall(AnyExprV v, const FunctionData* fun_ref) { +static void validate_function_used_as_noncall(AnyExprV v, FunctionPtr fun_ref) { if (!fun_ref->arg_order.empty() || !fun_ref->ret_order.empty()) { v->error("saving `" + fun_ref->name + "` into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack"); } @@ -97,6 +97,18 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody { parent::visit(v->get_expr()); } + void visit(V v) override { + // if `x!` is lvalue, then `x` is also lvalue, so check that `x` is ok + parent::visit(v->get_expr()); + } + + void visit(V v) override { + if (v->is_lvalue) { + fire_error_cannot_be_used_as_lvalue(v, v->is_negated ? "operator !=" : "operator =="); + } + parent::visit(v->get_expr()); + } + void visit(V v) override { if (v->is_lvalue) { fire_error_cannot_be_used_as_lvalue(v, "literal"); @@ -124,7 +136,7 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody { void visit(V v) override { // a reference to a method used as rvalue, like `var v = t.tupleAt` if (v->is_rvalue && v->is_target_fun_ref()) { - validate_function_used_as_noncall(v, std::get(v->target)); + validate_function_used_as_noncall(v, std::get(v->target)); } } @@ -158,17 +170,17 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody { void visit(V v) override { if (v->is_lvalue) { tolk_assert(v->sym); - if (const auto* var_ref = v->sym->try_as(); var_ref && var_ref->is_immutable()) { + if (LocalVarPtr var_ref = v->sym->try_as(); var_ref && var_ref->is_immutable()) { fire_error_modifying_immutable_variable(v, var_ref); - } else if (v->sym->try_as()) { + } else if (v->sym->try_as()) { v->error("modifying immutable constant"); - } else if (v->sym->try_as()) { + } else if (v->sym->try_as()) { v->error("function can't be used as lvalue"); } } // a reference to a function used as rvalue, like `var v = someFunction` - if (const FunctionData* fun_ref = v->sym->try_as(); fun_ref && v->is_rvalue) { + if (FunctionPtr fun_ref = v->sym->try_as(); fun_ref && v->is_rvalue) { validate_function_used_as_noncall(v, fun_ref); } } @@ -186,7 +198,7 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody { } public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } }; diff --git a/tolk/pipe-constant-folding.cpp b/tolk/pipe-constant-folding.cpp index 05d543b3..9c27029b 100644 --- a/tolk/pipe-constant-folding.cpp +++ b/tolk/pipe-constant-folding.cpp @@ -88,8 +88,19 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody { return v; } + AnyExprV replace(V v) override { + parent::replace(v); + + // `null == null` / `null != null` + if (v->get_expr()->type == ast_null_keyword) { + return create_bool_const(v->loc, !v->is_negated); + } + + return v; + } + public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } }; diff --git a/tolk/pipe-detect-unreachable.cpp b/tolk/pipe-detect-unreachable.cpp index 15824cf3..041e5581 100644 --- a/tolk/pipe-detect-unreachable.cpp +++ b/tolk/pipe-detect-unreachable.cpp @@ -111,11 +111,11 @@ class UnreachableStatementsDetectVisitor final { } public: - static bool should_visit_function(const FunctionData* fun_ref) { + static bool should_visit_function(FunctionPtr fun_ref) { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } - void start_visiting_function(const FunctionData* fun_ref, V v_function) { + void start_visiting_function(FunctionPtr fun_ref, V v_function) { bool control_flow_reaches_end = !always_returns(v_function->get_body()->as()); if (control_flow_reaches_end) { fun_ref->mutate()->assign_is_implicit_return(); @@ -128,7 +128,7 @@ void pipeline_detect_unreachable_statements() { visit_ast_of_all_functions(); } -void pipeline_detect_unreachable_statements(const FunctionData* fun_ref) { +void pipeline_detect_unreachable_statements(FunctionPtr fun_ref) { UnreachableStatementsDetectVisitor visitor; if (UnreachableStatementsDetectVisitor::should_visit_function(fun_ref)) { visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); diff --git a/tolk/pipe-find-unused-symbols.cpp b/tolk/pipe-find-unused-symbols.cpp index 29584cbf..2b7e5557 100644 --- a/tolk/pipe-find-unused-symbols.cpp +++ b/tolk/pipe-find-unused-symbols.cpp @@ -36,7 +36,7 @@ namespace tolk { static void mark_function_used_dfs(const std::unique_ptr& op); -static void mark_function_used(const FunctionData* fun_ref) { +static void mark_function_used(FunctionPtr fun_ref) { if (!fun_ref->is_code_function() || fun_ref->is_really_used()) { // already handled return; } @@ -45,7 +45,7 @@ static void mark_function_used(const FunctionData* fun_ref) { mark_function_used_dfs(std::get(fun_ref->body)->code->ops); } -static void mark_global_var_used(const GlobalVarData* glob_ref) { +static void mark_global_var_used(GlobalVarPtr glob_ref) { glob_ref->mutate()->assign_is_really_used(); } @@ -66,7 +66,7 @@ static void mark_function_used_dfs(const std::unique_ptr& op) { } void pipeline_find_unused_symbols() { - for (const FunctionData* fun_ref : G.all_functions) { + for (FunctionPtr fun_ref : G.all_functions) { if (fun_ref->is_method_id_not_empty()) { // get methods, main and other entrypoints, regular functions with @method_id mark_function_used(fun_ref); } diff --git a/tolk/pipe-generate-fif-output.cpp b/tolk/pipe-generate-fif-output.cpp index 7ef6ba7b..57f481f0 100644 --- a/tolk/pipe-generate-fif-output.cpp +++ b/tolk/pipe-generate-fif-output.cpp @@ -39,7 +39,7 @@ void FunctionBodyAsm::set_code(std::vector&& code) { } -static void generate_output_func(const FunctionData* fun_ref) { +static void generate_output_func(FunctionPtr fun_ref) { tolk_assert(fun_ref->is_code_function()); if (G.is_verbosity(2)) { std::cerr << "\n\n=========================\nfunction " << fun_ref->name << " : " << fun_ref->inferred_return_type << std::endl; @@ -119,7 +119,7 @@ void pipeline_generate_fif_output_to_std_cout() { std::cout << "PROGRAM{\n"; bool has_main_procedure = false; - for (const FunctionData* fun_ref : G.all_functions) { + for (FunctionPtr fun_ref : G.all_functions) { if (!fun_ref->does_need_codegen()) { if (G.is_verbosity(2) && fun_ref->is_code_function()) { std::cerr << fun_ref->name << ": code not generated, function does not need codegen\n"; @@ -143,7 +143,7 @@ void pipeline_generate_fif_output_to_std_cout() { throw Fatal("the contract has no entrypoint; forgot `fun onInternalMessage(...)`?"); } - for (const GlobalVarData* var_ref : G.all_global_vars) { + for (GlobalVarPtr var_ref : G.all_global_vars) { if (!var_ref->is_really_used() && G.settings.remove_unused_functions) { if (G.is_verbosity(2)) { std::cerr << var_ref->name << ": variable not generated, it's unused\n"; @@ -154,7 +154,7 @@ void pipeline_generate_fif_output_to_std_cout() { std::cout << std::string(2, ' ') << "DECLGLOBVAR " << var_ref->name << "\n"; } - for (const FunctionData* fun_ref : G.all_functions) { + for (FunctionPtr fun_ref : G.all_functions) { if (!fun_ref->does_need_codegen()) { continue; } diff --git a/tolk/pipe-infer-types-and-calls.cpp b/tolk/pipe-infer-types-and-calls.cpp index abb060a2..2f4290d6 100644 --- a/tolk/pipe-infer-types-and-calls.cpp +++ b/tolk/pipe-infer-types-and-calls.cpp @@ -63,9 +63,9 @@ namespace tolk { -static void infer_and_save_return_type_of_function(const FunctionData* fun_ref); +static void infer_and_save_return_type_of_function(FunctionPtr fun_ref); -static TypePtr get_or_infer_return_type(const FunctionData* fun_ref) { +static TypePtr get_or_infer_return_type(FunctionPtr fun_ref) { if (!fun_ref->inferred_return_type) { infer_and_save_return_type_of_function(fun_ref); } @@ -83,12 +83,7 @@ static std::string to_string(AnyExprV v_with_type) { } GNU_ATTRIBUTE_NOINLINE -static std::string to_string(const LocalVarData& var_ref) { - return "`" + var_ref.declared_type->as_human_readable() + "`"; -} - -GNU_ATTRIBUTE_NOINLINE -static std::string to_string(const FunctionData* fun_ref) { +static std::string to_string(FunctionPtr fun_ref) { return "`" + fun_ref->as_human_readable() + "`"; } @@ -96,8 +91,8 @@ static std::string to_string(const FunctionData* fun_ref) { // asm functions generally can't handle it, they expect T to be a TVM primitive // (in FunC, `forall` type just couldn't be unified with non-primitives; in Tolk, generic T is expectedly inferred) GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD -static void fire_error_calling_asm_function_with_non1_stack_width_arg(SrcLocation loc, const FunctionData* fun_ref, const std::vector& substitutions, int arg_idx) { - throw ParseError(loc, "can not call `" + fun_ref->as_human_readable() + "` with " + fun_ref->genericTs->get_nameT(arg_idx) + "=" + substitutions[arg_idx]->as_human_readable() + ", because it occupies " + std::to_string(substitutions[arg_idx]->calc_width_on_stack()) + " stack slots in TVM, not 1"); +static void fire_error_calling_asm_function_with_non1_stack_width_arg(SrcLocation loc, FunctionPtr fun_ref, const std::vector& substitutions, int arg_idx) { + throw ParseError(loc, "can not call `" + fun_ref->as_human_readable() + "` with " + fun_ref->genericTs->get_nameT(arg_idx) + "=" + substitutions[arg_idx]->as_human_readable() + ", because it occupies " + std::to_string(substitutions[arg_idx]->get_width_on_stack()) + " stack slots in TVM, not 1"); } // fire an error on `var n = null` @@ -105,7 +100,7 @@ static void fire_error_calling_asm_function_with_non1_stack_width_arg(SrcLocatio // so, it's better to see an error on assignment, that later, on `n` usage and types mismatch // (most common is situation above, but generally, `var (x,n) = xn` where xn is a tensor with 2-nd always-null, can be) GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD -static void fire_error_assign_always_null_to_variable(SrcLocation loc, const LocalVarData* assigned_var, bool is_assigned_null_literal) { +static void fire_error_assign_always_null_to_variable(SrcLocation loc, LocalVarPtr assigned_var, bool is_assigned_null_literal) { std::string var_name = assigned_var->name; throw ParseError(loc, "can not infer type of `" + var_name + "`, it's always null; specify its type with `" + var_name + ": `" + (is_assigned_null_literal ? " or use `null as `" : "")); } @@ -134,34 +129,26 @@ static void fire_error_cannot_deduce_untyped_tuple_access(SrcLocation loc, int i // fire an error on `untypedTupleVar.0` when inferred as (int,int), or `[int, (int,int)]`, or other non-1 width in a tuple GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_tuple_cannot_have_non1_stack_width_elem(SrcLocation loc, TypePtr inferred_type) { - throw ParseError(loc, "a tuple can not have " + to_string(inferred_type) + " inside, because it occupies " + std::to_string(inferred_type->calc_width_on_stack()) + " stack slots in TVM, not 1"); + throw ParseError(loc, "a tuple can not have " + to_string(inferred_type) + " inside, because it occupies " + std::to_string(inferred_type->get_width_on_stack()) + " stack slots in TVM, not 1"); } -// check correctness of called arguments counts and their type matching -static void check_function_arguments(const FunctionData* fun_ref, V v, AnyExprV lhs_of_dot_call) { - int delta_self = lhs_of_dot_call ? 1 : 0; - int n_arguments = v->size() + delta_self; - int n_parameters = fun_ref->get_num_params(); - - // Tolk doesn't have optional parameters currently, so just compare counts - if (!n_parameters && lhs_of_dot_call) { - v->error("`" + fun_ref->name + "` has no parameters and can not be called as method"); - } - if (n_parameters < n_arguments) { - v->error("too many arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); - } - if (n_arguments < n_parameters) { - v->error("too few arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); - } - - if (lhs_of_dot_call) { - if (!fun_ref->parameters[0].declared_type->can_rhs_be_assigned(lhs_of_dot_call->inferred_type)) { - lhs_of_dot_call->error("can not call method for " + to_string(fun_ref->parameters[0]) + " with object of type " + to_string(lhs_of_dot_call)); +// check type correctness of a passed argument when calling a function/method +static void check_function_argument(TypePtr param_type, bool is_mutate_param, AnyExprV ith_arg, bool is_obj_of_dot_call) { + // given `f(x: int)` and a call `f(expr)`, check that expr_type is assignable to `int` + if (!param_type->can_rhs_be_assigned(ith_arg->inferred_type)) { + if (is_obj_of_dot_call) { + ith_arg->error("can not call method for " + to_string(param_type) + " with object of type " + to_string(ith_arg)); + } else { + ith_arg->error("can not pass " + to_string(ith_arg) + " to " + to_string(param_type)); } } - for (int i = 0; i < v->size(); ++i) { - if (!fun_ref->parameters[i + delta_self].declared_type->can_rhs_be_assigned(v->get_arg(i)->inferred_type)) { - v->get_arg(i)->error("can not pass " + to_string(v->get_arg(i)) + " to " + to_string(fun_ref->parameters[i + delta_self])); + // given `f(x: mutate int?)` and a call `f(expr)`, check that `int?` is assignable to expr_type + // (for instance, can't call such a function with `f(mutate intVal)`, since f can potentially assign null to it) + if (is_mutate_param && !ith_arg->inferred_type->can_rhs_be_assigned(param_type)) { + if (is_obj_of_dot_call) { + ith_arg->error("can not call method for mutate " + to_string(param_type) + " with object of type " + to_string(ith_arg) + ", because mutation is not type compatible"); + } else { + ith_arg->error("can not pass " + to_string(ith_arg) + " to mutate " + to_string(param_type) + ", because mutation is not type compatible"); } } } @@ -189,6 +176,13 @@ class TypeInferringUnifyStrategy { return t2; } + if (t1 == TypeDataNullLiteral::create()) { + return TypeDataNullable::create(t2); + } + if (t2 == TypeDataNullLiteral::create()) { + return TypeDataNullable::create(t1); + } + const auto* tensor1 = t1->try_as(); const auto* tensor2 = t2->try_as(); if (tensor1 && tensor2 && tensor1->size() == tensor2->size()) { @@ -256,8 +250,8 @@ public: // handle __expect_type(expr, "type") call // this is used in compiler tests GNU_ATTRIBUTE_NOINLINE GNU_ATTRIBUTE_COLD -static void handle_possible_compiler_internal_call(const FunctionData* current_function, V v) { - const FunctionData* fun_ref = v->fun_maybe; +static void handle_possible_compiler_internal_call(FunctionPtr current_function, V v) { + FunctionPtr fun_ref = v->fun_maybe; tolk_assert(fun_ref && fun_ref->is_builtin_function()); static_cast(current_function); @@ -279,7 +273,7 @@ static void handle_possible_compiler_internal_call(const FunctionData* current_f * 2) easy to maintain a hint (see comments at the top of the file) */ class InferCheckTypesAndCallsAndFieldsVisitor final { - const FunctionData* current_function = nullptr; + FunctionPtr current_function = nullptr; TypeInferringUnifyStrategy return_unifier; GNU_ATTRIBUTE_ALWAYS_INLINE @@ -298,14 +292,14 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { dst->mutate()->assign_inferred_type(inferred_type); } - static void assign_inferred_type(const LocalVarData* local_var_or_param, TypePtr inferred_type) { + static void assign_inferred_type(LocalVarPtr local_var_or_param, TypePtr inferred_type) { #ifdef TOLK_DEBUG tolk_assert(inferred_type != nullptr && !inferred_type->has_unresolved_inside() && !inferred_type->has_genericT_inside()); #endif local_var_or_param->mutate()->assign_inferred_type(inferred_type); } - static void assign_inferred_type(const FunctionData* fun_ref, TypePtr inferred_return_type, TypePtr inferred_full_type) { + static void assign_inferred_type(FunctionPtr fun_ref, TypePtr inferred_return_type, TypePtr inferred_full_type) { #ifdef TOLK_DEBUG tolk_assert(inferred_return_type != nullptr && !inferred_return_type->has_unresolved_inside() && !inferred_return_type->has_genericT_inside()); #endif @@ -365,6 +359,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { return infer_ternary_operator(v->as(), hint); case ast_cast_as_operator: return infer_cast_as_operator(v->as()); + case ast_not_null_operator: + return infer_not_null_operator(v->as()); + case ast_is_null_check: + return infer_is_null_check(v->as()); case ast_parenthesized_expression: return infer_parenthesized(v->as(), hint); case ast_reference: @@ -388,14 +386,29 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } } + static TypePtr unwrap_nullable(TypePtr type) { + while (const TypeDataNullable* as_nullable = type->try_as()) { + type = as_nullable->inner; + } + return type; + } + static bool expect_integer(AnyExprV v_inferred) { return v_inferred->inferred_type == TypeDataInt::create(); } + static bool expect_integer(TypePtr inferred_type) { + return inferred_type == TypeDataInt::create(); + } + static bool expect_boolean(AnyExprV v_inferred) { return v_inferred->inferred_type == TypeDataBool::create(); } + static bool expect_boolean(TypePtr inferred_type) { + return inferred_type == TypeDataBool::create(); + } + static void infer_int_const(V v) { assign_inferred_type(v, TypeDataInt::create()); } @@ -467,7 +480,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } } else { if (rhs_type == TypeDataNullLiteral::create()) { - fire_error_assign_always_null_to_variable(err_loc->loc, lhs_var->var_ref->try_as(), corresponding_maybe_rhs && corresponding_maybe_rhs->type == ast_null_keyword); + fire_error_assign_always_null_to_variable(err_loc->loc, lhs_var->var_ref->try_as(), corresponding_maybe_rhs && corresponding_maybe_rhs->type == ast_null_keyword); } assign_inferred_type(lhs_var, rhs_type); assign_inferred_type(lhs_var->var_ref, rhs_type); @@ -520,7 +533,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // check `untypedTuple.0 = rhs_tensor` and other non-1 width elements if (auto lhs_dot = lhs->try_as()) { if (lhs_dot->is_target_indexed_access() && lhs_dot->get_obj()->inferred_type == TypeDataTuple::create()) { - if (rhs_type->calc_width_on_stack() != 1) { + if (rhs_type->get_width_on_stack() != 1) { fire_error_tuple_cannot_have_non1_stack_width_elem(err_loc->loc, rhs_type); } } @@ -563,8 +576,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { assign_inferred_type(v, lhs); if (!builtin_func.empty()) { - const FunctionData* builtin_sym = lookup_global_symbol("_" + static_cast(builtin_func) + "_")->as(); - tolk_assert(builtin_sym); + FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast(builtin_func) + "_")->try_as(); v->mutate()->assign_fun_ref(builtin_sym); } } @@ -598,8 +610,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } if (!builtin_func.empty()) { - const FunctionData* builtin_sym = lookup_global_symbol(static_cast(builtin_func) + "_")->as(); - tolk_assert(builtin_sym); + FunctionPtr builtin_sym = lookup_global_symbol(static_cast(builtin_func) + "_")->try_as(); v->mutate()->assign_fun_ref(builtin_sym); } } @@ -617,8 +628,8 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // == != can compare both integers and booleans, (int == bool) is NOT allowed case tok_eq: case tok_neq: { - bool both_int = expect_integer(lhs) && expect_integer(rhs); - bool both_bool = expect_boolean(lhs) && expect_boolean(rhs); + bool both_int = expect_integer(unwrap_nullable(lhs->inferred_type)) && expect_integer(unwrap_nullable(rhs->inferred_type)); + bool both_bool = expect_boolean(unwrap_nullable(lhs->inferred_type)) && expect_boolean(unwrap_nullable(rhs->inferred_type)); if (!both_int && !both_bool) { if (lhs->inferred_type == rhs->inferred_type) { // compare slice with slice v->error("type " + to_string(lhs) + " can not be compared with `== !=`"); @@ -674,8 +685,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } if (!builtin_func.empty()) { - const FunctionData* builtin_sym = lookup_global_symbol("_" + static_cast(builtin_func) + "_")->as(); - tolk_assert(builtin_sym); + FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast(builtin_func) + "_")->try_as(); v->mutate()->assign_fun_ref(builtin_sym); } } @@ -706,22 +716,38 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { assign_inferred_type(v, v->cast_to_type); } + void infer_is_null_check(V v) { + infer_any_expr(v->get_expr()); + assign_inferred_type(v, TypeDataBool::create()); + } + + void infer_not_null_operator(V v) { + infer_any_expr(v->get_expr()); + if (const auto* as_nullable = v->get_expr()->inferred_type->try_as()) { + // operator `!` used for `T?`, leave `T` + assign_inferred_type(v, as_nullable->inner); + } else { + // operator `!` used for non-nullable, probably a warning should be printed + assign_inferred_type(v, v->get_expr()); + } + } + void infer_parenthesized(V v, TypePtr hint) { infer_any_expr(v->get_expr(), hint); assign_inferred_type(v, v->get_expr()); } static void infer_reference(V v) { - if (const auto* var_ref = v->sym->try_as()) { + if (LocalVarPtr var_ref = v->sym->try_as()) { assign_inferred_type(v, var_ref->declared_type); - } else if (const auto* const_ref = v->sym->try_as()) { + } else if (GlobalConstPtr const_ref = v->sym->try_as()) { assign_inferred_type(v, const_ref->is_int_const() ? TypeDataInt::create() : TypeDataSlice::create()); - } else if (const auto* glob_ref = v->sym->try_as()) { + } else if (GlobalVarPtr glob_ref = v->sym->try_as()) { assign_inferred_type(v, glob_ref->declared_type); - } else if (const auto* fun_ref = v->sym->try_as()) { + } else if (FunctionPtr fun_ref = v->sym->try_as()) { // it's `globalF` / `globalF` - references to functions used as non-call V v_instantiationTs = v->get_instantiationTs(); @@ -758,7 +784,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // given `genericF` / `t.tupleFirst` (the user manually specified instantiation Ts), // validate and collect them // returns: [int, slice] / [cell] - static std::vector collect_fun_generic_substitutions_from_manually_specified(SrcLocation loc, const FunctionData* fun_ref, V instantiationT_list) { + static std::vector collect_fun_generic_substitutions_from_manually_specified(SrcLocation loc, FunctionPtr fun_ref, V instantiationT_list) { if (fun_ref->genericTs->size() != instantiationT_list->get_items().size()) { throw ParseError(loc, "wrong count of generic T: expected " + std::to_string(fun_ref->genericTs->size()) + ", got " + std::to_string(instantiationT_list->size())); } @@ -778,11 +804,11 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // example: was `t.tuplePush(2)`, read , instantiate `tuplePush` (will later fail type check) // example: was `var cb = t.tupleFirst;` (used as reference, as non-call), instantiate `tupleFirst` // returns fun_ref to instantiated function - static const FunctionData* check_and_instantiate_generic_function(SrcLocation loc, const FunctionData* fun_ref, std::vector&& substitutionTs) { + static FunctionPtr check_and_instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, std::vector&& substitutionTs) { // T for asm function must be a TVM primitive (width 1), otherwise, asm would act incorrectly if (fun_ref->is_asm_function() || fun_ref->is_builtin_function()) { for (int i = 0; i < static_cast(substitutionTs.size()); ++i) { - if (substitutionTs[i]->calc_width_on_stack() != 1) { + if (substitutionTs[i]->get_width_on_stack() != 1) { fire_error_calling_asm_function_with_non1_stack_width_arg(loc, fun_ref, substitutionTs, i); } } @@ -836,7 +862,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { if (hint == nullptr) { fire_error_cannot_deduce_untyped_tuple_access(v->loc, index_at); } - if (hint->calc_width_on_stack() != 1) { + if (hint->get_width_on_stack() != 1) { fire_error_tuple_cannot_have_non1_stack_width_elem(v->loc, hint); } item_type = hint; @@ -850,14 +876,14 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // for now, Tolk doesn't have fields and object-scoped methods; `t.tupleSize` is a global function `tupleSize` const Symbol* sym = lookup_global_symbol(field_name); - const FunctionData* fun_ref = sym ? sym->try_as() : nullptr; + FunctionPtr fun_ref = sym ? sym->try_as() : nullptr; if (!fun_ref) { v_ident->error("non-existing field `" + static_cast(field_name) + "` of type " + to_string(obj_type)); } // `t.tupleSize` is ok, `cs.tupleSize` not if (!fun_ref->parameters[0].declared_type->can_rhs_be_assigned(obj_type)) { - v_ident->error("referencing a method for " + to_string(fun_ref->parameters[0]) + " with object of type " + to_string(obj_type)); + v_ident->error("referencing a method for " + to_string(fun_ref->parameters[0].declared_type) + " with object of type " + to_string(obj_type)); } if (fun_ref->is_generic_function() && !v_instantiationTs) { @@ -886,12 +912,12 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // v is `globalF(args)` / `globalF(args)` / `obj.method(args)` / `local_var(args)` / `getF()(args)` int delta_self = 0; AnyExprV dot_obj = nullptr; - const FunctionData* fun_ref = nullptr; + FunctionPtr fun_ref = nullptr; V v_instantiationTs = nullptr; if (auto v_ref = callee->try_as()) { // `globalF()` / `globalF()` / `local_var()` / `SOME_CONST()` - fun_ref = v_ref->sym->try_as(); // not null for `globalF` + fun_ref = v_ref->sym->try_as(); // not null for `globalF` v_instantiationTs = v_ref->get_instantiationTs(); // present for `globalF()` } else if (auto v_dot = callee->try_as()) { @@ -910,7 +936,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } else { // for now, Tolk doesn't have fields and object-scoped methods; `t.tupleSize` is a global function `tupleSize` const Symbol* sym = lookup_global_symbol(field_name); - fun_ref = sym ? sym->try_as() : nullptr; + fun_ref = sym ? sym->try_as() : nullptr; if (!fun_ref) { v_dot->get_identifier()->error("non-existing method `" + static_cast(field_name) + "` of type " + to_string(dot_obj)); } @@ -921,30 +947,26 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // fun_ref remains nullptr } - // infer argument types, looking at fun_ref's parameters as hints - for (int i = 0; i < v->get_num_args(); ++i) { - TypePtr param_type = fun_ref && i < fun_ref->get_num_params() - delta_self ? fun_ref->parameters[delta_self + i].declared_type : nullptr; - auto arg_i = v->get_arg(i); - infer_any_expr(arg_i->get_expr(), param_type && !param_type->has_genericT_inside() ? param_type : nullptr); - assign_inferred_type(arg_i, arg_i->get_expr()); - } - // handle `local_var()` / `getF()()` / `5()` / `SOME_CONST()` / `obj.method()()()` / `tensorVar.0()` if (!fun_ref) { // treat callee like a usual expression, which must have "callable" inferred type infer_any_expr(callee); const TypeDataFunCallable* f_callable = callee->inferred_type->try_as(); if (!f_callable) { // `5()` / `SOME_CONST()` / `null()` - v->error("calling a non-function"); + v->error("calling a non-function " + to_string(callee->inferred_type)); } // check arguments count and their types if (v->get_num_args() != static_cast(f_callable->params_types.size())) { v->error("expected " + std::to_string(f_callable->params_types.size()) + " arguments, got " + std::to_string(v->get_arg_list()->size())); } for (int i = 0; i < v->get_num_args(); ++i) { - if (!f_callable->params_types[i]->can_rhs_be_assigned(v->get_arg(i)->inferred_type)) { - v->get_arg(i)->error("can not pass " + to_string(v->get_arg(i)) + " to " + to_string(f_callable->params_types[i])); + auto arg_i = v->get_arg(i)->get_expr(); + TypePtr param_type = f_callable->params_types[i]; + infer_any_expr(arg_i, param_type); + if (!param_type->can_rhs_be_assigned(arg_i->inferred_type)) { + arg_i->error("can not pass " + to_string(arg_i) + " to " + to_string(param_type)); } + assign_inferred_type(v->get_arg(i), arg_i); } v->mutate()->assign_fun_ref(nullptr); // no fun_ref to a global function assign_inferred_type(v, f_callable->return_type); @@ -952,30 +974,75 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } // so, we have a call `f(args)` or `obj.f(args)`, f is a global function (fun_ref) (code / asm / builtin) + // we're going to iterate over passed arguments, check type compatibility, and (if generic) infer substitutionTs + // at first, check arguments count (Tolk doesn't have optional parameters, so just compare counts) + int n_arguments = v->get_num_args() + delta_self; + int n_parameters = fun_ref->get_num_params(); + if (!n_parameters && dot_obj) { + v->error("`" + fun_ref->name + "` has no parameters and can not be called as method"); + } + if (n_parameters < n_arguments) { + v->error("too many arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); + } + if (n_arguments < n_parameters) { + v->error("too few arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); + } + + // now, for every passed argument, we need to infer its type, and check it against parameter type + // for regular functions, it's obvious + // but for generic functions, we need to infer type arguments (substitutionTs) on the fly + // (unless Ts are specified by a user like `f(args)` / `t.tupleAt()`, take them) + GenericSubstitutionsDeduceForCall* deducingTs = fun_ref->is_generic_function() ? new GenericSubstitutionsDeduceForCall(fun_ref) : nullptr; + if (deducingTs && v_instantiationTs) { + deducingTs->provide_manually_specified(collect_fun_generic_substitutions_from_manually_specified(v->loc, fun_ref, v_instantiationTs)); + } + + // loop over every argument, for `obj.method()` obj is the first one + // if genericT deducing has a conflict, ParseError is thrown + // note, that deducing Ts one by one is important to manage control flow (mutate params work like assignments) + // a corner case, e.g. `f(v1:T?, v2:T?)` and `f(null,2)` will fail on first argument, won't try the second one + if (dot_obj) { + const LocalVarData& param_0 = fun_ref->parameters[0]; + TypePtr param_type = param_0.declared_type; + if (param_type->has_genericT_inside()) { + param_type = deducingTs->auto_deduce_from_argument(dot_obj->loc, param_type, dot_obj->inferred_type); + } + check_function_argument(param_type, param_0.is_mutate_parameter(), dot_obj, true); + } + for (int i = 0; i < v->get_num_args(); ++i) { + const LocalVarData& param_i = fun_ref->parameters[delta_self + i]; + AnyExprV arg_i = v->get_arg(i)->get_expr(); + TypePtr param_type = param_i.declared_type; + if (param_type->has_genericT_inside() && deducingTs->is_manually_specified()) { // `f(a)` + param_type = deducingTs->replace_by_manually_specified(param_type); + } + if (param_type->has_genericT_inside()) { // `f(a)` where f is generic: use `a` to infer param type + infer_any_expr(arg_i); // then arg_i is inferred without any hint + param_type = deducingTs->auto_deduce_from_argument(arg_i->loc, param_type, arg_i->inferred_type); + } else { + infer_any_expr(arg_i, param_type); // param_type is hint, helps infer arg_i + } + assign_inferred_type(v->get_arg(i), arg_i); // arg itself is an expression + check_function_argument(param_type, param_i.is_mutate_parameter(), arg_i, false); + } + // if it's a generic function `f`, we need to instantiate it, like `f` // same for generic methods `t.tupleAt`, need to achieve `t.tupleAt` - if (fun_ref->is_generic_function() && v_instantiationTs) { - // if Ts are specified by a user like `f(args)` / `t.tupleAt()`, take them - std::vector substitutions = collect_fun_generic_substitutions_from_manually_specified(v->loc, fun_ref, v_instantiationTs); - fun_ref = check_and_instantiate_generic_function(v->loc, fun_ref, std::move(substitutions)); - - } else if (fun_ref->is_generic_function()) { - // if `f` called like `f(args)`, deduce T from arg types - std::vector arg_types; - arg_types.reserve(delta_self + v->get_num_args()); - if (dot_obj) { - arg_types.push_back(dot_obj->inferred_type); + if (fun_ref->is_generic_function()) { + // if `f(args)` was called, Ts were inferred; check that all of them are known + int idx = deducingTs->get_first_not_deduced_idx(); + if (idx != -1 && hint && fun_ref->declared_return_type->has_genericT_inside()) { + // example: `t.tupleFirst()`, T doesn't depend on arguments, but is determined by return type + // if used like `var x: int = t.tupleFirst()` / `t.tupleFirst() as int` / etc., use hint + deducingTs->auto_deduce_from_argument(v->loc, fun_ref->declared_return_type, hint); + idx = deducingTs->get_first_not_deduced_idx(); } - for (int i = 0; i < v->get_num_args(); ++i) { - arg_types.push_back(v->get_arg(i)->inferred_type); + if (idx != -1) { + v->error("can not deduce " + fun_ref->genericTs->get_nameT(idx)); } - - td::Result> deduced = deduce_substitutionTs_on_generic_func_call(fun_ref, std::move(arg_types), hint); - if (deduced.is_error()) { - v->error(deduced.error().message().str() + " for generic function " + to_string(fun_ref)); - } - fun_ref = check_and_instantiate_generic_function(v->loc, fun_ref, deduced.move_as_ok()); + fun_ref = check_and_instantiate_generic_function(v->loc, fun_ref, deducingTs->flush()); + delete deducingTs; } else if (UNLIKELY(v_instantiationTs != nullptr)) { // non-generic function/method called with type arguments, like `c.cellHash()` / `beginCell()` @@ -988,8 +1055,6 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { v->get_callee()->as()->mutate()->assign_target(fun_ref); v->get_callee()->as()->mutate()->assign_inferred_type(fun_ref->inferred_full_type); } - // check arguments count and their types - check_function_arguments(fun_ref, v->get_arg_list(), dot_obj); // get return type either from user-specified declaration or infer here on demand traversing its body get_or_infer_return_type(fun_ref); TypePtr inferred_type = dot_obj && fun_ref->does_return_self() ? dot_obj->inferred_type : fun_ref->inferred_return_type; @@ -1020,7 +1085,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { for (int i = 0; i < v->size(); ++i) { AnyExprV item = v->get_item(i); infer_any_expr(item, tuple_hint && i < tuple_hint->size() ? tuple_hint->items[i] : nullptr); - if (item->inferred_type->calc_width_on_stack() != 1) { + if (item->inferred_type->get_width_on_stack() != 1) { fire_error_tuple_cannot_have_non1_stack_width_elem(v->get_item(i)->loc, item->inferred_type); } types_list.emplace_back(item->inferred_type); @@ -1134,7 +1199,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { v->get_thrown_code()->error("excNo of `throw` must be an integer, got " + to_string(v->get_thrown_code())); } infer_any_expr(v->get_thrown_arg()); - if (v->has_thrown_arg() && v->get_thrown_arg()->inferred_type->calc_width_on_stack() != 1) { + if (v->has_thrown_arg() && v->get_thrown_arg()->inferred_type->get_width_on_stack() != 1) { v->get_thrown_arg()->error("can not throw " + to_string(v->get_thrown_arg()) + ", exception arg must occupy exactly 1 stack slot"); } } @@ -1153,7 +1218,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { static void process_catch_variable(AnyExprV catch_var, TypePtr catch_var_type) { if (auto v_ref = catch_var->try_as(); v_ref && v_ref->sym) { // not underscore - assign_inferred_type(v_ref->sym->as(), catch_var_type); + assign_inferred_type(v_ref->sym->try_as(), catch_var_type); } assign_inferred_type(catch_var, catch_var_type); } @@ -1163,7 +1228,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // `catch` has exactly 2 variables: excNo and arg (when missing, they are implicit underscores) // `arg` is a curious thing, it can be any TVM primitive, so assign unknown to it - // hence, using `fInt(arg)` (int from parameter is a hint) or `arg as slice` works well + // hence, using `fInt(arg)` (int from parameter is a target type) or `arg as slice` works well // it's not truly correct, because `arg as (int,int)` also compiles, but can never happen, but let it be user responsibility tolk_assert(v->get_catch_expr()->size() == 2); std::vector types_list = {TypeDataInt::create(), TypeDataUnknown::create()}; @@ -1175,7 +1240,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } public: - static void assign_fun_full_type(const FunctionData* fun_ref, TypePtr inferred_return_type) { + static void assign_fun_full_type(FunctionPtr fun_ref, TypePtr inferred_return_type) { // calculate function full type `fun(params) -> ret_type` std::vector params_types; params_types.reserve(fun_ref->get_num_params()); @@ -1185,7 +1250,7 @@ public: assign_inferred_type(fun_ref, inferred_return_type, TypeDataFunCallable::create(std::move(params_types), inferred_return_type)); } - void start_visiting_function(const FunctionData* fun_ref, V v_function) { + void start_visiting_function(FunctionPtr fun_ref, V v_function) { if (fun_ref->is_code_function()) { current_function = fun_ref; process_any_statement(v_function->get_body()); @@ -1212,12 +1277,12 @@ public: class LaunchInferTypesAndMethodsOnce final { public: - static bool should_visit_function(const FunctionData* fun_ref) { + static bool should_visit_function(FunctionPtr fun_ref) { // since inferring can be requested on demand, prevent second execution from a regular pipeline launcher return !fun_ref->is_type_inferring_done() && !fun_ref->is_generic_function(); } - static void start_visiting_function(const FunctionData* fun_ref, V v_function) { + static void start_visiting_function(FunctionPtr fun_ref, V v_function) { InferCheckTypesAndCallsAndFieldsVisitor visitor; visitor.start_visiting_function(fun_ref, v_function); } @@ -1227,8 +1292,8 @@ public: // example: `fun f() { return g(); } fun g() { ... }` // when analyzing `f()`, we need to infer what fun_ref=g returns // (if `g` is generic, it was already instantiated, so fun_ref=g is here) -static void infer_and_save_return_type_of_function(const FunctionData* fun_ref) { - static std::vector called_stack; +static void infer_and_save_return_type_of_function(FunctionPtr fun_ref) { + static std::vector called_stack; tolk_assert(!fun_ref->is_generic_function() && !fun_ref->is_type_inferring_done()); // if `g` has return type declared, like `fun g(): int { ... }`, don't traverse its body @@ -1255,7 +1320,7 @@ void pipeline_infer_types_and_calls_and_fields() { visit_ast_of_all_functions(); } -void pipeline_infer_types_and_calls_and_fields(const FunctionData* fun_ref) { +void pipeline_infer_types_and_calls_and_fields(FunctionPtr fun_ref) { InferCheckTypesAndCallsAndFieldsVisitor visitor; visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); } diff --git a/tolk/pipe-optimize-boolean-expr.cpp b/tolk/pipe-optimize-boolean-expr.cpp index a2e67047..c4c5d1dc 100644 --- a/tolk/pipe-optimize-boolean-expr.cpp +++ b/tolk/pipe-optimize-boolean-expr.cpp @@ -53,7 +53,7 @@ struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody { auto v_not = createV(loc, "!", tok_logical_not, rhs); v_not->assign_inferred_type(TypeDataBool::create()); v_not->assign_rvalue_true(); - v_not->assign_fun_ref(lookup_global_symbol("!b_")->as()); + v_not->assign_fun_ref(lookup_global_symbol("!b_")->try_as()); return v_not; } @@ -75,7 +75,7 @@ protected: auto v_neq = createV(v->loc, "!=", tok_neq, cond_not_not, v_zero); v_neq->mutate()->assign_rvalue_true(); v_neq->mutate()->assign_inferred_type(TypeDataBool::create()); - v_neq->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->as()); + v_neq->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->try_as()); return v_neq; } } @@ -117,12 +117,17 @@ protected: } v = createV(v->loc, !v->is_ifnot, v_cond_unary->get_rhs(), v->get_if_body(), v->get_else_body()); } + // `if (x != null)` -> ifnot(x == null) + if (auto v_cond_isnull = v->get_cond()->try_as(); v_cond_isnull && v_cond_isnull->is_negated) { + v_cond_isnull->mutate()->assign_is_negated(!v_cond_isnull->is_negated); + v = createV(v->loc, !v->is_ifnot, v_cond_isnull, v->get_if_body(), v->get_else_body()); + } return v; } public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } }; diff --git a/tolk/pipe-refine-lvalue-for-mutate.cpp b/tolk/pipe-refine-lvalue-for-mutate.cpp index 540d7413..a8b4f1ae 100644 --- a/tolk/pipe-refine-lvalue-for-mutate.cpp +++ b/tolk/pipe-refine-lvalue-for-mutate.cpp @@ -34,7 +34,7 @@ namespace tolk { GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD -static void fire_error_invalid_mutate_arg_passed(AnyExprV v, const FunctionData* fun_ref, const LocalVarData& p_sym, bool called_as_method, bool arg_passed_as_mutate, AnyV arg_expr) { +static void fire_error_invalid_mutate_arg_passed(AnyExprV v, FunctionPtr fun_ref, const LocalVarData& p_sym, bool called_as_method, bool arg_passed_as_mutate, AnyV arg_expr) { std::string arg_str(arg_expr->type == ast_reference ? arg_expr->as()->get_name() : "obj"); // case: `loadInt(cs, 32)`; suggest: `cs.loadInt(32)` @@ -60,7 +60,7 @@ static void fire_error_invalid_mutate_arg_passed(AnyExprV v, const FunctionData* class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBody { void visit(V v) override { // v is `globalF(args)` / `globalF(args)` / `obj.method(args)` / `local_var(args)` / `getF()(args)` - const FunctionData* fun_ref = v->fun_maybe; + FunctionPtr fun_ref = v->fun_maybe; if (!fun_ref) { parent::visit(v); for (int i = 0; i < v->get_num_args(); ++i) { @@ -86,6 +86,8 @@ class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBod leftmost_obj = as_par->get_expr(); } else if (auto as_cast = leftmost_obj->try_as()) { leftmost_obj = as_cast->get_expr(); + } else if (auto as_nn = leftmost_obj->try_as()) { + leftmost_obj = as_nn->get_expr(); } else { break; } @@ -114,7 +116,7 @@ class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBod } public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } }; diff --git a/tolk/pipe-register-symbols.cpp b/tolk/pipe-register-symbols.cpp index 2dae0d23..45246d6b 100644 --- a/tolk/pipe-register-symbols.cpp +++ b/tolk/pipe-register-symbols.cpp @@ -176,8 +176,8 @@ static void register_function(V v) { genericTs = construct_genericTs(v->genericsT_list); } if (v->is_builtin_function()) { - const Symbol* builtin_func = lookup_global_symbol(func_name); - const FunctionData* fun_ref = builtin_func ? builtin_func->as() : nullptr; + const Symbol* sym = lookup_global_symbol(func_name); + FunctionPtr fun_ref = sym ? sym->try_as() : nullptr; if (!fun_ref || !fun_ref->is_builtin_function()) { v->error("`builtin` used for non-builtin function"); } @@ -202,7 +202,7 @@ static void register_function(V v) { f_sym->method_id = static_cast(v->method_id->to_long()); } else if (v->flags & FunctionData::flagGetMethod) { f_sym->method_id = calculate_method_id_by_func_name(func_name); - for (const FunctionData* other : G.all_get_methods) { + for (FunctionPtr other : G.all_get_methods) { if (other->method_id == f_sym->method_id) { v->error(PSTRING() << "GET methods hash collision: `" << other->name << "` and `" << f_sym->name << "` produce the same hash. Consider renaming one of these functions."); } diff --git a/tolk/pipe-resolve-identifiers.cpp b/tolk/pipe-resolve-identifiers.cpp index 03b23c3c..95229d20 100644 --- a/tolk/pipe-resolve-identifiers.cpp +++ b/tolk/pipe-resolve-identifiers.cpp @@ -119,7 +119,7 @@ struct NameAndScopeResolver { return G.symtable.lookup(name); } - void add_local_var(const LocalVarData* v_sym) { + void add_local_var(LocalVarPtr v_sym) { if (UNLIKELY(scopes.empty())) { throw Fatal("unexpected scope_level = 0"); } @@ -168,9 +168,9 @@ static TypePtr finalize_type_data(TypePtr type_data, const GenericsDeclaration* class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody { // more correctly this field shouldn't be static, but currently there is no need to make it a part of state static NameAndScopeResolver current_scope; - static const FunctionData* current_function; + static FunctionPtr current_function; - static const LocalVarData* create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) { + static LocalVarPtr create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) { LocalVarData* v_sym = new LocalVarData(static_cast(name), loc, declared_type, immutable * LocalVarData::flagImmutable, -1); current_scope.add_local_var(v_sym); return v_sym; @@ -178,7 +178,7 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody { static void process_catch_variable(AnyExprV catch_var) { if (auto v_ref = catch_var->try_as()) { - const LocalVarData* var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true); + LocalVarPtr var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true); v_ref->mutate()->assign_sym(var_ref); } } @@ -190,14 +190,14 @@ protected: if (sym == nullptr) { v->error("`redef` for unknown variable"); } - const LocalVarData* var_ref = sym->try_as(); + LocalVarPtr var_ref = sym->try_as(); if (!var_ref) { v->error("`redef` for unknown variable"); } v->mutate()->assign_var_ref(var_ref); } else { TypePtr declared_type = finalize_type_data(v->declared_type, current_function->genericTs); - const LocalVarData* var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable); + LocalVarPtr var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable); v->mutate()->assign_resolved_type(declared_type); v->mutate()->assign_var_ref(var_ref); } @@ -216,7 +216,7 @@ protected: v->mutate()->assign_sym(sym); // for global functions, global vars and constants, `import` must exist - if (!sym->try_as()) { + if (!sym->try_as()) { check_import_exists_when_using_sym(v, sym); } @@ -276,14 +276,14 @@ protected: } public: - bool should_visit_function(const FunctionData* fun_ref) override { + bool should_visit_function(FunctionPtr fun_ref) override { // this pipe is done just after parsing // visit both asm and code functions, resolve identifiers in parameter/return types everywhere // for generic functions, unresolved "T" will be replaced by TypeDataGenericT return true; } - void start_visiting_function(const FunctionData* fun_ref, V v) override { + void start_visiting_function(FunctionPtr fun_ref, V v) override { current_function = fun_ref; for (int i = 0; i < v->get_num_params(); ++i) { @@ -313,7 +313,7 @@ public: }; NameAndScopeResolver AssignSymInsideFunctionVisitor::current_scope; -const FunctionData* AssignSymInsideFunctionVisitor::current_function = nullptr; +FunctionPtr AssignSymInsideFunctionVisitor::current_function = nullptr; void pipeline_resolve_identifiers_and_assign_symbols() { AssignSymInsideFunctionVisitor visitor; @@ -337,7 +337,7 @@ void pipeline_resolve_identifiers_and_assign_symbols() { } } -void pipeline_resolve_identifiers_and_assign_symbols(const FunctionData* fun_ref) { +void pipeline_resolve_identifiers_and_assign_symbols(FunctionPtr fun_ref) { AssignSymInsideFunctionVisitor visitor; if (visitor.should_visit_function(fun_ref)) { visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); diff --git a/tolk/pipeline.h b/tolk/pipeline.h index 6aec2b5e..ab65ef80 100644 --- a/tolk/pipeline.h +++ b/tolk/pipeline.h @@ -49,10 +49,10 @@ void pipeline_generate_fif_output_to_std_cout(); // these pipes also can be called per-function individually // they are called for instantiated generics functions, when `f` is deeply cloned as `f` -void pipeline_resolve_identifiers_and_assign_symbols(const FunctionData*); -void pipeline_calculate_rvalue_lvalue(const FunctionData*); -void pipeline_detect_unreachable_statements(const FunctionData*); -void pipeline_infer_types_and_calls_and_fields(const FunctionData*); +void pipeline_resolve_identifiers_and_assign_symbols(FunctionPtr); +void pipeline_calculate_rvalue_lvalue(FunctionPtr); +void pipeline_detect_unreachable_statements(FunctionPtr); +void pipeline_infer_types_and_calls_and_fields(FunctionPtr); } // namespace tolk diff --git a/tolk/symtable.cpp b/tolk/symtable.cpp index c56dc6ed..48b0b89d 100644 --- a/tolk/symtable.cpp +++ b/tolk/symtable.cpp @@ -120,7 +120,7 @@ static void fire_error_redefinition_of_symbol(SrcLocation loc, const Symbol* pre throw ParseError(loc, "redefinition of built-in symbol"); } -void GlobalSymbolTable::add_function(const FunctionData* f_sym) { +void GlobalSymbolTable::add_function(FunctionPtr f_sym) { auto key = key_hash(f_sym->name); auto [it, inserted] = entries.emplace(key, f_sym); if (!inserted) { @@ -128,7 +128,7 @@ void GlobalSymbolTable::add_function(const FunctionData* f_sym) { } } -void GlobalSymbolTable::add_global_var(const GlobalVarData* g_sym) { +void GlobalSymbolTable::add_global_var(GlobalVarPtr g_sym) { auto key = key_hash(g_sym->name); auto [it, inserted] = entries.emplace(key, g_sym); if (!inserted) { @@ -136,7 +136,7 @@ void GlobalSymbolTable::add_global_var(const GlobalVarData* g_sym) { } } -void GlobalSymbolTable::add_global_const(const GlobalConstData* c_sym) { +void GlobalSymbolTable::add_global_const(GlobalConstPtr c_sym) { auto key = key_hash(c_sym->name); auto [it, inserted] = entries.emplace(key, c_sym); if (!inserted) { diff --git a/tolk/symtable.h b/tolk/symtable.h index 27753ceb..9419afce 100644 --- a/tolk/symtable.h +++ b/tolk/symtable.h @@ -37,17 +37,12 @@ struct Symbol { virtual ~Symbol() = default; - template - const T* as() const { + template + ConstTPtr try_as() const { #ifdef TOLK_DEBUG - assert(dynamic_cast(this) != nullptr); + assert(this != nullptr); #endif - return dynamic_cast(this); - } - - template - const T* try_as() const { - return dynamic_cast(this); + return dynamic_cast(this); } }; @@ -229,9 +224,9 @@ class GlobalSymbolTable { } public: - void add_function(const FunctionData* f_sym); - void add_global_var(const GlobalVarData* g_sym); - void add_global_const(const GlobalConstData* c_sym); + void add_function(FunctionPtr f_sym); + void add_global_var(GlobalVarPtr g_sym); + void add_global_const(GlobalConstPtr c_sym); const Symbol* lookup(std::string_view name) const { const auto it = entries.find(key_hash(name)); diff --git a/tolk/tolk.h b/tolk/tolk.h index 4086d7f7..d218d510 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -45,7 +45,7 @@ typedef int const_idx_t; struct TmpVar { var_idx_t ir_idx; // every var in IR represents 1 stack slot - TypePtr v_type; // calc_width_on_stack() is 1 + TypePtr v_type; // get_width_on_stack() is 1 std::string name; // "x" for vars originated from user sources; "x.0" for tensor components; empty for implicitly created tmp vars SrcLocation loc; // location of var declaration in sources or where a tmp var was originated #ifdef TOLK_DEBUG @@ -283,8 +283,8 @@ struct Op { enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 }; int flags; std::unique_ptr next; - const FunctionData* f_sym = nullptr; - const GlobalVarData* g_sym = nullptr; + FunctionPtr f_sym = nullptr; + GlobalVarPtr g_sym = nullptr; SrcLocation where; VarDescrList var_info; std::vector args; @@ -313,19 +313,19 @@ struct Op { : cl(_cl), flags(0), f_sym(nullptr), where(_where), left(std::move(_left)), right(std::move(_right)) { } Op(SrcLocation _where, OpKind _cl, const std::vector& _left, const std::vector& _right, - const FunctionData* _fun) + FunctionPtr _fun) : cl(_cl), flags(0), f_sym(_fun), where(_where), left(_left), right(_right) { } Op(SrcLocation _where, OpKind _cl, std::vector&& _left, std::vector&& _right, - const FunctionData* _fun) + FunctionPtr _fun) : cl(_cl), flags(0), f_sym(_fun), where(_where), left(std::move(_left)), right(std::move(_right)) { } Op(SrcLocation _where, OpKind _cl, const std::vector& _left, const std::vector& _right, - const GlobalVarData* _gvar) + GlobalVarPtr _gvar) : cl(_cl), flags(0), g_sym(_gvar), where(_where), left(_left), right(_right) { } Op(SrcLocation _where, OpKind _cl, std::vector&& _left, std::vector&& _right, - const GlobalVarData* _gvar) + GlobalVarPtr _gvar) : cl(_cl), flags(0), g_sym(_gvar), where(_where), left(std::move(_left)), right(std::move(_right)) { } @@ -1083,7 +1083,7 @@ struct FunctionBodyAsm { struct CodeBlob { int var_cnt, in_var_cnt; - const FunctionData* fun_ref; + FunctionPtr fun_ref; std::string name; SrcLocation loc; std::vector vars; @@ -1094,7 +1094,7 @@ struct CodeBlob { #endif std::stack*> cur_ops_stack; bool require_callxargs = false; - CodeBlob(std::string name, SrcLocation loc, const FunctionData* fun_ref) + CodeBlob(std::string name, SrcLocation loc, FunctionPtr fun_ref) : var_cnt(0), in_var_cnt(0), fun_ref(fun_ref), name(std::move(name)), loc(loc), cur_ops(&ops) { } template diff --git a/tolk/type-system.cpp b/tolk/type-system.cpp index c7122e10..6cd353d5 100644 --- a/tolk/type-system.cpp +++ b/tolk/type-system.cpp @@ -108,6 +108,19 @@ void type_system_init() { // and creates an object only if it isn't found in a global hashtable // +TypePtr TypeDataNullable::create(TypePtr inner) { + TypeDataTypeIdCalculation hash(1774084920039440885ULL); + hash.feed_child(inner); + + if (TypePtr existing = hash.get_existing()) { + return existing; + } + // most types (int?, slice?, etc.), when nullable, still occupy 1 stack slot (holding TVM NULL at runtime) + // but for example for `(int, int)` we need an extra stack slot "null flag" + int width_on_stack = inner->can_hold_tvm_null_instead() ? 1 : inner->get_width_on_stack() + 1; + return hash.register_unique(new TypeDataNullable(hash.type_id(), hash.children_flags(), width_on_stack, inner)); +} + TypePtr TypeDataFunCallable::create(std::vector&& params_types, TypePtr return_type) { TypeDataTypeIdCalculation hash(3184039965511020991ULL); for (TypePtr param : params_types) { @@ -143,7 +156,11 @@ TypePtr TypeDataTensor::create(std::vector&& items) { if (TypePtr existing = hash.get_existing()) { return existing; } - return hash.register_unique(new TypeDataTensor(hash.type_id(), hash.children_flags(), std::move(items))); + int width_on_stack = 0; + for (TypePtr item : items) { + width_on_stack += item->get_width_on_stack(); + } + return hash.register_unique(new TypeDataTensor(hash.type_id(), hash.children_flags(), width_on_stack, std::move(items))); } TypePtr TypeDataTypedTuple::create(std::vector&& items) { @@ -178,6 +195,12 @@ TypePtr TypeDataUnresolved::create(std::string&& text, SrcLocation loc) { // only non-trivial implementations are here; trivial are defined in .h file // +std::string TypeDataNullable::as_human_readable() const { + std::string nested = inner->as_human_readable(); + bool embrace = inner->try_as(); + return embrace ? "(" + nested + ")?" : nested + "?"; +} + std::string TypeDataFunCallable::as_human_readable() const { std::string result = "("; for (TypePtr param : params_types) { @@ -223,6 +246,11 @@ std::string TypeDataTypedTuple::as_human_readable() const { // only non-trivial implementations are here; by default (no children), `callback(this)` is executed // +void TypeDataNullable::traverse(const TraverserCallbackT& callback) const { + callback(this); + inner->traverse(callback); +} + void TypeDataFunCallable::traverse(const TraverserCallbackT& callback) const { callback(this); for (TypePtr param : params_types) { @@ -254,6 +282,10 @@ void TypeDataTypedTuple::traverse(const TraverserCallbackT& callback) const { // only non-trivial implementations are here; by default (no children), `return callback(this)` is executed // +TypePtr TypeDataNullable::replace_children_custom(const ReplacerCallbackT& callback) const { + return callback(create(inner->replace_children_custom(callback))); +} + TypePtr TypeDataFunCallable::replace_children_custom(const ReplacerCallbackT& callback) const { std::vector mapped; mapped.reserve(params_types.size()); @@ -282,53 +314,17 @@ TypePtr TypeDataTypedTuple::replace_children_custom(const ReplacerCallbackT& cal } -// -------------------------------------------- -// calc_width_on_stack() -// -// returns the number of stack slots occupied by a variable of this type -// only non-trivial implementations are here; by default (most types) occupy 1 stack slot -// - -int TypeDataGenericT::calc_width_on_stack() const { - // this function is invoked only in functions with generics already instantiated - assert(false); - return -999999; -} - -int TypeDataTensor::calc_width_on_stack() const { - int sum = 0; - for (TypePtr item : items) { - sum += item->calc_width_on_stack(); - } - return sum; -} - -int TypeDataUnresolved::calc_width_on_stack() const { - // since early pipeline stages, no unresolved types left - assert(false); - return -999999; -} - -int TypeDataVoid::calc_width_on_stack() const { - return 0; -} - - // -------------------------------------------- // can_rhs_be_assigned() // // on `var lhs: = rhs`, having inferred rhs_type, check that it can be assigned without any casts // the same goes for passing arguments, returning values, etc. — where the "receiver" (lhs) checks "applier" (rhs) -// for now, `null` can be assigned to any TVM primitive, be later we'll have T? types and null safety // bool TypeDataInt::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -336,9 +332,6 @@ bool TypeDataBool::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -346,9 +339,6 @@ bool TypeDataCell::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -356,9 +346,6 @@ bool TypeDataSlice::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -366,9 +353,6 @@ bool TypeDataBuilder::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -376,9 +360,6 @@ bool TypeDataTuple::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -386,9 +367,6 @@ bool TypeDataContinuation::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -396,6 +374,19 @@ bool TypeDataNullLiteral::can_rhs_be_assigned(TypePtr rhs) const { return rhs == this; } +bool TypeDataNullable::can_rhs_be_assigned(TypePtr rhs) const { + if (rhs == this) { + return true; + } + if (rhs == TypeDataNullLiteral::create()) { + return true; + } + if (const TypeDataNullable* rhs_nullable = rhs->try_as()) { + return inner->can_rhs_be_assigned(rhs_nullable->inner); + } + return inner->can_rhs_be_assigned(rhs); +} + bool TypeDataFunCallable::can_rhs_be_assigned(TypePtr rhs) const { return rhs == this; } @@ -414,7 +405,6 @@ bool TypeDataTensor::can_rhs_be_assigned(TypePtr rhs) const { } return true; } - // note, that tensors can not accept null return false; } @@ -427,9 +417,6 @@ bool TypeDataTypedTuple::can_rhs_be_assigned(TypePtr rhs) const { } return true; } - if (rhs == TypeDataNullLiteral::create()) { - return true; - } return false; } @@ -455,41 +442,69 @@ bool TypeDataVoid::can_rhs_be_assigned(TypePtr rhs) const { // bool TypeDataInt::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { // `int` as `int?` + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this; } bool TypeDataBool::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this || cast_to == TypeDataInt::create(); } bool TypeDataCell::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this; } bool TypeDataSlice::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this; } bool TypeDataBuilder::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this; } bool TypeDataTuple::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this; } bool TypeDataContinuation::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return cast_to == this; } bool TypeDataNullLiteral::can_be_casted_with_as_operator(TypePtr cast_to) const { - return cast_to == this - || cast_to == TypeDataInt::create() || cast_to == TypeDataBool::create() || cast_to == TypeDataCell::create() || cast_to == TypeDataSlice::create() - || cast_to == TypeDataBuilder::create() || cast_to == TypeDataContinuation::create() || cast_to == TypeDataTuple::create() - || cast_to->try_as(); + return cast_to == this || cast_to->try_as(); +} + +bool TypeDataNullable::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return inner->can_be_casted_with_as_operator(to_nullable->inner); + } + return false; } bool TypeDataFunCallable::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return this == cast_to; } @@ -506,6 +521,9 @@ bool TypeDataTensor::can_be_casted_with_as_operator(TypePtr cast_to) const { } return true; } + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return false; } @@ -518,14 +536,15 @@ bool TypeDataTypedTuple::can_be_casted_with_as_operator(TypePtr cast_to) const { } return true; } + if (const auto* to_nullable = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_nullable->inner); + } return false; } bool TypeDataUnknown::can_be_casted_with_as_operator(TypePtr cast_to) const { - // 'unknown' can be cast to any type - // (though it's not valid for exception arguments when casting them to non-1 stack width, - // but to ensure it, we need a special type "unknown TVM primitive", which is overwhelming I think) - return true; + // 'unknown' can be cast to any TVM value + return cast_to->get_width_on_stack() == 1; } bool TypeDataUnresolved::can_be_casted_with_as_operator(TypePtr cast_to) const { @@ -537,12 +556,45 @@ bool TypeDataVoid::can_be_casted_with_as_operator(TypePtr cast_to) const { } +// -------------------------------------------- +// can_hold_tvm_null_instead() +// +// assigning `null` to a primitive variable like `int?` / `cell?` can store TVM NULL inside the same slot +// (that's why the default implementation is just "return true", and most of types occupy 1 slot) +// but for complex variables, like `(int, int)?`, "null presence" is kept in a separate slot (UTag for union types) +// though still, tricky situations like `(int, ())?` can still "embed" TVM NULL in parallel with original value +// + +bool TypeDataNullable::can_hold_tvm_null_instead() const { + if (get_width_on_stack() != 1) { // `(int, int)?` / `()?` can not hold null instead + return false; // only `int?` / `cell?` / `StructWith1IntField?` can + } // and some tricky situations like `(int, ())?`, but not `(int?, ())?` + return !inner->can_hold_tvm_null_instead(); +} + +bool TypeDataTensor::can_hold_tvm_null_instead() const { + if (get_width_on_stack() != 1) { // `(int, int)` / `()` can not hold null instead, since null is 1 slot + return false; // only `((), int)` and similar can: + } // one item is width 1 (and not nullable), others are 0 + for (TypePtr item : items) { + if (item->get_width_on_stack() == 1 && !item->can_hold_tvm_null_instead()) { + return false; + } + } + return true; +} + +bool TypeDataVoid::can_hold_tvm_null_instead() const { + return false; +} + + // -------------------------------------------- // parsing type from tokens // // here we implement parsing types (mostly after colon) to TypeData // example: `var v: int` is TypeDataInt -// example: `var v: (builder, [cell])` is TypeDataTensor(TypeDataBuilder, TypeDataTypedTuple(TypeDataCell)) +// example: `var v: (builder?, [cell])` is TypeDataTensor(TypeDataNullable(TypeDataBuilder), TypeDataTypedTuple(TypeDataCell)) // example: `fun f(): ()` is TypeDataTensor() (an empty one) // // note, that unrecognized type names (MyEnum, MyStruct, T) are parsed as TypeDataUnresolved, @@ -633,7 +685,8 @@ static TypePtr parse_type_nullable(Lexer& lex) { TypePtr result = parse_simple_type(lex); if (lex.tok() == tok_question) { - lex.error("nullable types are not supported yet"); + lex.next(); + result = TypeDataNullable::create(result); } return result; diff --git a/tolk/type-system.h b/tolk/type-system.h index 482039e6..02e50fc2 100644 --- a/tolk/type-system.h +++ b/tolk/type-system.h @@ -50,6 +50,8 @@ class TypeData { const uint64_t type_id; // bits of flag_mask, to store often-used properties and return them without tree traversing const int flags; + // how many slots on a stack this type occupies (calculated on creation), e.g. `int`=1, `(int,int)`=2, `(int,int)?`=3 + const int width_on_stack; friend class TypeDataTypeIdCalculation; @@ -60,9 +62,10 @@ protected: flag_contains_unresolved_inside = 1 << 3, }; - explicit TypeData(uint64_t type_id, int flags_with_children) + explicit TypeData(uint64_t type_id, int flags_with_children, int width_on_stack) : type_id(type_id) - , flags(flags_with_children) { + , flags(flags_with_children) + , width_on_stack(width_on_stack) { } public: @@ -74,6 +77,7 @@ public: } uint64_t get_type_id() const { return type_id; } + int get_width_on_stack() const { return width_on_stack; } bool has_unknown_inside() const { return flags & flag_contains_unknown_inside; } bool has_genericT_inside() const { return flags & flag_contains_genericT_inside; } @@ -86,6 +90,10 @@ public: virtual bool can_rhs_be_assigned(TypePtr rhs) const = 0; virtual bool can_be_casted_with_as_operator(TypePtr cast_to) const = 0; + virtual bool can_hold_tvm_null_instead() const { + return true; + } + virtual void traverse(const TraverserCallbackT& callback) const { callback(this); } @@ -93,17 +101,13 @@ public: virtual TypePtr replace_children_custom(const ReplacerCallbackT& callback) const { return callback(this); } - - virtual int calc_width_on_stack() const { - return 1; - } }; /* * `int` is TypeDataInt, representation of TVM int. */ class TypeDataInt final : public TypeData { - TypeDataInt() : TypeData(1ULL, 0) {} + TypeDataInt() : TypeData(1ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -121,7 +125,7 @@ public: * From the type system point of view, int and bool are different, not-autocastable types. */ class TypeDataBool final : public TypeData { - TypeDataBool() : TypeData(2ULL, 0) {} + TypeDataBool() : TypeData(2ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -138,7 +142,7 @@ public: * `cell` is TypeDataCell, representation of TVM cell. */ class TypeDataCell final : public TypeData { - TypeDataCell() : TypeData(3ULL, 0) {} + TypeDataCell() : TypeData(3ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -155,7 +159,7 @@ public: * `slice` is TypeDataSlice, representation of TVM slice. */ class TypeDataSlice final : public TypeData { - TypeDataSlice() : TypeData(4ULL, 0) {} + TypeDataSlice() : TypeData(4ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -172,7 +176,7 @@ public: * `builder` is TypeDataBuilder, representation of TVM builder. */ class TypeDataBuilder final : public TypeData { - TypeDataBuilder() : TypeData(5ULL, 0) {} + TypeDataBuilder() : TypeData(5ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -191,7 +195,7 @@ public: * so getting its element results in TypeDataUnknown (which must be assigned/cast explicitly). */ class TypeDataTuple final : public TypeData { - TypeDataTuple() : TypeData(6ULL, 0) {} + TypeDataTuple() : TypeData(6ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -209,7 +213,7 @@ public: * It's like "untyped callable", not compatible with other types. */ class TypeDataContinuation final : public TypeData { - TypeDataContinuation() : TypeData(7ULL, 0) {} + TypeDataContinuation() : TypeData(7ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -224,12 +228,12 @@ public: /* * `null` has TypeDataNullLiteral type. - * Currently, it can be assigned to int/slice/etc., but later Tolk will have T? types and null safety. + * It can be assigned only to nullable types (`int?`, etc.), to ensure null safety. * Note, that `var i = null`, though valid (i would be constant null), fires an "always-null" compilation error * (it's much better for user to see an error here than when he passes this variable somewhere). */ class TypeDataNullLiteral final : public TypeData { - TypeDataNullLiteral() : TypeData(8ULL, 0) {} + TypeDataNullLiteral() : TypeData(8ULL, 0, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -242,6 +246,30 @@ public: bool can_be_casted_with_as_operator(TypePtr cast_to) const override; }; +/* + * `T?` is "nullable T". + * It can be converted to T either with ! (non-null assertion operator) or with smart casts. + */ +class TypeDataNullable final : public TypeData { + TypeDataNullable(uint64_t type_id, int children_flags, int width_on_stack, TypePtr inner) + : TypeData(type_id, children_flags, width_on_stack) + , inner(inner) {} + +public: + const TypePtr inner; + + static TypePtr create(TypePtr inner); + + bool is_primitive_nullable() const { return get_width_on_stack() == 1 && inner->get_width_on_stack() == 1; } + + std::string as_human_readable() const override; + bool can_rhs_be_assigned(TypePtr rhs) const override; + bool can_be_casted_with_as_operator(TypePtr cast_to) const override; + void traverse(const TraverserCallbackT& callback) const override; + TypePtr replace_children_custom(const ReplacerCallbackT& callback) const override; + bool can_hold_tvm_null_instead() const override; +}; + /* * `fun(int, int) -> void` is TypeDataFunCallable, think of is as a typed continuation. * A type of function `fun f(x: int) { return x; }` is actually `fun(int) -> int`. @@ -249,7 +277,7 @@ public: */ class TypeDataFunCallable final : public TypeData { TypeDataFunCallable(uint64_t type_id, int children_flags, std::vector&& params_types, TypePtr return_type) - : TypeData(type_id, children_flags) + : TypeData(type_id, children_flags, 1) , params_types(std::move(params_types)) , return_type(return_type) {} @@ -275,7 +303,7 @@ public: */ class TypeDataGenericT final : public TypeData { TypeDataGenericT(uint64_t type_id, std::string&& nameT) - : TypeData(type_id, flag_contains_genericT_inside) + : TypeData(type_id, flag_contains_genericT_inside, -999999) // width undefined until instantiated , nameT(std::move(nameT)) {} public: @@ -286,7 +314,6 @@ public: std::string as_human_readable() const override { return nameT; } bool can_rhs_be_assigned(TypePtr rhs) const override; bool can_be_casted_with_as_operator(TypePtr cast_to) const override; - int calc_width_on_stack() const override; }; /* @@ -296,8 +323,8 @@ public: * A tensor can be empty. */ class TypeDataTensor final : public TypeData { - TypeDataTensor(uint64_t type_id, int children_flags, std::vector&& items) - : TypeData(type_id, children_flags) + TypeDataTensor(uint64_t type_id, int children_flags, int width_on_stack, std::vector&& items) + : TypeData(type_id, children_flags, width_on_stack) , items(std::move(items)) {} public: @@ -312,7 +339,7 @@ public: bool can_be_casted_with_as_operator(TypePtr cast_to) const override; void traverse(const TraverserCallbackT& callback) const override; TypePtr replace_children_custom(const ReplacerCallbackT& callback) const override; - int calc_width_on_stack() const override; + bool can_hold_tvm_null_instead() const override; }; /* @@ -322,7 +349,7 @@ public: */ class TypeDataTypedTuple final : public TypeData { TypeDataTypedTuple(uint64_t type_id, int children_flags, std::vector&& items) - : TypeData(type_id, children_flags) + : TypeData(type_id, children_flags, 1) , items(std::move(items)) {} public: @@ -346,7 +373,7 @@ public: * The only thing available to do with unknown is to cast it: `catch (excNo, arg) { var i = arg as int; }` */ class TypeDataUnknown final : public TypeData { - TypeDataUnknown() : TypeData(20ULL, flag_contains_unknown_inside) {} + TypeDataUnknown() : TypeData(20ULL, flag_contains_unknown_inside, 1) {} static TypePtr singleton; friend void type_system_init(); @@ -367,7 +394,7 @@ public: */ class TypeDataUnresolved final : public TypeData { TypeDataUnresolved(uint64_t type_id, std::string&& text, SrcLocation loc) - : TypeData(type_id, flag_contains_unresolved_inside) + : TypeData(type_id, flag_contains_unresolved_inside, -999999) , text(std::move(text)) , loc(loc) {} @@ -380,7 +407,6 @@ public: std::string as_human_readable() const override { return text + "*"; } bool can_rhs_be_assigned(TypePtr rhs) const override; bool can_be_casted_with_as_operator(TypePtr cast_to) const override; - int calc_width_on_stack() const override; }; /* @@ -389,7 +415,7 @@ public: * Empty tensor is not compatible with void, although at IR level they are similar, 0 stack slots. */ class TypeDataVoid final : public TypeData { - TypeDataVoid() : TypeData(10ULL, 0) {} + TypeDataVoid() : TypeData(10ULL, 0, 0) {} static TypePtr singleton; friend void type_system_init(); @@ -400,7 +426,7 @@ public: std::string as_human_readable() const override { return "void"; } bool can_rhs_be_assigned(TypePtr rhs) const override; bool can_be_casted_with_as_operator(TypePtr cast_to) const override; - int calc_width_on_stack() const override; + bool can_hold_tvm_null_instead() const override; };