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

Merge pull request #1545 from ton-blockchain/tolk-v0.9

Tolk v0.9: nullable types `T?`, null safety, control flow, smart casts
This commit is contained in:
EmelyanenkoK 2025-03-05 09:30:05 +03:00 committed by GitHub
commit 6eeae5d78b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
110 changed files with 5744 additions and 1843 deletions

View file

@ -1,7 +1,7 @@
// Standard library for Tolk (LGPL licence).
// It contains common functions that are available out of the box, the user doesn't have to import anything.
// More specific functions are required to be imported explicitly, like "@stdlib/tvm-dicts".
tolk 0.8
tolk 0.9
/**
Tuple manipulation primitives.
@ -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";

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/**
Gas and payment related primitives.

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/**
Lisp-style lists are nested 2-elements tuples: `(1, (2, (3, null)))` represents list `[1, 2, 3]`.
@ -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<X>(head: X, tail: tuple): tuple
fun listPrepend<X>(head: X, tail: tuple?): tuple
asm "CONS";
/// Extracts the head and the tail of lisp-style list.
@pure
fun listSplit<X>(list: tuple): (X, tuple)
fun listSplit<X>(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<X>(mutate self: tuple): X
fun listNext<X>(mutate self: tuple?): X
asm( -> 1 0) "UNCONS";
/// Returns the head of lisp-style list.
@ -34,5 +35,5 @@ fun listGetHead<X>(list: tuple): X
/// Returns the tail of lisp-style list.
@pure
fun listGetTail(list: tuple): tuple
fun listGetTail(list: tuple): tuple?
asm "CDR";

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/**
Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular).
@ -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";

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls.
/// The primitive returns the current value of `c3`.

View file

@ -2,7 +2,7 @@ import "@stdlib/tvm-lowlevel"
fun pair_first<X, Y>(p: [X, Y]): X asm "FIRST";
fun one(dummy: tuple) {
fun one(dummy: tuple?) {
return 1;
}
@ -144,15 +144,16 @@ fun test95() {
"""
test95 PROC:<{
...
next GETGLOB // '10
3 PUSHINT // '10 '12=3
4 PUSHINT // '10 '12=3 '13=4
5 PUSHINT // '10 '12=3 '13=4 '14=5
TRIPLE // '15 '16
next SETGLOB
next GETGLOB // g_next
3 PUSHINT // g_next '14=3
4 PUSHINT // g_next '14=3 '15=4
5 PUSHINT // g_next '14=3 '15=4 '16=5
TRIPLE // '10 '11
SWAP
cur SETGLOB
cur GETGLOB // '17
next GETGLOB // '17 '18
next SETGLOB
cur GETGLOB // g_cur
next GETGLOB // g_cur g_next
}>
"""
*/

View file

@ -147,5 +147,5 @@ fun main() {
// x.0 x.1
"""
@code_hash 7627024945492125068389905298530400936797031708759561372406088054030801992712
@code_hash 61280273714870328160131559159866470128402169974050439159015534193532598351244
*/

View file

@ -26,10 +26,189 @@ fun typesAsIdentifiers(builder: builder) {
return int;
}
global callOrder: tuple;
fun getTensor_12() {
callOrder.tuplePush(100);
return (1, 2);
}
fun getTensor_1X(x: int) {
callOrder.tuplePush(101);
return (1, x);
}
fun getTuple_12() {
callOrder.tuplePush(110);
return [1, 2];
}
fun getTuple_1X(x: int) {
callOrder.tuplePush(111);
return [1, x];
}
fun getUntypedTuple_12() {
callOrder.tuplePush(120);
var t = createEmptyTuple(); t.tuplePush(1); t.tuplePush(2);
return t;
}
fun getUntypedTuple_1X(x: int) {
callOrder.tuplePush(121);
var t = createEmptyTuple(); t.tuplePush(1); t.tuplePush(x);
return t;
}
fun getIntValue5() {
callOrder.tuplePush(10);
return 5;
}
fun getIntValueX(x: int) {
callOrder.tuplePush(11);
return x;
}
@method_id(102)
fun test102() {
callOrder = createEmptyTuple();
var x = 0;
getTensor_12().0 = getIntValue5();
getTensor_1X(5).1 = getIntValue5();
getTensor_1X(x = 10).0 = getIntValueX(x);
return (callOrder, x);
}
@method_id(103)
fun test103() {
callOrder = createEmptyTuple();
var x = 0;
getTuple_12().0 = getIntValue5();
getTuple_1X(5).1 = getIntValue5();
getTuple_1X(x = 10).0 = getIntValueX(x);
return (callOrder, x);
}
@method_id(104)
fun test104() {
callOrder = createEmptyTuple();
var x = 0;
getUntypedTuple_12().0 = getIntValue5();
getUntypedTuple_1X(5).1 = getIntValue5();
getUntypedTuple_1X(x = 10).0 = getIntValueX(x);
return (callOrder, x);
}
@method_id(105)
fun test105() {
callOrder = createEmptyTuple();
getTensor_12().0 = getTensor_1X(getIntValue5()).1 = getIntValueX(getTensor_12().1);
return callOrder;
}
@method_id(106)
fun test106() {
callOrder = createEmptyTuple();
getTuple_12().0 = getTuple_1X(getIntValue5()).1 = getIntValueX(getTuple_12().1);
return callOrder;
}
global t107: (int, int);
@method_id(107)
fun test107() {
((t107 = (1, 2)).0, (t107 = (3, 4)).1) = (5, 6);
return t107;
}
global g108: int;
fun assertEq(a: int, b: int) {
assert(a == b, 10);
return b;
}
@method_id(108)
fun test108() {
callOrder = createEmptyTuple();
g108 = 0;
getTensor_1X(g108 = 8).1 = assertEq(g108, 8);
return (callOrder, g108);
}
@method_id(109)
fun test109() {
callOrder = createEmptyTuple();
var x = 0;
[getTuple_12().0, getTuple_1X(x = getIntValue5()).1, getTuple_1X(x += 10).0] = [getIntValue5(), getIntValue5(), getIntValueX(x)];
return (callOrder, x);
}
global g110: int;
global t110: (int, int);
@method_id(110)
fun test110() {
callOrder = createEmptyTuple();
var xy = [0, 0];
[xy.0, getTuple_1X(g110 = 8).0] = [g110 += 5, getIntValueX(g110 += 10)];
[xy.1, getTuple_1X((t110 = (8, 9)).0).1] = [t110.0 += 5, getIntValueX(t110.1 += 10)];
return (xy, callOrder, g110, t110);
}
@method_id(111)
fun test111() {
callOrder = createEmptyTuple();
var z = -1;
var xy = [0, z = 0];
var rhs = [getIntValueX(xy.1 += 10), xy.1, xy.0, z += 50];
[xy.0, getTuple_1X(g110 = 8 + getIntValueX(xy.1)).0, xy.1, z] = rhs;
return (xy, g110, callOrder, z);
}
@method_id(112)
fun test112() {
var xy = [1, 2];
((((xy))).0, ((xy.1))) = ((xy).1, ((xy.0)));
return xy;
}
@method_id(113)
fun test113() {
var (a, t, z) = (1, [2,3], (-1,-1));
(a, t, a, z, t.1, z.1) = (10, [a,12], 13, (a, t.1), 14, t.1);
return (a, t, z);
}
global g114: int;
global t114: [int, int];
global z114: (int, int);
@method_id(114)
fun test114() {
g114 = 1;
t114 = [2, 3];
(g114, t114, g114, z114, t114.1, z114.1) = (10, [g114,12], 13, (g114, t114.1), 14, t114.1);
return (g114, t114, z114);
}
@method_id(115)
fun test115() {
callOrder = createEmptyTuple();
var x = 0;
var y = 0;
[getTensor_1X(x = 5).0, y] = getTuple_1X(x = 9);
return (callOrder, x, y);
}
@method_id(116)
fun test116() {
var (a,b,c,d) = (0,0,0,0);
var rhs = [1, 2, 3, 4];
var rhs2 = ([a,b,c,d] = rhs);
__expect_type(rhs2, "[int, int, int, int]");
return (a, b, c, d, rhs2);
}
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!;
}
/**
@ -37,4 +216,35 @@ fun main(value: int) {
@testcase | 0 | 6 | -1
@testcase | 0 | 11 | (null)
@testcase | 101 | 78 | 88
@testcase | 102 | | [ 100 10 101 10 101 11 ] 10
@testcase | 103 | | [ 110 10 111 10 111 11 ] 10
@testcase | 104 | | [ 120 10 121 10 121 11 ] 10
@testcase | 105 | | [ 100 10 101 100 11 ]
@testcase | 106 | | [ 110 10 111 110 11 ]
@testcase | 107 | | 3 4
@testcase | 108 | | [ 101 ] 8
@testcase | 109 | | [ 110 10 111 111 10 10 11 ] 15
@testcase | 110 | | [ 13 13 ] [ 111 11 111 11 ] 23 13 19
@testcase | 111 | | [ 10 0 ] 18 [ 11 11 111 ] 50
@testcase | 112 | | [ 2 1 ]
@testcase | 113 | | 13 [ 1 14 ] 1 3
@testcase | 114 | | 13 [ 1 14 ] 1 3
@testcase | 115 | | [ 101 111 ] 9 9
@testcase | 116 | | 1 2 3 4 [ 1 2 3 4 ]
@fif_codegen
"""
test116 PROC:<{
//
1 PUSHINT // '10=1
2 PUSHINT // '10=1 '11=2
3 PUSHINT // '10=1 '11=2 '12=3
4 PUSHINT // '10=1 '11=2 '12=3 '13=4
4 TUPLE // rhs
DUP // rhs rhs
4 UNTUPLE // rhs2 a b c d
4 ROLL // a b c d rhs2
}>
"""
*/

View file

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

View file

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

View file

@ -35,7 +35,7 @@ Below, I just give examples of @fif_codegen tag:
"""
main PROC:<{
// s
17 PUSHINT // s '1=17
17 PUSHINT // s '3=17
OVER // s z=17 t
WHILE:<{
...

View file

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

View file

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

View file

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

View file

@ -21,6 +21,26 @@ fun plus(mutate self: int, y: int): int {
fun eq<X>(v: X): X { return v; }
global gTup: [int];
global gTens: (int, int);
@method_id(100)
fun testCodegenSimple() {
var t1 = [1];
t1.0 = 2;
debugPrintString("");
var t2 = [[1]];
t2.0.0 = 2;
debugPrintString("");
gTup = [1];
gTup.0 = 2;
debugPrintString("");
gTens = (1,2);
gTens.1 = 4;
debugPrintString("");
return (t1, t2, gTup, gTens);
}
@method_id(101)
fun test101() {
var t = (1, (2, 3), [4, 5, [6, 7]], 8);
@ -66,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;
@ -78,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;
@ -158,7 +178,7 @@ fun test114(f: int, s: int) {
@method_id(115)
fun test115() {
var y = [[[[true]]]];
return (y, y.0.0.0.0 = !y.0.0.0.0, y.0);
return (y, ((((y).0).0).0).0 = !y.0.0.0.0, y.0);
}
@method_id(116)
@ -213,6 +233,25 @@ fun test121(zero: int) {
return t;
}
fun isFirstComponentGt0<T1,T2>(t: (T1, T2)): bool {
return t.0 > 0;
}
@method_id(122)
fun test122(x: (int, int)) {
return (
isFirstComponentGt0(x), isFirstComponentGt0((2, beginCell())), isFirstComponentGt0<int,slice?>((0, null)),
x.isFirstComponentGt0(), (2, beginCell()).isFirstComponentGt0(), (0, null).isFirstComponentGt0<int,slice?>()
);
}
@method_id(123)
fun test123() {
var t = [[10, 20]] as [[int,int]]?;
((t!).0).0 = ((t!).0).1 = 100;
return t;
}
fun main(){}
@ -238,6 +277,58 @@ 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
"""
testCodegenSimple PROC:<{
//
1 PUSHINT // '2=1
SINGLE // t1
2 PUSHINT // t1 '3=2
0 SETINDEX // t1
x{} PUSHSLICE // t1 '6
STRDUMP DROP
1 PUSHINT // t1 '10=1
SINGLE // t1 '9
SINGLE // t1 t2
2 PUSHINT // t1 t2 '11=2
OVER // t1 t2 '11=2 t2
0 INDEX // t1 t2 '11=2 '14
SWAP // t1 t2 '14 '11=2
0 SETINDEX // t1 t2 '14
0 SETINDEX // t1 t2
x{} PUSHSLICE // t1 t2 '17
STRDUMP DROP
1 PUSHINT // t1 t2 '20=1
SINGLE // t1 t2 '18
gTup SETGLOB
2 PUSHINT // t1 t2 '21=2
gTup GETGLOB // t1 t2 '21=2 g_gTup
SWAP // t1 t2 g_gTup '21=2
0 SETINDEX // t1 t2 g_gTup
gTup SETGLOB
x{} PUSHSLICE // t1 t2 '25
STRDUMP DROP
1 PUSHINT // t1 t2 '28=1
2 PUSHINT // t1 t2 '26=1 '27=2
PAIR
gTens SETGLOB
4 PUSHINT // t1 t2 g_gTens.1=4
gTens GETGLOB
UNPAIR // t1 t2 g_gTens.1=4 g_gTens.0 g_gTens.1
DROP // t1 t2 g_gTens.1=4 g_gTens.0
SWAP // t1 t2 g_gTens.0 g_gTens.1=4
PAIR
gTens SETGLOB
x{} PUSHSLICE // t1 t2 '36
STRDUMP DROP
gTup GETGLOB // t1 t2 g_gTup
gTens GETGLOB
UNPAIR // t1 t2 g_gTup g_gTens.0 g_gTens.1
}>
"""
@fif_codegen
"""
@ -247,26 +338,6 @@ fun main(){}
}>
"""
@fif_codegen
"""
test104 PROC:<{
//
5 PUSHINT // '2=5
DUP // '2=5 '3=5
PAIR // '1
SINGLE // m
10 PUSHINT // m '5=10
20 PUSHINT // m '5=10 '6=20
s2 PUSH // m '5=10 '6=20 m
0 INDEX // m '10=10 '12=20 '8
SWAP // m '10=10 '8 '12=20
1 SETINDEX // m '10=10 '8
SWAP // m '8 '10=10
0 SETINDEX // m '8
0 SETINDEX // m
...
"""
@fif_codegen
"""
testCodegenIndexPostfix1 PROC:<{

View file

@ -18,10 +18,12 @@ fun test1(x: int, y: int) {
__expect_type(random() ? x : y, "int");
__expect_type(eq(x), "int");
__expect_type(eq<int>(x), "int");
__expect_type(eq<int>(null), "int");
__expect_type(eq<int?>(null), "int?");
__expect_type(x as int, "int");
__expect_type(+x, "int");
__expect_type(~x, "int");
__expect_type(x!, "int");
__expect_type(x!!!, "int");
{
var x: slice = beginCell().endCell().beginParse();
__expect_type(x, "slice");
@ -62,9 +64,9 @@ fun test5(x: int) {
__expect_type([], "[]");
__expect_type([x], "[int]");
__expect_type([x, x >= 1], "[int, bool]");
__expect_type([x, x >= 1, null as slice], "[int, bool, slice]");
__expect_type([x, x >= 1, null as slice?], "[int, bool, slice?]");
__expect_type((x, [x], [[x], x]), "(int, [int], [[int], int])");
__expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell]");
__expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell?]");
}
fun test6() {
@ -84,6 +86,17 @@ fun test7() {
// __expect_type(eq<(int, slice)>, "(int, slice) -> (int, slice)");
}
fun alwaysThrows(): never { throw 123; }
fun alwaysThrowsNotAnnotated() { throw 123; }
fun alwaysThrowsNotAnnotated2() { alwaysThrows(); }
fun test9() {
__expect_type(alwaysThrows(), "never");
__expect_type(alwaysThrows, "() -> never");
__expect_type(alwaysThrowsNotAnnotated(), "void");
__expect_type(alwaysThrowsNotAnnotated2(), "void");
}
fun main() {
return 0;

View file

@ -1,9 +1,9 @@
fun main() {
var c = 1;
(c, c) = (2, 3);
var t = createEmptyTuple();
t.0 = (1, 2);
}
/**
@compilation_should_fail
@stderr one variable modified twice inside the same expression
@stderr a tuple can not have `(int, int)` inside, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -1,11 +1,8 @@
fun incThree(mutate a: int, mutate b: int, mutate c: int) {}
fun main() {
var c = [[[1, 2]]];
incThree(mutate c.0.0.0, mutate c.0.0.1, mutate c.0.0.0);
fun main(cs: slice) {
var cb = cs.tupleSize;
}
/**
@compilation_should_fail
@stderr one variable modified twice inside the same expression
@stderr referencing a method for `tuple` with object of type `slice`
*/

View file

@ -1,10 +1,9 @@
global gg: (int, int);
fun main() {
[gg.0, gg.1, gg.0] = [0, 1, 0];
var t = createEmptyTuple();
var xy = t.0 as (int, int);
}
/**
@compilation_should_fail
@stderr one variable modified twice inside the same expression
@stderr a tuple can not have `(int, int)` inside, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -1,10 +0,0 @@
global gg: (int, [int, int]);
fun main() {
(gg.1.0, gg.1, gg.1.1) = (0, [1, 2], 3);
}
/**
@compilation_should_fail
@stderr one variable both modified and read inside the same expression
*/

View file

@ -1,9 +0,0 @@
fun main() {
var ab = (1, 2);
(ab, ab.1) = ((2, 3), 4);
}
/**
@compilation_should_fail
@stderr one variable both modified and read inside the same expression
*/

View file

@ -1,9 +0,0 @@
fun main() {
var t = createEmptyTuple();
t.0 = (1, 2);
}
/**
@compilation_should_fail
@stderr can not put `(int, int)` into a tuple, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -1,8 +0,0 @@
fun main(cs: slice) {
var cb = cs.tupleSize;
}
/**
@compilation_should_fail
@stderr referencing a method for `tuple` with object of type `slice`
*/

View file

@ -7,5 +7,5 @@ fun main() {
/**
@compilation_should_fail
@stderr can not put `(int, builder)` into a tuple, because it occupies 2 stack slots in TVM, not 1
@stderr a tuple can not have `(int, builder)` inside, because it occupies 2 stack slots in TVM, not 1
*/

View file

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

View file

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

View file

@ -0,0 +1,17 @@
fun eq<X>(v: X) {}
fun cantDeduceWhenNotInferred() {
// at type inferring (before type checking) they are unknown
var (x, y) = 2;
eq(x as int); // ok (since execution doesn't reach type checking)
eq<int>(x); // ok (since execution doesn't reach type checking)
eq(x);
}
/**
@compilation_should_fail
@stderr in function `cantDeduceWhenNotInferred`
@stderr can not deduce X for generic function `eq<X>`
@stderr eq(x);
*/

View file

@ -11,8 +11,7 @@ fun foo<X>(value: X) : X {
/**
@compilation_should_fail
@stderr while instantiating generic function `foo<slice>`
@stderr while instantiating generic function `bar<slice>`
@stderr in function `bar<slice>`
@stderr can not convert type `int` to return type `slice`
@stderr return 1
*/

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
fun invalidNever(): never {
if (random()) { throw 123; }
}
/**
@compilation_should_fail
@stderr a function returning `never` can not have a reachable endpoint
*/

View file

@ -3,6 +3,7 @@ fun failBitwiseNotOnBool() {
if (~eq) {
return 0;
}
return -1;
}
/**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,12 @@
fun getNullableInt(): int? { return 5; }
fun testCantApplyNotNullForAlwaysNull() {
var x: int? = getNullableInt();
if (x != null) { return 0; }
return x! + 1;
}
/**
@compilation_should_fail
@stderr operator `!` used for always null expression
*/

View file

@ -0,0 +1,15 @@
fun getNullableInt(): int? { return 5; }
fun testFlowContextAppliedInBinaryOperator() {
var x: int? = getNullableInt();
var y: int? = getNullableInt();
if ((y = null) < y) {
return -100;
}
return 0;
}
/**
@compilation_should_fail
@stderr can not apply operator `<` to `null` and `null`
*/

View file

@ -0,0 +1,14 @@
fun getNullableInt(): int? { return 5; }
fun testNeverTypeOccurs() {
var x: int? = getNullableInt();
if (x == null && x != null) {
return x + 0;
}
return 0;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `never` and `int`
*/

View file

@ -0,0 +1,9 @@
fun testLogicalAndNotConditionDoesntAffect(x: int?) {
var gt1 = x != null && x > 1;
return x + 0;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int`
*/

View file

@ -0,0 +1,15 @@
fun getTensor(): (int?, int?) { return (5, null); }
fun testSmartCastsForFieldsDropAfterAssign() {
var t = getTensor();
if (t.0 != null && t.1 != null) {
t = getTensor();
return t.0 + t.1;
}
return -1;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int?`
*/

View file

@ -0,0 +1,16 @@
fun getNullableInt(): int? { return 5; }
fun getTensor(x: int?): (int?, int) { return (x, 0); }
fun testSmartCastsDropAfterAssign() {
var x: int? = 0;
var y: int? = 0;
(getTensor(x = getNullableInt()).0, getTensor(y = getNullableInt()).0) = (x + y, x - y);
return x+y;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int?`
@stderr x + y, x - y
*/

View file

@ -0,0 +1,14 @@
fun takeNullableTensor(mutate ij: (int, int)?) { }
fun testSmartCastsDropAfterMutate() {
var x: (int, int)? = (1, 2);
return x.0; // ok
takeNullableTensor(mutate x);
return x.1; // error
}
/**
@compilation_should_fail
@stderr type `(int, int)?` is not indexable
@stderr return x.1
*/

View file

@ -0,0 +1,12 @@
fun getNullableInt(): int? { return 5; }
fun testAssertThrowIsConditional() {
var (x, y) = (getNullableInt(), getNullableInt());
assert(x != null) throw(y = 10);
return x + y;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int` and `int?`
*/

View file

@ -0,0 +1,18 @@
fun assignNull2<T1, T2>(mutate x: T1?, mutate y: T2?) {
if (false) {
x = null;
y = null;
}
}
fun testSmartCastsDropAfterNullableGeneric() {
var (x: int?, y: int?) = (1, 2);
x * y; // ok
assignNull2(x, y); // treated like assignments to nullable
x << y; // error
}
/**
@compilation_should_fail
@stderr can not apply operator `<<` to `int?` and `int?`
*/

View file

@ -0,0 +1,15 @@
fun getNullableInt(): int? { return 5; }
fun testReassignInRedef() {
var t1: int? = getNullableInt();
if (t1 != null) {
var (t1 redef, t2) = (getNullableInt(), 5);
return t1 + t2;
}
return -1;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int`
*/

View file

@ -0,0 +1,14 @@
fun getNullableInt(): int? { return 5; }
fun testTryBodyDontSmartCast() {
var x = getNullableInt();
try {
x = 5;
} catch {}
return x * 10; // x is not int here; for now, we have no exception edges, assuming it can be anywhere inside try
}
/**
@compilation_should_fail
@stderr can not apply operator `*` to `int?` and `int`
*/

View file

@ -0,0 +1,15 @@
fun getNullableInt(): int? { return 5; }
fun testDoWhileCondition() {
var (x: int?, y: int?) = (10, 20);
do {
x = getNullableInt();
y = getNullableInt();
} while(x == null);
return x * y; // x is 100% int, but y is not
}
/**
@compilation_should_fail
@stderr can not apply operator `*` to `int` and `int?`
*/

View file

@ -0,0 +1,9 @@
fun cantAssignIntToTensor() {
var (x, y) = 2;
x + y;
}
/**
@compilation_should_fail
@stderr can not assign `int` to a tensor
*/

View file

@ -0,0 +1,9 @@
fun cantAssignSizesMismatch() {
var [x, y] = [2, 3, 4];
x + y;
}
/**
@compilation_should_fail
@stderr can not assign `[int, int, int]`, sizes mismatch
*/

View file

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

View file

@ -307,7 +307,7 @@ fun main(){}
...
incrementTwoInPlace CALLDICT // x y sum1
-ROT
10 PUSHINT // sum1 x y '10=10
10 PUSHINT // sum1 x y '11=10
incrementTwoInPlace CALLDICT // sum1 x y sum2
s1 s3 s0 XCHG3 // x y sum1 sum2
}>

View file

@ -0,0 +1,28 @@
fun takeInt(a: int) {}
@method_id(101)
fun test1(x: int?) {
if (x == null && x != null) {
var y = x;
__expect_type(y, "never");
__expect_type(y!, "never");
// `never` type is assignable to anything, flow won't reach this point
var t: (int, int) = x;
t = y;
takeInt(x);
var cb: (int) -> int = x;
x as int?;
x as (int, int)?;
x as never;
return x;
}
return 123;
}
fun main() {
__expect_type(test1, "(int?) -> int");
}
/**
@testcase | 101 | null | 123
*/

View file

@ -2,13 +2,13 @@ import "@stdlib/lisp-lists"
@method_id(101)
fun test1() {
var numbers: tuple = createEmptyList();
var numbers: tuple? = createEmptyList();
numbers = listPrepend(1, numbers);
numbers = listPrepend(2, numbers);
numbers = listPrepend(3, numbers);
numbers = listPrepend(4, numbers);
var (h: int, numbers redef) = listSplit(numbers);
h += listGetHead(numbers);
var (h: int, numbers redef) = listSplit(numbers!);
h += listGetHead(numbers!);
_ = null;
(_, _) = (null, null);
@ -22,22 +22,24 @@ 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;
if (10 < 20) { // always true at runtime (not at compile-time)
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)
@ -45,14 +47,6 @@ fun test3(x: int) {
return myIsNull(x > 10 ? null : x);
}
fun getUntypedNull() {
var untyped: null = null;
if (true) {
return untyped;
}
return untyped;
}
@method_id(104)
fun test4(): null {
var (_, (_, untyped: null)) = (3, (createEmptyTuple, null));
@ -62,23 +56,25 @@ fun test4(): null {
return untyped;
}
@method_id(105)
fun test5() {
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;
// the compiler optimizes this at compile-time
var i: int? = null;
if (i == null) {
return 1;
}
@ -92,7 +88,6 @@ fun main() {
@testcase | 103 | 5 | 5
@testcase | 103 | 15 | -1
@testcase | 104 | | (null)
@testcase | 105 | | 100
@testcase | 107 | | -11
@fif_codegen
"""
@ -120,12 +115,7 @@ fun main() {
"""
main PROC:<{
//
PUSHNULL // i
ISNULL // '2
IFJMP:<{ //
1 PUSHINT // '3=1
}> //
10 PUSHINT // '4=10
1 PUSHINT // '3=1
}>
"""
@ -133,14 +123,14 @@ fun main() {
"""
test7 PROC:<{
...
LDOPTREF // b '8 '7
LDOPTREF // b '9 '8
DROP // b c
ISNULL // b '11
10 MULCONST // b '13
SWAP // '13 b
ISNULL // '13 '14
NOT // '13 '15
ADD // '16
NOT // '13 '14
ADD // '15
}>
"""
*/

View file

@ -0,0 +1,492 @@
fun getNullableInt(): int? { return 5; }
fun sumOfNullableTensorComponents(t: (int, int)?): int {
if (t == null) { return 0; }
return t!.0 + t!.1;
}
fun isTensorNull(t: (int, int)?) {
return t == null;
}
fun incrementNullableTensorComponents(mutate self: (int, int)?): self {
if (self != null) {
self!.0 += 1;
self!.1 += 1;
}
return self;
}
fun incrementTensorComponents(mutate self: (int, int)): self {
self.0 += 1;
self.1 += 1;
return self;
}
fun assignFirstComponent(mutate t: (int, int), first: int) {
t!.0 = first;
}
fun assignFirstComponentNullable(mutate t: (int, int)?, first: int) {
if (t == null) {
t = (first, 0);
} else {
t!.0 = first;
}
}
fun getNullableTensor(firstComponent: int?): (int, int)? {
return firstComponent == null ? null : (firstComponent!, 2);
}
fun sumOfTensor(x: (int, int)) {
return x.0 + x.1;
}
fun assignNullTo<T>(mutate x: T?) {
x = null;
}
fun getTensor12() {
return (1,2);
}
@method_id(101)
fun test101(): (int, int)? {
return (1, 2);
}
@method_id(102)
fun test102(): ((int, int)?, (int, int)?) {
var t = (1, 2);
return (t, null);
}
@method_id(103)
fun test103(t: (int, int)) {
var t2: (int, int)? = t;
return (sumOfNullableTensorComponents(t), sumOfNullableTensorComponents(t2), sumOfNullableTensorComponents(null), t2);
}
@method_id(104)
fun test104() {
var t1_1: (int, int)? = (1, 2);
var t1_2: (int, int)? = t1_1;
var t1_3: (int, int)? = t1_1!;
var t2_1: (int, int)? = getNullableTensor(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;
__expect_type(x2, "(int, int)");
x2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x2);
__expect_type(x2, "(int, int)?");
var x3: (int, int)? = null;
__expect_type(x3, "null");
x3.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x3);
return (x1, x2, x3);
}
fun isTensorNullGen<T1, T2>(t: (T1, T2)?) {
return t == null;
}
@method_id(109)
fun test109() {
var x1 = (1, 2);
var x2: (int, int)? = x1;
var x3: (int, int)? = x1.1 > 10 ? (1, 2) : null;
return (
isTensorNullGen(x1), isTensorNullGen(x2), isTensorNullGen<int,int>(null),
isTensorNullGen<int,int>(x1), isTensorNullGen<int,int>(x3),
x1.isTensorNullGen(), x2.isTensorNullGen(), x3.isTensorNullGen(), null.isTensorNullGen<int,int>()
);
}
global g110_1: (int, int);
global g110_2: (int, int)?;
@method_id(110)
fun test110() {
g110_1 = getNullableTensor(1)!;
incrementTensorComponents(mutate g110_1);
g110_1.incrementTensorComponents();
g110_2 = g110_1;
g110_2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate g110_2);
var tmp = g110_2;
g110_2 = null;
g110_2.incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate g110_2);
return (g110_1, g110_2, tmp);
}
@method_id(111)
fun test111() {
var x = (1, 2);
assignFirstComponent(mutate x, 50);
var x2: (int, int)? = null;
var x3 = x2 as (int, int)?;
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);
}
@method_id(133)
fun test133() {
var x: (int, int)? = (10, 20);
return sumOfTensor(x) + x.0 + x.1; // smart casted
}
@method_id(134)
fun test134(): (int, int)? {
var x: (int, int)? = (10, 20);
incrementTensorComponents(mutate x); // smart casted
return x;
}
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
@testcase | 104 | | 1 2 (null) (null) 0
@testcase | 105 | | (null) (null) (null) 0 1 2 3 -1
@testcase | 106 | | 1 2
@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
@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) 1 3
@testcase | 118 | 5 | 5 10 -1
@testcase | 118 | null | (null) (null) 0
@testcase | 119 | | (null) (null) 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
@testcase | 124 | -1 | 1 (null) (null) 0 4 (null) (null) 0
@testcase | 125 | | 3
@testcase | 126 | | 1 (null) 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 | 133 | | 60
@testcase | 134 | | 11 21 -1
@testcase | 135 | | [ 10 ] [ (null) ] (null) 777 10 -1 (null) -1 (null) 0 777 10 -1 (null) -1 (null) 0 777 0 0 -1 0 0 -1 0 0 -1 777 0 -1 0 0 -1 0
@fif_codegen
"""
isTensorNull PROC:<{
// t.0 t.1 t.NNFlag
2 1 BLKDROP2 // t.NNFlag
0 EQINT // '3
}>
"""
@fif_codegen
"""
test113 PROC:<{
//
1 PUSHINT // '2=1
PUSHNULL // '2=1 '3
PAIR // t
1 INDEX // '5
PUSHNULL // '5 '6
0 PUSHINT // '5 '6 '7=0
isTensorNull CALLDICT // '8
}>
"""
*/

View file

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

View file

@ -0,0 +1,678 @@
// the goal of this file is not only to @testcase results —
// but to check that this file compiles
fun getNullableInt(): int? { return 5; }
fun getNullableSlice(): slice? { return null; }
fun takeNullableInt(a: int?) {}
fun takeNullableSlice(a: slice?) {}
fun increment(mutate self: int) { self += 1; }
fun assignToInt(mutate self: int, value: int) { self = value; }
fun assignToNullableInt(mutate self: int?, value: int) { self = value; }
fun sameTensor(t: (int, int)) { return t; }
fun sameTensor2(t: (int?, (slice, slice, slice, builder)?)) { return t; }
fun eq<T>(v: T) { return v; }
fun getTwo<X>(): X { return 2 as X; }
fun test1(): int {
var x = getNullableInt();
var y = getNullableInt();
if (x != null && y != null) {
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
return -1;
}
fun test2() {
var (x, y) = (getNullableInt(), getNullableInt());
if (x == null || y == null) {
return null;
}
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test3(): int {
var ([x, y]) = [getNullableInt(), getNullableInt()];
if (x != null) {
if (((y)) != null) {
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
return x;
}
if (random() > -1) {
if (y == null) { return -1; }
else { return y; }
}
return 0;
}
fun test4() {
var x = getNullableInt();
if (x != null && x > 0) {
var x = getNullableInt();
if ((x) != null && x + 10 < 0) {
var x = getNullableInt();
return 10 > 3 && 10 < 10 && x != null && x + 8 > 10;
}
}
if (x != null && x < 1) {
return false;
}
if (x == null && x == null) {
__expect_type(x, "null");
return true;
}
return x < x + 3;
}
fun test5() {
var (a, (b, c)) = (getNullableInt(), (getNullableInt(), getNullableInt()));
if (a == null) { return -1; }
if (!(b != null)) { return -2; }
if (random() ? c == null && c == null : c == null) { return -3; }
return a + b + c;
}
fun test6() {
var a: int? = 5;
__expect_type(a, "int");
__expect_type(a != null ? a : null, "int");
__expect_type(a == null ? "" : a, "int");
takeNullableInt(a);
__expect_type(a, "int");
if (random()) {
a = null;
} else {
if (random()) { a = null; }
else { a = null; }
}
__expect_type(a, "null");
takeNullableSlice(a); // ok, `slice?` is `slice | null`, here a definitely null
var b: int? = true ? null : "sl";
__expect_type(b, "null");
takeNullableInt(b);
takeNullableSlice(b); // same reason
var c: int? = 10;
__expect_type(c, "int");
takeNullableSlice(c = null);
}
fun test7() {
var (a, b, c, d) = (getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt());
if (a == null && true) { return -1; }
if (true && true && 1 && !0 && b == null) { return -2; }
if (true ? c == null && (((c))) == null && true : false) { return -3; }
if (!true ? random() > 0 : a != null && (d == null && b != null)) { return -4; }
return a + b + c + d;
}
fun test8(x: int?, y: int?) {
var allGt1 = x != null && x > 1 && y != null && y > 1;
var xGtY = x != null && y != null && x > y;
var xLtEq0 = x == null || x < 0;
(x = 0) < random() || x > 10;
return x + 0;
}
fun test9() {
var x = getNullableInt();
var y = getNullableInt();
if (x == null || y == null) {
return -1;
}
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test10(): int {
var (x, y) = (getNullableInt(), getNullableInt());
if (x == null) {
if (y == null) { return -1; }
__expect_type(x, "null");
__expect_type(y, "int");
return y;
}
if (y == null) {
return x;
}
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test11() {
var [x, y] = [getNullableInt(), getNullableInt()];
if (random()) { return x == null || y == null ? -1 : x + y; }
if (true && (x == null || y == null) && !!true) { return 0; }
return x + y;
}
fun test12() {
var (x, y) = (getNullableInt(), getNullableInt());
if (random() ? x == null || y == null : x == null || y == null) { return -1; }
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test13() {
var x: int? = getNullableInt();
var y: int? = 10;
var z = getNullableInt();
var w = getNullableInt();
beginCell().storeInt(x!, 32).storeInt(x = getNullableInt()!, 32).storeInt(x, 32)
.storeInt(y, 32).storeInt(z = 10, 32).storeInt(x + y + z, 32)
.storeInt(w == null ? -1 : w, 32).storeInt(!(null == w) ? w : -1, 32);
}
fun test14() {
var (x, y) = (getNullableInt(), getNullableInt());
if (x == null) {
x = 0;
}
if (y == null) {
if (random()) { return 0; }
else { y = 0; }
}
return x + y;
}
fun test20() {
var t = (getNullableInt(), getNullableInt());
if (t.0 != null && t.1 != null) {
__expect_type(t.0, "int");
__expect_type(t.1, "int");
return t.0 + t.1;
}
t.0 = 10;
if (t.1 == null) {
t.1 = 20;
}
__expect_type(t.0, "int");
__expect_type(t.1, "int");
return t.0 + t.1;
}
fun test21() {
var t = (getNullableInt(), (getNullableInt(), getNullableInt()));
if (t.0 != null && t.1.0 != null) {
if (t.1.1 != null) { return t.0 + t.1.0 + t.1.1; }
return t.0 + t.1.0;
}
if (t.0 != null) {
return t.0 + 0;
}
__expect_type(t.0, "null");
__expect_type(t.1.0, "int?");
return t.1.0 == null ? -1 : t.1.0 + 0;
}
fun test22() {
var t = (getNullableInt(), (getNullableInt(), getNullableInt()));
if (t.0 == null || t.1.0 == null || t.1.1 == null) {
return -1;
}
return t.0 + t.1.0 + t.1.1;
}
@method_id(123)
fun test23() {
var (x: int?, y: int?, z: int?) = (getNullableInt(), getNullableInt(), getNullableInt());
((x = 1, 0).0, (y = 2, 1).0) = (3, z = 4);
return x + y + z;
}
@method_id(124)
fun test24(x: int?) {
if (x == null) {
__expect_type(x, "null");
assignToNullableInt(mutate x, 10);
__expect_type(x, "int?");
x.assignToNullableInt(x! + 5);
} else {
__expect_type(x, "int");
increment(mutate x);
x.increment();
__expect_type(x, "int");
}
__expect_type(x, "int?");
return x;
}
fun test25() {
var x = (getNullableInt(), getNullableInt(), getNullableInt());
x.0 = x.2 = random();
return (x.0) + ((x.2));
}
fun test26() {
var x = [getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(),
getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt()];
if (~(x.0 = random())) { return; }
if ((x.1 = random()) < (x.2 = random())) { return; }
else if (!(x.2 <=> (x.3 = random()))) { return; }
x.5 = (x.4 = random()) ? (x.6 = random()) : (x.6 = random());
if ((x.7 = random()) as int) { return; }
if (((((x.8 = random()) != null)))) { return; }
if ([x.1, (x.9 = random())!].1) { return; }
val result = x.0+x.1+x.2+x.3+x.4+x.5+x.6+x.7+x.8+x.9;
}
fun test27() {
var (x, _) = ([getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(),
getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt()], []);
+(x.0 = random());
x.0 += [((x.1 = random()) < (x.2 = random() + x.1)) as int].0;
!(x.2 <=> (x.3 = random() + x.2));
x.5 = (x.4 = random()) ? (x.6 = random()) : (x.6 = random());
(x.7 = random()) as int;
(((((x.8 = random()) != null))));
[x.1, (x.9 = random())!].1;
return x.0+x.1+x.2+x.3+x.4+x.5+x.6+x.7+x.8+x.9;
}
fun test28() {
var x = (getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt());
__expect_type((x.0 = random(), x.0 += (x.1 = random()) as int, !(x.1 <=> (x.2 = random() + x.0)) == null, (x.3 = random()) ? x.3 : (!x.3) as int),
"(int, int, bool, int)");
}
fun test29() {
var x = (getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt());
__expect_type([x.0 = random(), ((x.0 += (x.1 = random()) as int)), !(x.1 <=> (x.2 = random() + x.0)) == null, (x.3 = random()) ? x.3 : (!x.3) as int],
"[int, int, bool, int]");
}
@method_id(130)
fun test30(initial5: bool) {
var t: (int?, (int?, (int?, int?))) = initial5
? (getNullableInt(), (getNullableInt(), (getNullableInt(), getNullableInt())))
: (null, (null, (null, null)));
if (t.0 == null || t.1.0 == null || t.1.1.0 == null || t.1.1.1 == null) {
if (t.1.0 == null || t.1.1.0 == null) {
if (t.1.1.0 == null) {
t.1.1.0 = 4;
}
__expect_type(t.1.1.0, "int");
__expect_type(t.1.1.1, "int?");
__expect_type(t.1.0, "int?");
t.1.1.1 = 3;
t.1.0 = 2;
__expect_type(t.1.1.1, "int");
__expect_type(t.1.0, "int");
}
if (((((t.1.1.1)))) != null) {}
else { t.1.1.1 = 3; }
t.0 = 1;
}
return t.0 + t.1.0 + t.1.1.0 + t.1.1.1;
}
fun test31() {
var t = (getNullableInt(), getNullableInt());
t.0 == null ? (t.0, t.1) = (1, 2) : (t.1, t.0) = (4, 3);
return t.0 + t.1;
}
@method_id(132)
fun test32() {
var t: (int?, (int?, int?)?, (int?, int?)) = (getNullableInt(), (getNullableInt(), getNullableInt()), (getNullableInt(), getNullableInt()));
if (t.0 == null) { return -1; }
t.1 != null && t.1.0 == null ? t.1 = (1, 2) : t.1 = (3, 4);
if (t.2.1 != null) { t.2.0 = 1; t.2.1 = 2; }
else { [t.2.0, t.2.1] = [3, 4]; }
return t.0 + t.1.0! + t.1.1! + t.2.0 + t.2.1;
}
@method_id(133)
fun test33(): int {
var x = getNullableInt();
repeat (eq(x = 5)) {
__expect_type(x, "int");
increment(mutate x);
}
return x;
}
fun test34() {
var (x, y) = (getNullableInt(), getNullableInt());
if (random()) { throw (x = 1, y = 2); }
else { throw (x = 3, y = (1, getNullableInt()!).1); }
return x + y;
}
fun test35() {
var (x, y, z, t) = (getNullableInt(), getNullableInt(), getNullableInt(), (getNullableInt(), getNullableInt()));
assert (x != null, 404);
assert (t.0 != null && true && !(t.1 == null) && !(z = 4)) throw (y = 404);
__expect_type(y, "int?");
return x + t.0 + t.1 + z;
}
fun test36() {
var x = getNullableInt();
assert (x == null, x + 0); // check that x is int there
__expect_type(x, "null");
}
fun test37() {
var (x, code) = (getNullableInt()!, getNullableInt());
try {
} catch(code) {
x = 20;
return x + code; // code is scoped
}
return code == null ? x : x + code;
}
fun assignNull2<T1, T2>(mutate x: T1?, mutate y: T2?) {
x = null;
y = null;
}
fun test38() {
var (x: int?, y: int?) = (1, 2);
__expect_type(x, "int");
__expect_type(y, "int");
assignNull2<int, int>(mutate x, mutate y);
__expect_type(x, "int?");
__expect_type(y, "int?");
if (x != null) {
if (y == null) { return -1; }
return x + y;
}
var t: (int?, slice?) = (null, null);
if (!false) { t.0 = 1; }
if (true) { t.1 = beginCell().endCell().beginParse(); }
__expect_type(t.0, "int");
__expect_type(t.1, "slice");
t.0 + t.1.loadInt(32);
assignNull2(mutate t.0, mutate t.1);
__expect_type(t.0, "int?");
__expect_type(t.1, "slice?");
t.0 != null && t.1 != null ? t.0 + loadInt(mutate t.1, 32) : -1;
return t.0 != null && t.1 != null ? t.0 + loadInt(mutate t.1, 32) : -1;
}
@method_id(139)
fun test39() {
var x: (int?, int?)? = (4, null);
x.1 = 10;
x.1 += 1;
x!.1 += 1;
return (x!.0! + x.1);
}
@method_id(140)
fun test40(second: int?) {
var x: (int?, int?)? = (4, second);
if (x.1 != null) {
val result = x.1 + x!.1 + x!!.1 + x.1! + x!!.1!!;
}
if (x!.1 != null) {
val result = x.1 + x!.1 + x!!.1 + x.1! + x!!.1!!;
}
if (!(x!!.1 != null)) {
return -1;
}
return x.1 + x!.1 + x!!.1 + x.1! + x!!.1!!;
}
@method_id(141)
fun test41() {
var t: (int, int)? = null;
return sameTensor(t = (1, 2));
}
@method_id(142)
fun test42() {
var t: (int?, (int?, (int, int)?)?) = (getNullableInt(), (1, (2, 3)));
t.1 = (3,null);
__expect_type(t.1, "(int?, (int, int)?)");
__expect_type(t, "(int?, (int?, (int, int)?)?)");
return (t, t.1);
}
@method_id(143)
fun test43() {
var t1: ((int, int), int?) = ((1, 2), 3);
var t2: ((int?, int?), (int?,int?)?) = ((null, null), (null, 5));
t2.0 = t1.0 = (10, 11);
t2.1 = t1.1 = null;
return (t1, t2);
}
@method_id(144)
fun test44() {
var t1: ((int, int), int?) = ((1, 2), 3);
var t2: ((int?, int?), (int?,int?)?) = ((null, null), (null, 5));
t1.0 = t2.0 = (10, 11);
t1.1 = t2.1 = null;
__expect_type(t1, "((int, int), int?)");
__expect_type(t2, "((int?, int?), (int?, int?)?)");
return (t1, t2);
}
@method_id(145)
fun test45() {
var t: (int?, (int?, (int, int)?)?) = (getNullableInt(), (1, (2, 3)));
var t2 = sameTensor2(t.1 = (3,null));
return (t, t2, t.1);
}
fun autoInfer46() {
var t1: int? = 3;
var t2: (int, int)? = (4, 5);
__expect_type(t1, "int");
__expect_type(t2, "(int, int)");
return (t1, t2); // proven to be not null, inferred (int, (int,int))
}
@method_id(146)
fun test46() {
var r46_1: (int, (int,int)) = autoInfer46();
var r46_2: (int, (int,int)?) = autoInfer46();
return (r46_1, r46_2);
}
@method_id(147)
fun test47() {
var t1: int? = 3;
var t2: (int, int)? = (4, 5);
t1 = t2 = null;
__expect_type(t1, "null");
__expect_type(t2, "null");
var result = (t1, t2); // proven to be always null, inferred (null, null), 2 slots on a stack
return (result, 100, result.1, 100, t2 as (int, int)?);
}
fun test48() {
var t1: int? = getNullableInt();
if (t1 != null) {
var (t1 redef, t2) = (10, 5);
return t1 + t2;
var t2 redef = getNullableInt()!;
return t1 + t2;
}
return -1;
}
fun test49(x: int?) {
while (x == null) {
x = getNullableInt();
}
__expect_type(x, "int");
return x + 1;
}
fun test50() {
var (x: int?, y: int?) = (1, 2);
do {
x = getNullableInt();
y = getNullableInt();
} while (x == null || y == null);
return x + y;
}
fun test51() {
while (true) { return; }
// test that no error "control reaches end of function"
}
fun test52() {
do { } while (true);
}
fun test53() {
var x1: int? = getNullableInt();
var x2: int? = 5;
var x3: int? = 5;
var x10: int? = null;
var x11: int? = 5;
var x12: int? = 5;
while (x1 != null) {
__expect_type(x1, "int"); // because condition
__expect_type(x2, "int?"); // because re-assigned
__expect_type(x3, "int?"); // because re-assigned
__expect_type(x10, "null");
__expect_type(x11, "int");
x1 = getNullableInt();
__expect_type(x1, "int?");
assignToNullableInt(mutate x2, 5);
x3.assignToNullableInt(5);
x11 = 10;
assignToInt(mutate x12, 5);
}
__expect_type(x1, "null");
__expect_type(x2, "int?");
__expect_type(x3, "int?");
}
fun test54() {
var x1: int? = null;
var x2: int? = 5;
var x3: int? = 5;
var x10: int? = null;
var x11: int? = 5;
var x12: int? = 5;
do {
__expect_type(x1, "int?"); // because re-assigned
__expect_type(x2, "int?"); // because re-assigned
__expect_type(x3, "int?"); // because re-assigned
__expect_type(x10, "null");
__expect_type(x11, "int");
x1 = getNullableInt();
__expect_type(x1, "int?");
assignToNullableInt(mutate x2, 5);
if (random()) { x3.assignToNullableInt(5); }
x11 = 10;
assignToInt(mutate x12, 5);
} while (x1 != null);
__expect_type(x1, "null");
__expect_type(x2, "int?");
__expect_type(x3, "int?");
}
fun eq55<T>(v: T) { return v; }
fun test55() {
var x: int? = 4;
while (true) {
// currently, generic functions are instantiated at the type inferring step
// in case of loops, type inferring is re-enterable
// first iteration: x is int, eq<int> instantiated
// second (final) iteration: x is int?, eq<int?> instantiated
// (checked via codegen)
eq55(x);
__expect_type(x, "int?"); // types are checked (unlike generics instantiated) after inferring
x = random() ? 1 : null;
}
__expect_type(x, "int?");
}
fun test56() {
var i: int? = null;
var (j: int?, k: int?) = (null, null);
__expect_type(i, "null");
__expect_type(k, "null");
i = getTwo();
[j, ((k))] = [getTwo(), ((getTwo()))];
__expect_type(i, "int?");
__expect_type(j, "int?");
__expect_type(k, "int?");
}
fun test57(mutate x: int?): int {
if (x == null) { x = 5; }
else {
if (x < 10) { x = 10; }
else { x = 20; }
}
if (x != null) {
return 123;
}
__expect_type(x, "int");
// no "return" needed, because end of function is unreachable
}
@method_id(158)
fun test58() {
var (x1, x2: int?) = (getNullableInt(), null);
return (test57(mutate x1), x1, test57(mutate x2), x2);
}
fun test59() {
var (x1: int?, x2, x3) = (getNullableInt()!, getNullableInt(), 5);
if ((x2 = x3) != null) {
__expect_type(x2, "int");
}
__expect_type(x2, "int");
if ((x2 = getNullableInt()) != null) {
__expect_type(x2, "int");
}
__expect_type(x2, "int?");
if (((x1) = x2) == null) {
return;
}
__expect_type(x1, "int");
}
fun main(x: int?): int {
return x == null ? -1 : x;
}
/**
@testcase | 0 | 1 | 1
@testcase | 123 | | 7
@testcase | 124 | 4 | 6
@testcase | 124 | null | 15
@testcase | 130 | -1 | 20
@testcase | 130 | 0 | 10
@testcase | 132 | | 15
@testcase | 133 | | 10
@testcase | 139 | | 16
@testcase | 140 | 5 | 25
@testcase | 141 | | 1 2
@testcase | 142 | | 5 3 (null) (null) 0 -1 3 (null) (null) 0
@testcase | 143 | | 10 11 (null) 10 11 (null) (null) 0
@testcase | 144 | | 10 11 (null) 10 11 (null) (null) 0
@testcase | 145 | | 5 3 (null) (null) 0 -1 3 (null) (null) (null) (null) 0 3 (null) (null) 0
@testcase | 146 | | 3 4 5 3 4 5 -1
@testcase | 147 | | (null) (null) 100 (null) 100 (null) (null) 0
@testcase | 158 | | 123 10 123 5
@stderr warning: expression of type `int` is always not null, this condition is always true
@stderr warning: unreachable code
@stderr var t2 redef = getNullableInt()!;
@fif_codegen eq55<int?> PROC:<{
@fif_codegen eq55<int> PROC:<{
*/

View file

@ -164,6 +164,78 @@ fun test109(): (int, int) {
return (g_reg, l_reg);
}
fun alwaysThrow123(): never {
throw 123;
}
fun alwaysThrowX(x: int): never {
if (x > 10) { throw (x, beginCell()); }
else { throw (x, null); }
}
fun anotherNever(throw123: bool): never {
if (throw123) { alwaysThrow123(); }
alwaysThrowX(456);
}
fun testCodegen1(x: int) {
if (x > 10) {
throw 123;
anotherNever(true); // unreachable, will be dropped
}
else if (x < 10) {
throw x;
return -123; // unreachable, will be dropped
}
return 0;
}
fun testCodegen2(x: int) {
if (x > 10) {
alwaysThrow123();
anotherNever(true); // unreachable, will be dropped
}
else if (x < 10) {
anotherNever(false);
return -123; // unreachable, will be dropped
}
return 0;
}
@method_id(110)
fun test110(b: bool) {
try {
if (b == true) { testCodegen1(100); }
testCodegen1(5);
return -1;
} catch (ex) {
return ex;
}
}
@method_id(111)
fun test111(b: bool) {
try {
if (b == true) { testCodegen2(100); }
testCodegen2(5);
return -1;
} catch (ex) {
return ex;
}
}
fun mySetCode(newCode: slice): void
asm "SETCODE";
fun testCodegen3(numberId: int, paramVal: cell) {
if (numberId == -1000) {
var cs = paramVal.beginParse();
mySetCode(cs);
throw 0;
}
paramVal.beginParse();
}
fun main() {
}
@ -187,6 +259,65 @@ fun main() {
@testcase | 107 | 5 | 5
@testcase | 107 | 20 | 20
@testcase | 108 | | 0
@testcase | 109 | | 10 10
@testcase | 110 | -1 | 123
@testcase | 110 | 0 | 5
@testcase | 111 | -1 | 123
@testcase | 111 | 0 | 456
@code_hash 39307974281105539319288356721945232226028429128341177951717392648324358675585
@code_hash 57361460846265694653029920796509802052573595128418810728101968091567195330515
@fif_codegen
"""
testCodegen1 PROC:<{
// x
DUP // x x
10 GTINT // x '2
IFJMP:<{ // x
123 THROW
}> // x
DUP // x x
10 LESSINT // x '6
IFJMP:<{ // x
THROWANY
}> // x
DROP //
0 PUSHINT // '8=0
}>
"""
@fif_codegen
"""
testCodegen2 PROC:<{
// x
DUP // x x
10 GTINT // x '2
IFJMP:<{ // x
DROP //
alwaysThrow123 CALLDICT
}> // x
10 LESSINT // '5
IFJMP:<{ //
FALSE // '6
anotherNever CALLDICT
}> //
0 PUSHINT // '8=0
}>
"""
@fif_codegen
"""
testCodegen3 PROC:<{
// numberId paramVal
SWAP
-1000 PUSHINT // paramVal numberId '2=-1000
EQUAL // paramVal '3
IFJMP:<{ // paramVal
CTOS // cs
SETCODE
0 THROW
}> // paramVal
DROP //
}>
"""
*/

View file

@ -0,0 +1,22 @@
fun main(x: int?) {
if (x != null && x == null) {
return 1 + 2;
}
if (x == null) {
return -1;
}
if (x != null) {
return -2;
}
return 3 + 4;
}
/**
@testcase | 0 | 5 | -2
@testcase | 0 | null | -1
@stderr warning: variable `x` of type `int` is always not null
@stderr if (x != null)
@stderr warning: unreachable code
@stderr return 3 + 4
*/

View file

@ -0,0 +1,24 @@
fun alwaysThrows(): never {
throw 456;
}
fun testUnreachable(x: int) {
if (x) { throw 123; }
else { alwaysThrows(); }
return 1;
}
fun main() {
try {
testUnreachable(100);
throw 80;
} catch (excNo) {
return excNo;
}
}
/**
@testcase | 0 | | 123
@stderr warning: unreachable code
@stderr return 1;
*/

View file

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

View file

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

View file

@ -0,0 +1,28 @@
fun getNullableInt(): int? { return null; }
fun main() {
var c: int? = 6;
__expect_type(c, "int");
if (c == null) {}
var d: int? = c;
if (((d)) != null && tupleSize(createEmptyTuple())) {}
var e: int? = getNullableInt();
if (e != null) {
return true;
}
__expect_type(e, "null");
null == e;
return null != null;
}
/**
@testcase | 0 | | 0
@stderr warning: variable `c` of type `int` is always not null, this condition is always false
@stderr warning: variable `d` of type `int` is always not null, this condition is always true
@stderr warning: variable `e` is always null, this condition is always true
@stderr warning: expression is always null, this condition is always false
*/

View file

@ -0,0 +1,26 @@
fun main() {
var (a, b, c, d, e) = (1, beginCell(), beginCell().endCell().beginParse(), [1], true as bool?);
var alwaysInt = a != null ? 1 : null;
__expect_type(alwaysInt, "int");
if (!(c == null)) {
if (10 < 3) { assert(b == null, 100); }
}
while (d == null || false) {}
return e! != null;
}
/**
@testcase | 0 | | -1
@stderr warning: variable `a` of type `int` is always not null, this condition is always true
@stderr warning: condition of ternary operator is always true
@stderr warning: variable `c` of type `slice` is always not null, this condition is always false
@stderr warning: condition of `if` is always true
@stderr warning: variable `b` of type `builder` is always not null, this condition is always false
@stderr warning: condition of `assert` is always false
@stderr warning: condition of `while` is always false
@stderr warning: expression of type `bool` is always not null, this condition is always true
*/

View file

@ -12,8 +12,8 @@ set(TOLK_SOURCE
pipe-register-symbols.cpp
pipe-resolve-identifiers.cpp
pipe-calc-rvalue-lvalue.cpp
pipe-detect-unreachable.cpp
pipe-infer-types-and-calls.cpp
pipe-check-inferred-types.cpp
pipe-refine-lvalue-for-mutate.cpp
pipe-check-rvalue-lvalue.cpp
pipe-check-pure-impure.cpp
@ -23,6 +23,7 @@ set(TOLK_SOURCE
pipe-find-unused-symbols.cpp
pipe-generate-fif-output.cpp
type-system.cpp
smart-casts-cfg.cpp
generics-helpers.cpp
abscode.cpp
analyzer.cpp

View file

@ -402,7 +402,7 @@ void CodeBlob::print(std::ostream& os, int flags) const {
std::vector<var_idx_t> CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name) {
std::vector<var_idx_t> 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<TypeDataTensor>()) {
for (int i = 0; i < t_tensor->size(); ++i) {
@ -410,7 +410,11 @@ std::vector<var_idx_t> CodeBlob::create_var(TypePtr var_type, SrcLocation loc, s
std::vector<var_idx_t> nested = create_var(t_tensor->items[i], loc, std::move(sub_name));
ir_idx.insert(ir_idx.end(), nested.begin(), nested.end());
}
} else if (var_type != TypeDataVoid::create()) {
} else if (const TypeDataNullable* t_nullable = var_type->try_as<TypeDataNullable>(); 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() && var_type != TypeDataNever::create()) {
#ifdef TOLK_DEBUG
tolk_assert(stack_w == 1);
#endif

View file

@ -20,6 +20,13 @@
namespace tolk {
// functions returning "never" are assumed to interrupt flow
// for instance, variables after their call aren't considered used
// its main purpose is `throw` statement, it's a call to a built-in `__throw` function
static bool does_function_always_throw(FunctionPtr fun_ref) {
return fun_ref->declared_return_type == TypeDataNever::create();
}
/*
*
* ANALYZE AND PREPROCESS ABSTRACT CODE
@ -262,17 +269,6 @@ VarDescrList& VarDescrList::operator|=(const VarDescrList& y) {
}
}
VarDescrList& VarDescrList::operator&=(const VarDescrList& values) {
for (const VarDescr& vd : values.list) {
VarDescr* item = operator[](vd.idx);
if (item) {
*item &= vd;
}
}
unreachable |= values.unreachable;
return *this;
}
VarDescrList& VarDescrList::import_values(const VarDescrList& values) {
if (values.unreachable) {
set_unreachable();
@ -326,6 +322,17 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
}
return std_compute_used_vars(true);
}
if (cl == _Call && does_function_always_throw(f_sym)) {
VarDescrList new_var_info; // empty, not next->var_info
if (args.size() == right.size()) {
for (const VarDescr& arg : args) {
new_var_info.add_var(arg.idx, arg.is_unused());
}
} else {
new_var_info.add_vars(right, false);
}
return set_var_info(std::move(new_var_info));
}
return std_compute_used_vars();
}
case _SetGlob: {
@ -516,20 +523,19 @@ bool prune_unreachable(std::unique_ptr<Op>& ops) {
case Op::_SliceConst:
case Op::_GlobVar:
case Op::_SetGlob:
case Op::_Call:
case Op::_CallInd:
case Op::_Tuple:
case Op::_UnTuple:
case Op::_Import:
case Op::_Let:
reach = true;
break;
case Op::_Let: {
reach = true;
break;
}
case Op::_Return:
reach = false;
break;
case Op::_Call:
reach = !does_function_always_throw(op.f_sym);
break;
case Op::_If: {
// if left then block0 else block1; ...
VarDescr* c_var = op.var_info[op.left[0]];
@ -712,6 +718,9 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
values.add_newval(i);
}
}
if (does_function_always_throw(f_sym)) {
values.set_unreachable();
}
break;
}
case _Tuple:
@ -860,10 +869,11 @@ bool Op::mark_noreturn() {
case _SetGlob:
case _GlobVar:
case _CallInd:
case _Call:
return set_noreturn(next->mark_noreturn());
case _Return:
return set_noreturn();
case _Call:
return set_noreturn(next->mark_noreturn() || does_function_always_throw(f_sym));
case _If:
case _TryCatch:
// note, that & | (not && ||) here and below is mandatory to invoke both left and right calls

View file

@ -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<ast_binary_operator> 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<ast_binary_operator> 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<ast_identifier>(v->loc, "__isNull"); // built-in function
auto v_ref = createV<ast_reference>(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<ast_argument>(v->loc, v_null, false);
AnyExprV v_isNull = createV<ast_function_call>(v->loc, v_ref, createV<ast_argument_list>(v->loc, {v_arg}));
if (v->tok == tok_neq) {
v_isNull = createV<ast_unary_operator>(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<ast_is_null_check>(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<ast_function_call>(lhs->loc, lhs, parse_argument_list(lex));
} else if (lex.tok() == tok_logical_not) {
lex.next();
lhs = createV<ast_not_null_operator>(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<ast_function_call>(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<ast_dot_access>(loc, lhs, v_ident, v_instantiationTs);
while (lex.tok() == tok_oppar) {
lhs = createV<ast_function_call>(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<ast_binary_operator>(loc, operator_name, t, lhs, rhs);
if (t == tok_eq || t == tok_neq) {
lhs = maybe_replace_eq_null_with_isNull_call(lhs->as<ast_binary_operator>());
lhs = maybe_replace_eq_null_with_isNull_check(lhs->as<ast_binary_operator>());
}
}
return lhs;

View file

@ -108,6 +108,8 @@ protected:
virtual AnyExprV replace(V<ast_binary_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_ternary_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_cast_as_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_not_null_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_is_null_check> v) { return replace_children(v); }
// statements
virtual AnyV replace(V<ast_empty_statement> v) { return replace_children(v); }
virtual AnyV replace(V<ast_sequence> v) { return replace_children(v); }
@ -144,6 +146,8 @@ protected:
case ast_binary_operator: return replace(v->as<ast_binary_operator>());
case ast_ternary_operator: return replace(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return replace(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return replace(v->as<ast_not_null_operator>());
case ast_is_null_check: return replace(v->as<ast_is_null_check>());
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<ast_function_declaration> v_function) {
void start_replacing_in_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
replace(v_function->get_body());
}
};
const std::vector<const FunctionData*>& get_all_not_builtin_functions();
const std::vector<FunctionPtr>& get_all_not_builtin_functions();
template<class BodyReplacerT>
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<ast_function_declaration>());
}

View file

@ -121,6 +121,12 @@ protected:
virtual V<ast_cast_as_operator> clone(V<ast_cast_as_operator> v) {
return createV<ast_cast_as_operator>(v->loc, clone(v->get_expr()), clone(v->cast_to_type));
}
virtual V<ast_not_null_operator> clone(V<ast_not_null_operator> v) {
return createV<ast_not_null_operator>(v->loc, clone(v->get_expr()));
}
virtual V<ast_is_null_check> clone(V<ast_is_null_check> v) {
return createV<ast_is_null_check>(v->loc, clone(v->get_expr()), v->is_negated);
}
// statements
@ -200,6 +206,8 @@ protected:
case ast_binary_operator: return clone(v->as<ast_binary_operator>());
case ast_ternary_operator: return clone(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return clone(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return clone(v->as<ast_not_null_operator>());
case ast_is_null_check: return clone(v->as<ast_is_null_check>());
default:
throw UnexpectedASTNodeType(v, "ASTReplicatorFunction::clone");
}

View file

@ -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"},
@ -193,7 +195,7 @@ class ASTStringifier final : public ASTVisitor {
}
case ast_local_var_lhs: {
std::ostringstream os;
os << (v->as<ast_local_var_lhs>()->inferred_type ? v->as<ast_local_var_lhs>()->inferred_type : v->as<ast_local_var_lhs>()->declared_type);
os << (v->as<ast_local_var_lhs>()->inferred_type ? v->as<ast_local_var_lhs>()->inferred_type->as_human_readable() : v->as<ast_local_var_lhs>()->declared_type->as_human_readable());
if (v->as<ast_local_var_lhs>()->get_name().empty()) {
return "_: " + os.str();
}
@ -268,6 +270,8 @@ public:
case ast_binary_operator: return handle_vertex(v->as<ast_binary_operator>());
case ast_ternary_operator: return handle_vertex(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return handle_vertex(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return handle_vertex(v->as<ast_not_null_operator>());
case ast_is_null_check: return handle_vertex(v->as<ast_is_null_check>());
// statements
case ast_empty_statement: return handle_vertex(v->as<ast_empty_statement>());
case ast_sequence: return handle_vertex(v->as<ast_sequence>());

View file

@ -109,6 +109,8 @@ protected:
virtual void visit(V<ast_binary_operator> v) { return visit_children(v); }
virtual void visit(V<ast_ternary_operator> v) { return visit_children(v); }
virtual void visit(V<ast_cast_as_operator> v) { return visit_children(v); }
virtual void visit(V<ast_not_null_operator> v) { return visit_children(v); }
virtual void visit(V<ast_is_null_check> v) { return visit_children(v); }
// statements
virtual void visit(V<ast_empty_statement> v) { return visit_children(v); }
virtual void visit(V<ast_sequence> v) { return visit_children(v); }
@ -146,6 +148,8 @@ protected:
case ast_binary_operator: return visit(v->as<ast_binary_operator>());
case ast_ternary_operator: return visit(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return visit(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return visit(v->as<ast_not_null_operator>());
case ast_is_null_check: return visit(v->as<ast_is_null_check>());
// statements
case ast_empty_statement: return visit(v->as<ast_empty_statement>());
case ast_sequence: return visit(v->as<ast_sequence>());
@ -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<ast_function_declaration> v_function) {
virtual void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
visit(v_function->get_body());
}
};
const std::vector<const FunctionData*>& get_all_not_builtin_functions();
const std::vector<FunctionPtr>& get_all_not_builtin_functions();
template<class BodyVisitorT>
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<ast_function_declaration>());
}

View file

@ -117,11 +117,16 @@ void ASTNodeExpressionBase::assign_lvalue_true() {
this->is_lvalue = true;
}
void ASTNodeExpressionBase::assign_always_true_or_false(int flow_true_false_state) {
this->is_always_true = flow_true_false_state == 1; // see smart-casts-cfg.h
this->is_always_false = flow_true_false_state == 2;
}
void Vertex<ast_reference>::assign_sym(const Symbol* sym) {
this->sym = sym;
}
void Vertex<ast_function_call>::assign_fun_ref(const FunctionData* fun_ref) {
void Vertex<ast_function_call>::assign_fun_ref(FunctionPtr fun_ref) {
this->fun_maybe = fun_ref;
}
@ -129,7 +134,7 @@ void Vertex<ast_cast_as_operator>::assign_resolved_type(TypePtr cast_to_type) {
this->cast_to_type = cast_to_type;
}
void Vertex<ast_global_var_declaration>::assign_var_ref(const GlobalVarData* var_ref) {
void Vertex<ast_global_var_declaration>::assign_var_ref(GlobalVarPtr var_ref) {
this->var_ref = var_ref;
}
@ -137,7 +142,7 @@ void Vertex<ast_global_var_declaration>::assign_resolved_type(TypePtr declared_t
this->declared_type = declared_type;
}
void Vertex<ast_constant_declaration>::assign_const_ref(const GlobalConstData* const_ref) {
void Vertex<ast_constant_declaration>::assign_const_ref(GlobalConstPtr const_ref) {
this->const_ref = const_ref;
}
@ -149,7 +154,7 @@ void Vertex<ast_instantiationT_item>::assign_resolved_type(TypePtr substituted_t
this->substituted_type = substituted_type;
}
void Vertex<ast_parameter>::assign_param_ref(const LocalVarData* param_ref) {
void Vertex<ast_parameter>::assign_param_ref(LocalVarPtr param_ref) {
this->param_ref = param_ref;
}
@ -157,23 +162,31 @@ void Vertex<ast_parameter>::assign_resolved_type(TypePtr declared_type) {
this->declared_type = declared_type;
}
void Vertex<ast_set_assign>::assign_fun_ref(const FunctionData* fun_ref) {
void Vertex<ast_set_assign>::assign_fun_ref(FunctionPtr fun_ref) {
this->fun_ref = fun_ref;
}
void Vertex<ast_unary_operator>::assign_fun_ref(const FunctionData* fun_ref) {
void Vertex<ast_unary_operator>::assign_fun_ref(FunctionPtr fun_ref) {
this->fun_ref = fun_ref;
}
void Vertex<ast_binary_operator>::assign_fun_ref(const FunctionData* fun_ref) {
void Vertex<ast_binary_operator>::assign_fun_ref(FunctionPtr fun_ref) {
this->fun_ref = fun_ref;
}
void Vertex<ast_is_null_check>::assign_is_negated(bool is_negated) {
this->is_negated = is_negated;
}
void Vertex<ast_sequence>::assign_first_unreachable(AnyV first_unreachable) {
this->first_unreachable = first_unreachable;
}
void Vertex<ast_dot_access>::assign_target(const DotTarget& target) {
this->target = target;
}
void Vertex<ast_function_declaration>::assign_fun_ref(const FunctionData* fun_ref) {
void Vertex<ast_function_declaration>::assign_fun_ref(FunctionPtr fun_ref) {
this->fun_ref = fun_ref;
}
@ -181,7 +194,7 @@ void Vertex<ast_function_declaration>::assign_resolved_type(TypePtr declared_ret
this->declared_return_type = declared_return_type;
}
void Vertex<ast_local_var_lhs>::assign_var_ref(const LocalVarData* var_ref) {
void Vertex<ast_local_var_lhs>::assign_var_ref(LocalVarPtr var_ref) {
this->var_ref = var_ref;
}

View file

@ -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,
@ -184,11 +186,14 @@ struct ASTNodeExpressionBase : ASTNodeBase {
TypePtr inferred_type = nullptr;
bool is_rvalue: 1 = false;
bool is_lvalue: 1 = false;
bool is_always_true: 1 = false; // inside `if`, `while`, ternary condition, `== null`, etc.
bool is_always_false: 1 = false; // (when expression is guaranteed to be always true or always false)
ASTNodeExpressionBase* mutate() const { return const_cast<ASTNodeExpressionBase*>(this); }
void assign_inferred_type(TypePtr type);
void assign_rvalue_true();
void assign_lvalue_true();
void assign_always_true_or_false(int flow_true_false_state);
ASTNodeExpressionBase(ASTNodeType type, SrcLocation loc) : ASTNodeBase(type, loc) {}
};
@ -408,7 +413,7 @@ private:
V<ast_identifier> 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 +422,7 @@ public:
std::string_view get_name() const { return identifier->name; } // empty for underscore
Vertex* mutate() const { return const_cast<Vertex*>(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<ast_identifier> identifier, TypePtr declared_type, bool is_immutable, bool marked_as_redef)
@ -530,12 +535,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<FunctionData*>(nullptr); // filled at type inferring
bool is_target_fun_ref() const { return std::holds_alternative<const FunctionData*>(target); }
bool is_target_fun_ref() const { return std::holds_alternative<FunctionPtr>(target); }
bool is_target_indexed_access() const { return std::holds_alternative<int>(target); }
AnyExprV get_obj() const { return child; }
@ -560,7 +565,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<ast_function_call> 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 +575,7 @@ struct Vertex<ast_function_call> final : ASTExprBinary {
auto get_arg(int i) const { return rhs->as<ast_argument_list>()->get_arg(i); }
Vertex* mutate() const { return const_cast<Vertex*>(this); }
void assign_fun_ref(const FunctionData* fun_ref);
void assign_fun_ref(FunctionPtr fun_ref);
Vertex(SrcLocation loc, AnyExprV lhs_f, V<ast_argument_list> arguments)
: ASTExprBinary(ast_function_call, loc, lhs_f, arguments) {}
@ -603,7 +608,7 @@ template<>
// ast_set_assign represents assignment-and-set operation "lhs <op>= rhs"
// examples: `a += 4` / `b <<= c`
struct Vertex<ast_set_assign> 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 +616,7 @@ struct Vertex<ast_set_assign> final : ASTExprBinary {
AnyExprV get_rhs() const { return rhs; }
Vertex* mutate() const { return const_cast<Vertex*>(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 +627,14 @@ template<>
// ast_unary_operator is "some operator over one expression"
// examples: `-1` / `~found`
struct Vertex<ast_unary_operator> 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<Vertex*>(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 +646,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<ast_binary_operator> 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 +654,7 @@ struct Vertex<ast_binary_operator> final : ASTExprBinary {
AnyExprV get_rhs() const { return rhs; }
Vertex* mutate() const { return const_cast<Vertex*>(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 +689,32 @@ struct Vertex<ast_cast_as_operator> 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<ast_not_null_operator> 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<ast_is_null_check> final : ASTExprUnary {
bool is_negated;
AnyExprV get_expr() const { return child; }
Vertex* mutate() const { return const_cast<Vertex*>(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) {}
};
//
// ---------------------------------------------------------
@ -706,10 +737,14 @@ template<>
// example: do while body is a sequence
struct Vertex<ast_sequence> final : ASTStatementVararg {
SrcLocation loc_end;
AnyV first_unreachable = nullptr;
const std::vector<AnyV>& get_items() const { return children; }
AnyV get_item(int i) const { return children.at(i); }
Vertex* mutate() const { return const_cast<Vertex*>(this); }
void assign_first_unreachable(AnyV first_unreachable);
Vertex(SrcLocation loc, SrcLocation loc_end, std::vector<AnyV> items)
: ASTStatementVararg(ast_sequence, loc, std::move(items))
, loc_end(loc_end) {}
@ -892,7 +927,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<ast_parameter> 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 +935,7 @@ struct Vertex<ast_parameter> final : ASTOtherLeaf {
bool is_underscore() const { return param_name.empty(); }
Vertex* mutate() const { return const_cast<Vertex*>(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 +986,7 @@ struct Vertex<ast_function_declaration> final : ASTOtherVararg {
auto get_param(int i) const { return children.at(1)->as<ast_parameter_list>()->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<ast_genericsT_list> genericsT_list; // for non-generics it's nullptr
td::RefInt256 method_id; // specified via @method_id annotation
@ -962,7 +997,7 @@ struct Vertex<ast_function_declaration> final : ASTOtherVararg {
bool is_builtin_function() const { return children.at(2)->type == ast_empty_statement; }
Vertex* mutate() const { return const_cast<Vertex*>(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<ast_identifier> name_identifier, V<ast_parameter_list> parameters, AnyV body, TypePtr declared_return_type, V<ast_genericsT_list> genericsT_list, td::RefInt256 method_id, int flags)
@ -975,13 +1010,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<ast_global_var_declaration> 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<ast_identifier>(); }
Vertex* mutate() const { return const_cast<Vertex*>(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<ast_identifier> name_identifier, TypePtr declared_type)
@ -993,14 +1028,14 @@ template<>
// ast_constant_declaration is declaring a global constant, outside a function
// example: `const op = 0x123;`
struct Vertex<ast_constant_declaration> 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<ast_identifier>(); }
AnyExprV get_init_value() const { return child_as_expr(1); }
Vertex* mutate() const { return const_cast<Vertex*>(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<ast_identifier> name_identifier, TypePtr declared_type, AnyExprV init_value)

View file

@ -1088,6 +1088,7 @@ void define_builtins() {
TypePtr Slice = TypeDataSlice::create();
TypePtr Builder = TypeDataBuilder::create();
TypePtr Tuple = TypeDataTuple::create();
TypePtr Never = TypeDataNever::create();
std::vector<GenericsDeclaration::GenericsItem> itemsT;
itemsT.emplace_back("T");
@ -1201,10 +1202,10 @@ void define_builtins() {
define_builtin_func("__isNull", {typeT}, Bool, declGenericT,
compile_is_null,
FunctionData::flagMarkedAsPure);
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
define_builtin_func("__throw", ParamsInt1, Never, nullptr,
compile_throw,
0);
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
define_builtin_func("__throw_arg", {typeT, Int}, Never, declGenericT,
compile_throw_arg,
0);
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,

View file

@ -274,8 +274,16 @@ void Stack::rearrange_top(var_idx_t top, bool last) {
bool Op::generate_code_step(Stack& stack) {
stack.opt_show();
stack.drop_vars_except(var_info);
stack.opt_show();
// detect `throw 123` (actually _IntConst 123 + _Call __throw)
// don't clear the stack, since dropping unused elements make no sense, an exception is thrown anyway
bool will_now_immediate_throw = (cl == _Call && f_sym->is_builtin_function() && f_sym->name == "__throw")
|| (cl == _IntConst && next->cl == _Call && next->f_sym->is_builtin_function() && next->f_sym->name == "__throw");
if (!will_now_immediate_throw) {
stack.drop_vars_except(var_info);
stack.opt_show();
}
bool inline_func = stack.mode & Stack::_InlineFunc;
switch (cl) {
case _Nop:
@ -285,6 +293,7 @@ bool Op::generate_code_step(Stack& stack) {
stack.enforce_state(left);
if (stack.o.retalt_ && (stack.mode & Stack::_NeedRetAlt)) {
stack.o << "RETALT";
stack.o.retalt_inserted_ = true;
}
stack.opt_show();
return false;
@ -348,9 +357,9 @@ bool Op::generate_code_step(Stack& stack) {
std::vector<VarDescr> 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);
@ -514,7 +523,7 @@ bool Op::generate_code_step(Stack& stack) {
int j = ret_order ? ret_order->at(i) : i;
stack.push_new_var(left.at(j));
}
return true;
return !f_sym || f_sym->declared_return_type != TypeDataNever::create();
}
case _SetGlob: {
tolk_assert(g_sym);

View file

@ -66,7 +66,7 @@ void CompilerSettings::parse_experimental_options_cmd_arg(const std::string& cmd
}
}
const std::vector<const FunctionData*>& get_all_not_builtin_functions() {
const std::vector<FunctionPtr>& get_all_not_builtin_functions() {
return G.all_functions;
}

View file

@ -95,10 +95,10 @@ struct CompilerState {
GlobalSymbolTable symtable;
PersistentHeapAllocator persistent_mem;
std::vector<const FunctionData*> all_functions; // all user-defined (not built-in) functions, with generic instantiations
std::vector<const FunctionData*> all_get_methods;
std::vector<const GlobalVarData*> all_global_vars;
std::vector<const GlobalConstData*> all_constants;
std::vector<FunctionPtr> all_functions; // all user-defined (not built-in) functions, with generic instantiations
std::vector<FunctionPtr> all_get_methods;
std::vector<GlobalVarPtr> all_global_vars;
std::vector<GlobalConstPtr> all_constants;
AllRegisteredSrcFiles all_src_files;
bool is_verbosity(int gt_eq) const { return settings.verbosity >= gt_eq; }

View file

@ -255,7 +255,7 @@ struct ConstantEvaluator {
if (!sym) {
v->error("undefined symbol `" + static_cast<std::string>(name) + "`");
}
const GlobalConstData* const_ref = sym->try_as<GlobalConstData>();
GlobalConstPtr const_ref = sym->try_as<GlobalConstPtr>();
if (!const_ref) {
v->error("symbol `" + static_cast<std::string>(name) + "` is not a constant");
}

View file

@ -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*;

View file

@ -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<TypePtr>&& substitutionTs) {
this->substitutionTs = std::move(substitutionTs);
this->manually_specified = true;
}
// purpose: having `f<T>(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<TypePtr> 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<TypeDataGenericT>()) {
// `(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<TypeDataNullable>()) {
// `arg: T?` called as `f(nullableInt)` => T is int
if (const auto* a_nullable = arg_type->try_as<TypeDataNullable>()) {
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<TypeDataGenericT>()) {
// `(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<TypeDataTensor>()) {
// `arg: (int, T)` called as `f((5, cs))` => T is slice
if (const auto* a_tensor = arg_type->try_as<TypeDataTensor>(); 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<TypeDataTypedTuple>()) {
// `arg: [int, T]` called as `f([5, cs])` => T is slice
if (const auto* a_tuple = arg_type->try_as<TypeDataTypedTuple>(); 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<TypeDataFunCallable>()) {
// `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int
if (const auto* a_callable = arg_type->try_as<TypeDataFunCallable>(); 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<TypeDataTensor>()) {
// `arg: (int, T)` called as `f((5, cs))` => T is slice
if (const auto* a_tensor = arg_type->try_as<TypeDataTensor>(); 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<int>(substitutions.size()); ++i) {
if (substitutions[i] == nullptr) {
return i;
} else if (const auto* p_tuple = param_type->try_as<TypeDataTypedTuple>()) {
// `arg: [int, T]` called as `f([5, cs])` => T is slice
if (const auto* a_tuple = arg_type->try_as<TypeDataTypedTuple>(); 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<TypeDataFunCallable>()) {
// `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int
if (const auto* a_callable = arg_type->try_as<TypeDataFunCallable>(); 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<TypePtr> 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(FunctionPtr cur_f, 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(cur_f, 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<int>(substitutionTs.size()); ++i) {
if (substitutionTs[i] == nullptr) {
return i;
}
}
return -1;
}
// clone the body of `f<T>` replacing T everywhere with a substitution
// before: `fun f<T>(v: T) { var cp: [T] = [v]; }`
@ -175,11 +197,10 @@ int GenericsDeclaration::find_nameT(std::string_view nameT) const {
// after creating a deep copy of `f<T>` like `f<int>`, 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);
pipeline_detect_unreachable_statements(inst_fun_ref);
pipeline_infer_types_and_calls_and_fields(inst_fun_ref);
}
@ -198,34 +219,12 @@ std::string generate_instantiated_name(const std::string& orig_name, const std::
return name;
}
td::Result<std::vector<TypePtr>> deduce_substitutionTs_on_generic_func_call(const FunctionData* called_fun, std::vector<TypePtr>&& 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<int>(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<TypePtr>&& substitutionTs) {
FunctionPtr instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, const std::string& inst_name, std::vector<TypePtr>&& substitutionTs) {
tolk_assert(fun_ref->genericTs);
// if `f<int>` was earlier instantiated, return it
if (const auto* existing = lookup_global_symbol(inst_name)) {
const FunctionData* inst_ref = existing->try_as<FunctionData>();
FunctionPtr inst_ref = existing->try_as<FunctionPtr>();
tolk_assert(inst_ref);
return inst_ref;
}

View file

@ -57,8 +57,46 @@ struct GenericsInstantiation {
}
};
// this class helps to deduce Ts on the fly
// purpose: having `f<T>(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<int>(5)`
class GenericSubstitutionsDeduceForCall {
FunctionPtr fun_ref;
std::vector<TypePtr> 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<TypePtr>&& substitutionTs);
TypePtr replace_by_manually_specified(TypePtr param_type) const;
TypePtr auto_deduce_from_argument(FunctionPtr cur_f, SrcLocation loc, TypePtr param_type, TypePtr arg_type);
int get_first_not_deduced_idx() const;
std::vector<TypePtr>&& 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<TypePtr>& substitutions);
td::Result<std::vector<TypePtr>> deduce_substitutionTs_on_generic_func_call(const FunctionData* called_fun, std::vector<TypePtr>&& arg_types, TypePtr return_hint);
const FunctionData* instantiate_generic_function(SrcLocation loc, const FunctionData* fun_ref, const std::string& inst_name, std::vector<TypePtr>&& substitutionTs);
FunctionPtr instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, const std::string& inst_name, std::vector<TypePtr>&& substitutionTs);
} // namespace tolk

File diff suppressed because it is too large Load diff

View file

@ -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<ast_not_null_operator> 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<ast_is_null_check> 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<ast_local_var_lhs> 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<CalculateRvalueLvalueVisitor>();
}
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<ast_function_declaration>());

View file

@ -0,0 +1,586 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tolk.h"
#include "ast.h"
#include "ast-visitor.h"
#include "type-system.h"
namespace tolk {
GNU_ATTRIBUTE_NOINLINE
static std::string to_string(TypePtr type) {
return "`" + type->as_human_readable() + "`";
}
GNU_ATTRIBUTE_NOINLINE
static std::string to_string(AnyExprV v_with_type) {
return "`" + v_with_type->inferred_type->as_human_readable() + "`";
}
GNU_ATTRIBUTE_NOINLINE
static std::string expression_as_string(AnyExprV v) {
if (auto v_ref = v->try_as<ast_reference>()) {
if (v_ref->sym->try_as<LocalVarPtr>() || v_ref->sym->try_as<GlobalVarPtr>()) {
return "variable `" + static_cast<std::string>(v_ref->get_identifier()->name) + "`";
}
}
if (auto v_par = v->try_as<ast_parenthesized_expression>()) {
return expression_as_string(v_par->get_expr());
}
return "expression";
}
// fire a general "type mismatch" error, just a wrapper over `throw`
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire(FunctionPtr cur_f, SrcLocation loc, const std::string& message) {
throw ParseError(cur_f, loc, message);
}
// fire an error on `!cell` / `+slice`
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_cannot_apply_operator(FunctionPtr cur_f, SrcLocation loc, std::string_view operator_name, AnyExprV unary_expr) {
std::string op = static_cast<std::string>(operator_name);
fire(cur_f, loc, "can not apply operator `" + op + "` to " + to_string(unary_expr->inferred_type));
}
// fire an error on `int + cell` / `slice & int`
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_cannot_apply_operator(FunctionPtr cur_f, SrcLocation loc, std::string_view operator_name, AnyExprV lhs, AnyExprV rhs) {
std::string op = static_cast<std::string>(operator_name);
fire(cur_f, loc, "can not apply operator `" + op + "` to " + to_string(lhs->inferred_type) + " and " + to_string(rhs->inferred_type));
}
GNU_ATTRIBUTE_NOINLINE
static void warning_condition_always_true_or_false(FunctionPtr cur_f, SrcLocation loc, AnyExprV cond, const char* operator_name) {
loc.show_warning("condition of " + static_cast<std::string>(operator_name) + " is always " + (cond->is_always_true ? "true" : "false"));
}
// given `f(x: int)` and a call `f(expr)`, check that expr_type is assignable to `int`
static void check_function_argument_passed(FunctionPtr cur_f, TypePtr param_type, AnyExprV ith_arg, bool is_obj_of_dot_call) {
if (!param_type->can_rhs_be_assigned(ith_arg->inferred_type)) {
if (is_obj_of_dot_call) {
fire(cur_f, ith_arg->loc, "can not call method for " + to_string(param_type) + " with object of type " + to_string(ith_arg));
} else {
fire(cur_f, ith_arg->loc, "can not pass " + to_string(ith_arg) + " to " + to_string(param_type));
}
}
}
// given `f(x: mutate int?)` and a call `f(expr)`, check that `int?` is assignable to expr_type
// (for instance, can't call `f(mutate intVal)`, since f can potentially assign null to it)
static void check_function_argument_mutate_back(FunctionPtr cur_f, TypePtr param_type, AnyExprV ith_arg, bool is_obj_of_dot_call) {
if (!ith_arg->inferred_type->can_rhs_be_assigned(param_type)) {
if (is_obj_of_dot_call) {
fire(cur_f, ith_arg->loc,"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 {
fire(cur_f, ith_arg->loc,"can not pass " + to_string(ith_arg) + " to mutate " + to_string(param_type) + ", because mutation is not type compatible");
}
}
}
// fire an error on `var n = null`
// technically it's correct, type of `n` is TypeDataNullLiteral, but it's not what the user wanted
// 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(FunctionPtr cur_f, SrcLocation loc, LocalVarPtr assigned_var, bool is_assigned_null_literal) {
std::string var_name = assigned_var->name;
fire(cur_f, loc, "can not infer type of `" + var_name + "`, it's always null; specify its type with `" + var_name + ": <type>`" + (is_assigned_null_literal ? " or use `null as <type>`" : ""));
}
// 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_cannot_put_non1_stack_width_arg_to_tuple(FunctionPtr cur_f, SrcLocation loc, TypePtr inferred_type) {
fire(cur_f, 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");
}
// 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(FunctionPtr cur_f, V<ast_function_call> v) {
FunctionPtr fun_ref = v->fun_maybe;
tolk_assert(fun_ref && fun_ref->is_builtin_function());
if (fun_ref->name == "__expect_type") {
tolk_assert(v->get_num_args() == 2);
TypePtr expected_type = parse_type_from_string(v->get_arg(1)->get_expr()->as<ast_string_const>()->str_val);
TypePtr expr_type = v->get_arg(0)->inferred_type;
if (expected_type != expr_type) {
fire(cur_f, v->loc, "__expect_type failed: expected " + to_string(expected_type) + ", got " + to_string(expr_type));
}
}
}
static bool expect_integer(AnyExprV v_inferred) {
return v_inferred->inferred_type == TypeDataInt::create();
}
static bool expect_boolean(AnyExprV v_inferred) {
return v_inferred->inferred_type == TypeDataBool::create();
}
class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody {
FunctionPtr cur_f = nullptr; // may be nullptr if checking `const a = ...` init_value
protected:
void visit(V<ast_set_assign> v) override {
AnyExprV lhs = v->get_lhs();
AnyExprV rhs = v->get_rhs();
parent::visit(lhs);
parent::visit(rhs);
// all operators (+=, etc.) can work for integers (if both sides are integers)
bool types_ok = expect_integer(lhs) && expect_integer(rhs);
// bitwise operators &= |= ^= are "overloaded" for booleans also (if both sides are booleans)
if (!types_ok && (v->tok == tok_set_bitwise_and || v->tok == tok_set_bitwise_or || v->tok == tok_set_bitwise_xor)) {
types_ok = expect_boolean(lhs) && expect_boolean(rhs);
}
// using += for other types (e.g. `tensorVar += tensorVar`) is not allowed
if (!types_ok) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);
}
}
void visit(V<ast_unary_operator> v) override {
AnyExprV rhs = v->get_rhs();
parent::visit(rhs);
switch (v->tok) {
case tok_logical_not:
if (!expect_integer(rhs) && !expect_boolean(rhs)) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, rhs);
}
break;
default:
if (!expect_integer(rhs)) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, rhs);
}
}
}
void visit(V<ast_binary_operator> v) override {
AnyExprV lhs = v->get_lhs();
AnyExprV rhs = v->get_rhs();
parent::visit(lhs);
parent::visit(rhs);
switch (v->tok) {
// == != can compare both integers and booleans, (int == bool) is NOT allowed
// note, that `int?` and `int?` can't be compared, since Fift `EQUAL` works with integers only
// (if to allow `int?` in the future, `==` must be expressed in a complicated Fift code considering TVM NULL)
case tok_eq:
case tok_neq: {
bool both_int = expect_integer(lhs) && expect_integer(rhs);
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
if (!both_int && !both_bool) {
if (lhs->inferred_type == rhs->inferred_type) { // compare slice with slice, int? with int?
fire(cur_f, v->loc, "type " + to_string(lhs) + " can not be compared with `== !=`");
} else {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);
}
}
break;
}
// < > can compare only strict integers
case tok_lt:
case tok_gt:
case tok_leq:
case tok_geq:
case tok_spaceship:
if (!expect_integer(lhs) || !expect_integer(rhs)) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);
}
break;
// & | ^ are "overloaded" both for integers and booleans, (int & bool) is NOT allowed
case tok_bitwise_and:
case tok_bitwise_or:
case tok_bitwise_xor: {
bool both_int = expect_integer(lhs) && expect_integer(rhs);
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
if (!both_int && !both_bool) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);
}
break;
}
// && || can work with integers and booleans, (int && bool) is allowed, (int16 && int32) also
case tok_logical_and:
case tok_logical_or: {
bool lhs_ok = expect_integer(lhs) || expect_boolean(lhs);
bool rhs_ok = expect_integer(rhs) || expect_boolean(rhs);
if (!lhs_ok || !rhs_ok) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);
}
break;
}
// others are mathematical: + * ...
default:
if (!expect_integer(lhs) || !expect_integer(rhs)) {
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);
}
}
}
void visit(V<ast_cast_as_operator> v) override {
parent::visit(v->get_expr());
if (!v->get_expr()->inferred_type->can_be_casted_with_as_operator(v->cast_to_type)) {
fire(cur_f, v->loc, "type " + to_string(v->get_expr()) + " can not be cast to " + to_string(v->cast_to_type));
}
}
void visit(V<ast_not_null_operator> v) override {
parent::visit(v->get_expr());
if (v->get_expr()->inferred_type == TypeDataNullLiteral::create()) {
// operator `!` used for always-null (proven by smart casts, for example), it's an error
fire(cur_f, v->loc, "operator `!` used for always null expression");
}
// if operator `!` used for non-nullable, probably a warning should be printed
}
void visit(V<ast_is_null_check> v) override {
parent::visit(v->get_expr());
if ((v->is_always_true && !v->is_negated) || (v->is_always_false && v->is_negated)) {
v->loc.show_warning(expression_as_string(v->get_expr()) + " is always null, this condition is always " + (v->is_always_true ? "true" : "false"));
}
if ((v->is_always_false && !v->is_negated) || (v->is_always_true && v->is_negated)) {
v->loc.show_warning(expression_as_string(v->get_expr()) + " of type " + to_string(v->get_expr()) + " is always not null, this condition is always " + (v->is_always_true ? "true" : "false"));
}
}
void visit(V<ast_typed_tuple> v) override {
parent::visit(v);
for (int i = 0; i < v->size(); ++i) {
AnyExprV item = v->get_item(i);
if (item->inferred_type->get_width_on_stack() != 1) {
fire_error_cannot_put_non1_stack_width_arg_to_tuple(cur_f, v->get_item(i)->loc, item->inferred_type);
}
}
}
void visit(V<ast_dot_access> v) override {
parent::visit(v);
TypePtr obj_type = v->get_obj()->inferred_type;
if (v->is_target_indexed_access()) {
if (obj_type->try_as<TypeDataTuple>() && v->inferred_type->get_width_on_stack() != 1) {
fire_error_cannot_put_non1_stack_width_arg_to_tuple(cur_f, v->loc, v->inferred_type);
}
}
}
void visit(V<ast_function_call> v) override {
parent::visit(v); // check against type mismatch inside nested arguments
FunctionPtr fun_ref = v->fun_maybe;
if (!fun_ref) {
// `local_var(args)` and similar
const TypeDataFunCallable* f_callable = v->get_callee()->inferred_type->try_as<TypeDataFunCallable>();
tolk_assert(f_callable && f_callable->params_size() == v->get_num_args());
for (int i = 0; i < v->get_num_args(); ++i) {
auto arg_i = v->get_arg(i)->get_expr();
TypePtr param_type = f_callable->params_types[i];
if (!param_type->can_rhs_be_assigned(arg_i->inferred_type)) {
fire(cur_f, arg_i->loc, "can not pass " + to_string(arg_i) + " to " + to_string(param_type));
}
}
return;
}
// so, we have a call `f(args)` or `obj.f(args)`, f is a global function (fun_ref) (code / asm / builtin)
int delta_self = 0;
AnyExprV dot_obj = nullptr;
if (auto v_dot = v->get_callee()->try_as<ast_dot_access>()) {
delta_self = 1;
dot_obj = v_dot->get_obj();
}
if (dot_obj) {
const LocalVarData& param_0 = fun_ref->parameters[0];
TypePtr param_type = param_0.declared_type;
check_function_argument_passed(cur_f, param_type, dot_obj, true);
if (param_0.is_mutate_parameter()) {
check_function_argument_mutate_back(cur_f, param_type, 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;
check_function_argument_passed(cur_f, param_type, arg_i, false);
if (param_i.is_mutate_parameter()) {
check_function_argument_mutate_back(cur_f, param_type, arg_i, false);
}
}
if (fun_ref->is_builtin_function() && fun_ref->name[0] == '_') {
handle_possible_compiler_internal_call(cur_f, v);
}
}
void visit(V<ast_assign> v) override {
parent::visit(v->get_lhs());
parent::visit(v->get_rhs());
process_assignment_lhs(v->get_lhs(), v->get_rhs()->inferred_type, v->get_rhs());
}
// handle (and dig recursively) into `var lhs = rhs`
// examples: `var z = 5`, `var (x, [y]) = (2, [3])`, `var (x, [y]) = xy`
// while recursing, keep track of rhs if lhs and rhs have common shape (5 for z, 2 for x, [3] for [y], 3 for y)
// (so that on type mismatch, point to corresponding rhs, example: `var (x, y:slice) = (1, 2)` point to 2
void process_assignment_lhs(AnyExprV lhs, TypePtr rhs_type, AnyExprV corresponding_maybe_rhs) {
AnyExprV err_loc = corresponding_maybe_rhs ? corresponding_maybe_rhs : lhs;
// `var ... = rhs` - dig into left part
if (auto lhs_decl = lhs->try_as<ast_local_vars_declaration>()) {
process_assignment_lhs(lhs_decl->get_expr(), rhs_type, corresponding_maybe_rhs);
return;
}
// inside `var v: int = rhs` / `var _ = rhs` / `var v redef = rhs` (lhs is "v" / "_" / "v")
if (auto lhs_var = lhs->try_as<ast_local_var_lhs>()) {
TypePtr declared_type = lhs_var->declared_type; // `var v: int = rhs` (otherwise, nullptr)
if (lhs_var->marked_as_redef) {
tolk_assert(lhs_var->var_ref && lhs_var->var_ref->declared_type);
declared_type = lhs_var->var_ref->declared_type;
}
if (declared_type) {
if (!declared_type->can_rhs_be_assigned(rhs_type)) {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + " to variable of type " + to_string(declared_type));
}
} else {
if (rhs_type == TypeDataNullLiteral::create()) {
fire_error_assign_always_null_to_variable(cur_f, err_loc->loc, lhs_var->var_ref->try_as<LocalVarPtr>(), corresponding_maybe_rhs && corresponding_maybe_rhs->type == ast_null_keyword);
}
}
return;
}
// `(v1, v2) = rhs` / `var (v1, v2) = rhs` (rhs may be `(1,2)` or `tensorVar` or `someF()`, doesn't matter)
// dig recursively into v1 and v2 with corresponding rhs i-th item of a tensor
if (auto lhs_tensor = lhs->try_as<ast_tensor>()) {
const TypeDataTensor* rhs_type_tensor = rhs_type->try_as<TypeDataTensor>();
if (!rhs_type_tensor) {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + " to a tensor");
}
if (lhs_tensor->size() != rhs_type_tensor->size()) {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + ", sizes mismatch");
}
V<ast_tensor> rhs_tensor_maybe = corresponding_maybe_rhs ? corresponding_maybe_rhs->try_as<ast_tensor>() : nullptr;
for (int i = 0; i < lhs_tensor->size(); ++i) {
process_assignment_lhs(lhs_tensor->get_item(i), rhs_type_tensor->items[i], rhs_tensor_maybe ? rhs_tensor_maybe->get_item(i) : nullptr);
}
return;
}
// `[v1, v2] = rhs` / `var [v1, v2] = rhs` (rhs may be `[1,2]` or `tupleVar` or `someF()`, doesn't matter)
// dig recursively into v1 and v2 with corresponding rhs i-th item of a tuple
if (auto lhs_tuple = lhs->try_as<ast_typed_tuple>()) {
const TypeDataTypedTuple* rhs_type_tuple = rhs_type->try_as<TypeDataTypedTuple>();
if (!rhs_type_tuple) {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + " to a tuple");
}
if (lhs_tuple->size() != rhs_type_tuple->size()) {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + ", sizes mismatch");
}
V<ast_typed_tuple> rhs_tuple_maybe = corresponding_maybe_rhs ? corresponding_maybe_rhs->try_as<ast_typed_tuple>() : nullptr;
for (int i = 0; i < lhs_tuple->size(); ++i) {
process_assignment_lhs(lhs_tuple->get_item(i), rhs_type_tuple->items[i], rhs_tuple_maybe ? rhs_tuple_maybe->get_item(i) : nullptr);
}
return;
}
// check `untypedTuple.0 = rhs_tensor` and other non-1 width elements
if (auto lhs_dot = lhs->try_as<ast_dot_access>()) {
if (lhs_dot->is_target_indexed_access() && lhs_dot->get_obj()->inferred_type == TypeDataTuple::create()) {
if (rhs_type->get_width_on_stack() != 1) {
fire_error_cannot_put_non1_stack_width_arg_to_tuple(cur_f, err_loc->loc, rhs_type);
}
}
}
// here is `v = rhs` (just assignment, not `var v = rhs`) / `a.0 = rhs` / `getObj(z=f()).0 = rhs` etc.
// types were already inferred, so just check their compatibility
// for strange lhs like `f() = rhs` type checking will pass, but will fail lvalue check later
if (!lhs->inferred_type->can_rhs_be_assigned(rhs_type)) {
if (lhs->try_as<ast_reference>()) {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + " to variable of type " + to_string(lhs));
} else {
fire(cur_f, err_loc->loc, "can not assign " + to_string(rhs_type) + " to " + to_string(lhs));
}
}
}
void visit(V<ast_return_statement> v) override {
parent::visit(v->get_return_value());
if (cur_f->does_return_self()) {
if (!is_expr_valid_as_return_self(v->get_return_value())) {
fire(cur_f, v->loc, "invalid return from `self` function");
}
return;
}
TypePtr expr_type = v->get_return_value()->inferred_type;
if (!cur_f->inferred_return_type->can_rhs_be_assigned(expr_type)) {
fire(cur_f, v->get_return_value()->loc, "can not convert type " + to_string(expr_type) + " to return type " + to_string(cur_f->inferred_return_type));
}
}
static bool is_expr_valid_as_return_self(AnyExprV return_expr) {
// `return self`
if (return_expr->type == ast_reference && return_expr->as<ast_reference>()->get_name() == "self") {
return true;
}
// `return self.someMethod()`
if (auto v_call = return_expr->try_as<ast_function_call>(); v_call && v_call->is_dot_call()) {
return v_call->fun_maybe && v_call->fun_maybe->does_return_self() && is_expr_valid_as_return_self(v_call->get_dot_obj());
}
// `return cond ? ... : ...`
if (auto v_ternary = return_expr->try_as<ast_ternary_operator>()) {
return is_expr_valid_as_return_self(v_ternary->get_when_true()) && is_expr_valid_as_return_self(v_ternary->get_when_false());
}
return false;
}
void visit(V<ast_ternary_operator> v) override {
parent::visit(v);
AnyExprV cond = v->get_cond();
if (!expect_integer(cond) && !expect_boolean(cond)) {
fire(cur_f, cond->loc, "can not use " + to_string(cond) + " as a boolean condition");
}
if (cond->is_always_true || cond->is_always_false) {
warning_condition_always_true_or_false(cur_f, v->loc, cond, "ternary operator");
}
}
void visit(V<ast_if_statement> v) override {
parent::visit(v);
AnyExprV cond = v->get_cond();
if (!expect_integer(cond) && !expect_boolean(cond)) {
fire(cur_f, cond->loc, "can not use " + to_string(cond) + " as a boolean condition");
}
if (cond->is_always_true || cond->is_always_false) {
warning_condition_always_true_or_false(cur_f, v->loc, cond, "`if`");
}
}
void visit(V<ast_repeat_statement> v) override {
parent::visit(v);
AnyExprV cond = v->get_cond();
if (!expect_integer(cond)) {
fire(cur_f, cond->loc, "condition of `repeat` must be an integer, got " + to_string(cond));
}
}
void visit(V<ast_while_statement> v) override {
parent::visit(v);
AnyExprV cond = v->get_cond();
if (!expect_integer(cond) && !expect_boolean(cond)) {
fire(cur_f, cond->loc, "can not use " + to_string(cond) + " as a boolean condition");
}
if (cond->is_always_true || cond->is_always_false) {
warning_condition_always_true_or_false(cur_f, v->loc, cond, "`while`");
}
}
void visit(V<ast_do_while_statement> v) override {
parent::visit(v);
AnyExprV cond = v->get_cond();
if (!expect_integer(cond) && !expect_boolean(cond)) {
fire(cur_f, cond->loc, "can not use " + to_string(cond) + " as a boolean condition");
}
if (cond->is_always_true || cond->is_always_false) {
warning_condition_always_true_or_false(cur_f, v->loc, cond, "`do while`");
}
}
void visit(V<ast_throw_statement> v) override {
parent::visit(v);
if (!expect_integer(v->get_thrown_code())) {
fire(cur_f, v->get_thrown_code()->loc, "excNo of `throw` must be an integer, got " + to_string(v->get_thrown_code()));
}
if (v->has_thrown_arg() && v->get_thrown_arg()->inferred_type->get_width_on_stack() != 1) {
fire(cur_f, v->get_thrown_arg()->loc, "can not throw " + to_string(v->get_thrown_arg()) + ", exception arg must occupy exactly 1 stack slot");
}
}
void visit(V<ast_assert_statement> v) override {
parent::visit(v);
AnyExprV cond = v->get_cond();
if (!expect_integer(cond) && !expect_boolean(cond)) {
fire(cur_f, cond->loc, "can not use " + to_string(cond) + " as a boolean condition");
}
if (!expect_integer(v->get_thrown_code())) {
fire(cur_f, v->get_thrown_code()->loc, "thrown excNo of `assert` must be an integer, got " + to_string(v->get_thrown_code()));
}
if (cond->is_always_true || cond->is_always_false) {
warning_condition_always_true_or_false(cur_f, v->loc, cond, "`assert`");
}
}
void visit(V<ast_sequence> v) override {
parent::visit(v);
if (v->first_unreachable) {
// it's essential to print "unreachable code" warning AFTER type checking
// (printing it while inferring might be a false positive if types are incorrect, due to smart casts for example)
// a more correct approach would be to access cfg here somehow, but since cfg is now available only while inferring,
// a special v->first_unreachable was set specifically for this warning (again, which is correct if types match)
v->first_unreachable->loc.show_warning("unreachable code");
}
}
public:
bool should_visit_function(FunctionPtr fun_ref) override {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) override {
cur_f = fun_ref;
parent::visit(v_function->get_body());
cur_f = nullptr;
if (fun_ref->is_implicit_return() && fun_ref->declared_return_type) {
if (!fun_ref->declared_return_type->can_rhs_be_assigned(TypeDataVoid::create()) || fun_ref->does_return_self()) {
fire(fun_ref, v_function->get_body()->as<ast_sequence>()->loc_end, "missing return");
}
}
}
};
void pipeline_check_inferred_types() {
visit_ast_of_all_functions<CheckInferredTypesVisitor>();
}
} // namespace tolk

View file

@ -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<ast_reference>()) {
if (v_ident->sym->try_as<GlobalVarData>()) {
if (v_ident->sym->try_as<GlobalVarPtr>()) {
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();
}
};

View file

@ -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<ast_not_null_operator> 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<ast_is_null_check> 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<ast_int_const> 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<ast_dot_access> 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<const FunctionData*>(v->target));
validate_function_used_as_noncall(v, std::get<FunctionPtr>(v->target));
}
}
@ -158,17 +170,17 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody {
void visit(V<ast_reference> v) override {
if (v->is_lvalue) {
tolk_assert(v->sym);
if (const auto* var_ref = v->sym->try_as<LocalVarData>(); var_ref && var_ref->is_immutable()) {
if (LocalVarPtr var_ref = v->sym->try_as<LocalVarPtr>(); var_ref && var_ref->is_immutable()) {
fire_error_modifying_immutable_variable(v, var_ref);
} else if (v->sym->try_as<GlobalConstData>()) {
} else if (v->sym->try_as<GlobalConstPtr>()) {
v->error("modifying immutable constant");
} else if (v->sym->try_as<FunctionData>()) {
} else if (v->sym->try_as<FunctionPtr>()) {
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<FunctionData>(); fun_ref && v->is_rvalue) {
if (FunctionPtr fun_ref = v->sym->try_as<FunctionPtr>(); 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();
}
};

View file

@ -25,6 +25,8 @@
*
* Currently, it just replaces `-1` (ast_unary_operator ast_int_const) with a number -1
* and `!true` with false.
* Also, all parenthesized `((expr))` are replaced with `expr`, it's a constant transformation.
* (not to handle parenthesized in optimization passes, like `((x)) == true`)
* More rich constant folding should be done some day, but even without this, IR optimizations
* (operating low-level stack variables) pretty manage to do all related optimizations.
* Constant folding in the future, done at AST level, just would slightly reduce amount of work for optimizer.
@ -47,6 +49,14 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody {
return v_bool;
}
AnyExprV replace(V<ast_parenthesized_expression> v) override {
AnyExprV inner = parent::replace(v->get_expr());
if (v->is_lvalue) {
inner->mutate()->assign_lvalue_true();
}
return inner;
}
AnyExprV replace(V<ast_unary_operator> v) override {
parent::replace(v);
@ -78,8 +88,19 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody {
return v;
}
AnyExprV replace(V<ast_is_null_check> 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();
}
};

View file

@ -1,138 +0,0 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tolk.h"
#include "ast.h"
#include "ast-visitor.h"
/*
* This pipe does two things:
* 1) detects unreachable code and prints warnings about it
* example: `fun main() { if(1){return;}else{return;} var x = 0; }` var is unreachable
* 2) if control flow reaches end of function, store a flag to insert an implicit return
* example: `fun main() { assert(...); }` has an implicit `return ()` statement before a brace
*
* Note, that it does not delete unreachable code, only prints warnings.
* Actual deleting is done much later (in "legacy" part), after AST is converted to Op.
*
* Note, that it's not CFG, it's just a shallow reachability detection.
* In the future, a true CFG should be introduced. For instance, in order to have nullable types,
* I'll need to implement smart casts. Then I'll think of a complicated granular control flow graph,
* considering data flow and exceptions (built before type inferring, of course),
* and detecting unreachable code will be a part of it.
*/
namespace tolk {
class UnreachableStatementsDetectVisitor final {
bool always_returns(AnyV v) {
switch (v->type) {
case ast_sequence: return always_returns(v->as<ast_sequence>());
case ast_return_statement: return always_returns(v->as<ast_return_statement>());
case ast_throw_statement: return always_returns(v->as<ast_throw_statement>());
case ast_function_call: return always_returns(v->as<ast_function_call>());
case ast_repeat_statement: return always_returns(v->as<ast_repeat_statement>());
case ast_while_statement: return always_returns(v->as<ast_while_statement>());
case ast_do_while_statement: return always_returns(v->as<ast_do_while_statement>());
case ast_try_catch_statement: return always_returns(v->as<ast_try_catch_statement>());
case ast_if_statement: return always_returns(v->as<ast_if_statement>());
default:
// unhandled statements (like assert) and statement expressions
return false;
}
}
bool always_returns(V<ast_sequence> v) {
bool always = false;
for (AnyV item : v->get_items()) {
if (always && item->type != ast_empty_statement) {
item->loc.show_warning("unreachable code");
break;
}
always |= always_returns(item);
}
return always;
}
static bool always_returns([[maybe_unused]] V<ast_return_statement> v) {
// quite obvious: `return expr` interrupts control flow
return true;
}
static bool always_returns([[maybe_unused]] V<ast_throw_statement> v) {
// todo `throw excNo` currently does not interrupt control flow
// (in other words, `throw 1; something` - something is reachable)
// the reason is that internally it's transformed to a call of built-in function __throw(),
// which is a regular function, like __throw_if() or loadInt()
// to fix this later on, it should be deeper, introducing Op::_Throw for example,
// to make intermediate representations and stack optimizer also be aware that after it there is unreachable
return false;
}
static bool always_returns([[maybe_unused]] V<ast_function_call> v) {
// neither annotations like @noreturn nor auto-detection of always-throwing functions also doesn't exist
// in order to do this in the future, it should be handled not only at AST/CFG level,
// but inside Op and low-level optimizer (at least if reachability detection is not moved out of there)
// see comments for `throw` above, similar to this case
return false;
}
bool always_returns(V<ast_repeat_statement> v) {
return always_returns(v->get_body());
}
bool always_returns(V<ast_while_statement> v) {
return always_returns(v->get_body());
}
bool always_returns(V<ast_do_while_statement> v) {
return always_returns(v->get_body());
}
bool always_returns(V<ast_try_catch_statement> v) {
return always_returns(v->get_try_body()) && always_returns(v->get_catch_body());
}
bool always_returns(V<ast_if_statement> v) {
return always_returns(v->get_if_body()) && always_returns(v->get_else_body());
}
public:
static bool should_visit_function(const FunctionData* fun_ref) {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
bool control_flow_reaches_end = !always_returns(v_function->get_body()->as<ast_sequence>());
if (control_flow_reaches_end) {
fun_ref->mutate()->assign_is_implicit_return();
}
}
};
void pipeline_detect_unreachable_statements() {
visit_ast_of_all_functions<UnreachableStatementsDetectVisitor>();
}
void pipeline_detect_unreachable_statements(const FunctionData* fun_ref) {
UnreachableStatementsDetectVisitor visitor;
if (UnreachableStatementsDetectVisitor::should_visit_function(fun_ref)) {
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
}
}
} // namespace tolk

View file

@ -36,7 +36,7 @@ namespace tolk {
static void mark_function_used_dfs(const std::unique_ptr<Op>& 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<FunctionBodyCode*>(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>& 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);
}

View file

@ -39,7 +39,7 @@ void FunctionBodyAsm::set_code(std::vector<AsmOp>&& 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;
}

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,6 @@
*
* Example: `boolVar == true` -> `boolVar`.
* Example: `!!boolVar` -> `boolVar`.
* Also in unwraps parenthesis inside if condition and similar: `assert(((x)), 404)` -> `assert(x, 404)`
*
* todo some day, replace && || with & | when it's safe (currently, && always produces IFs in Fift)
* It's tricky to implement whether replacing is safe.
@ -35,13 +34,6 @@
namespace tolk {
static AnyExprV unwrap_parenthesis(AnyExprV v) {
while (v->type == ast_parenthesized_expression) {
v = v->as<ast_parenthesized_expression>()->get_expr();
}
return v;
}
struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody {
static V<ast_int_const> create_int_const(SrcLocation loc, td::RefInt256&& intval) {
auto v_int = createV<ast_int_const>(loc, std::move(intval), {});
@ -61,7 +53,7 @@ struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody {
auto v_not = createV<ast_unary_operator>(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<FunctionData>());
v_not->assign_fun_ref(lookup_global_symbol("!b_")->try_as<FunctionPtr>());
return v_not;
}
@ -83,7 +75,7 @@ protected:
auto v_neq = createV<ast_binary_operator>(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<FunctionData>());
v_neq->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->try_as<FunctionPtr>());
return v_neq;
}
}
@ -117,9 +109,6 @@ protected:
AnyV replace(V<ast_if_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_if_statement>(v->loc, v->is_ifnot, unwrap_parenthesis(v->get_cond()), v->get_if_body(), v->get_else_body());
}
// `if (!x)` -> ifnot(x)
while (auto v_cond_unary = v->get_cond()->try_as<ast_unary_operator>()) {
@ -128,39 +117,17 @@ protected:
}
v = createV<ast_if_statement>(v->loc, !v->is_ifnot, v_cond_unary->get_rhs(), v->get_if_body(), v->get_else_body());
}
return v;
}
AnyV replace(V<ast_while_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_while_statement>(v->loc, unwrap_parenthesis(v->get_cond()), v->get_body());
// `if (x != null)` -> ifnot(x == null)
if (auto v_cond_isnull = v->get_cond()->try_as<ast_is_null_check>(); v_cond_isnull && v_cond_isnull->is_negated) {
v_cond_isnull->mutate()->assign_is_negated(!v_cond_isnull->is_negated);
v = createV<ast_if_statement>(v->loc, !v->is_ifnot, v_cond_isnull, v->get_if_body(), v->get_else_body());
}
return v;
}
AnyV replace(V<ast_do_while_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_do_while_statement>(v->loc, v->get_body(), unwrap_parenthesis(v->get_cond()));
}
return v;
}
AnyV replace(V<ast_assert_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_assert_statement>(v->loc, unwrap_parenthesis(v->get_cond()), v->get_thrown_code());
}
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();
}
};

View file

@ -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<ast_reference>()->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<ast_function_call> v) override {
// v is `globalF(args)` / `globalF<int>(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<ast_cast_as_operator>()) {
leftmost_obj = as_cast->get_expr();
} else if (auto as_nn = leftmost_obj->try_as<ast_not_null_operator>()) {
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();
}
};

View file

@ -176,8 +176,8 @@ static void register_function(V<ast_function_declaration> 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<FunctionData>() : nullptr;
const Symbol* sym = lookup_global_symbol(func_name);
FunctionPtr fun_ref = sym ? sym->try_as<FunctionPtr>() : 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<ast_function_declaration> v) {
f_sym->method_id = static_cast<int>(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.");
}

View file

@ -59,20 +59,20 @@
namespace tolk {
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_undefined_symbol(V<ast_identifier> v) {
static void fire_error_undefined_symbol(FunctionPtr cur_f, V<ast_identifier> v) {
if (v->name == "self") {
v->error("using `self` in a non-member function (it does not accept the first `self` parameter)");
throw ParseError(cur_f, v->loc, "using `self` in a non-member function (it does not accept the first `self` parameter)");
} else {
v->error("undefined symbol `" + static_cast<std::string>(v->name) + "`");
throw ParseError(cur_f, v->loc, "undefined symbol `" + static_cast<std::string>(v->name) + "`");
}
}
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_unknown_type_name(SrcLocation loc, const std::string &text) {
throw ParseError(loc, "unknown type name `" + text + "`");
static void fire_error_unknown_type_name(FunctionPtr cur_f, SrcLocation loc, const std::string &text) {
throw ParseError(cur_f, loc, "unknown type name `" + text + "`");
}
static void check_import_exists_when_using_sym(AnyV v_usage, const Symbol* used_sym) {
static void check_import_exists_when_using_sym(FunctionPtr cur_f, AnyV v_usage, const Symbol* used_sym) {
SrcLocation sym_loc = used_sym->loc;
if (!v_usage->loc.is_symbol_from_same_or_builtin_file(sym_loc)) {
const SrcFile* declared_in = sym_loc.get_src_file();
@ -83,7 +83,7 @@ static void check_import_exists_when_using_sym(AnyV v_usage, const Symbol* used_
}
}
if (!has_import) {
v_usage->error("Using a non-imported symbol `" + used_sym->name + "`. Forgot to import \"" + declared_in->rel_filename + "\"?");
throw ParseError(cur_f, v_usage->loc, "Using a non-imported symbol `" + used_sym->name + "`. Forgot to import \"" + declared_in->rel_filename + "\"?");
}
}
}
@ -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");
}
@ -137,40 +137,41 @@ struct NameAndScopeResolver {
struct TypeDataResolver {
GNU_ATTRIBUTE_NOINLINE
static TypePtr resolve_identifiers_in_type_data(TypePtr type_data, const GenericsDeclaration* genericTs) {
return type_data->replace_children_custom([genericTs](TypePtr child) {
static TypePtr resolve_identifiers_in_type_data(FunctionPtr cur_f, TypePtr type_data, const GenericsDeclaration* genericTs) {
return type_data->replace_children_custom([cur_f, genericTs](TypePtr child) {
if (const TypeDataUnresolved* un = child->try_as<TypeDataUnresolved>()) {
if (genericTs && genericTs->has_nameT(un->text)) {
std::string nameT = un->text;
return TypeDataGenericT::create(std::move(nameT));
}
if (un->text == "auto") {
throw ParseError(un->loc, "`auto` type does not exist; just omit a type for local variable (will be inferred from assignment); parameters should always be typed");
throw ParseError(cur_f, un->loc, "`auto` type does not exist; just omit a type for local variable (will be inferred from assignment); parameters should always be typed");
}
if (un->text == "self") {
throw ParseError(un->loc, "`self` type can be used only as a return type of a function (enforcing it to be chainable)");
throw ParseError(cur_f, un->loc, "`self` type can be used only as a return type of a function (enforcing it to be chainable)");
}
fire_error_unknown_type_name(un->loc, un->text);
fire_error_unknown_type_name(cur_f, un->loc, un->text);
}
return child;
});
}
};
static TypePtr finalize_type_data(TypePtr type_data, const GenericsDeclaration* genericTs) {
static TypePtr finalize_type_data(FunctionPtr cur_f, TypePtr type_data, const GenericsDeclaration* genericTs) {
if (!type_data || !type_data->has_unresolved_inside()) {
return type_data;
}
return TypeDataResolver::resolve_identifiers_in_type_data(type_data, genericTs);
return TypeDataResolver::resolve_identifiers_in_type_data(cur_f, type_data, genericTs);
}
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 cur_f;
static const GenericsDeclaration* current_genericTs;
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<std::string>(name), loc, declared_type, immutable * LocalVarData::flagImmutable, -1);
current_scope.add_local_var(v_sym);
return v_sym;
@ -178,7 +179,7 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody {
static void process_catch_variable(AnyExprV catch_var) {
if (auto v_ref = catch_var->try_as<ast_reference>()) {
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);
}
}
@ -188,16 +189,16 @@ protected:
if (v->marked_as_redef) {
const Symbol* sym = current_scope.lookup_symbol(v->get_name());
if (sym == nullptr) {
v->error("`redef` for unknown variable");
throw ParseError(cur_f, v->loc, "`redef` for unknown variable");
}
const LocalVarData* var_ref = sym->try_as<LocalVarData>();
LocalVarPtr var_ref = sym->try_as<LocalVarPtr>();
if (!var_ref) {
v->error("`redef` for unknown variable");
throw ParseError(cur_f, v->loc, "`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);
TypePtr declared_type = finalize_type_data(cur_f, v->declared_type, current_genericTs);
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);
}
@ -211,20 +212,20 @@ protected:
void visit(V<ast_reference> v) override {
const Symbol* sym = current_scope.lookup_symbol(v->get_name());
if (!sym) {
fire_error_undefined_symbol(v->get_identifier());
fire_error_undefined_symbol(cur_f, v->get_identifier());
}
v->mutate()->assign_sym(sym);
// for global functions, global vars and constants, `import` must exist
if (!sym->try_as<LocalVarData>()) {
check_import_exists_when_using_sym(v, sym);
if (!sym->try_as<LocalVarPtr>()) {
check_import_exists_when_using_sym(cur_f, v, sym);
}
// for `f<int, MyAlias>` / `f<T>`, resolve "MyAlias" and "T"
// (for function call `f<T>()`, this v (ast_reference `f<T>`) is callee)
if (auto v_instantiationTs = v->get_instantiationTs()) {
for (int i = 0; i < v_instantiationTs->size(); ++i) {
TypePtr substituted_type = finalize_type_data(v_instantiationTs->get_item(i)->substituted_type, current_function->genericTs);
TypePtr substituted_type = finalize_type_data(cur_f, v_instantiationTs->get_item(i)->substituted_type, current_genericTs);
v_instantiationTs->get_item(i)->mutate()->assign_resolved_type(substituted_type);
}
}
@ -235,7 +236,7 @@ protected:
// (for function call `t.tupleAt<MyAlias>()`, this v (ast_dot_access `t.tupleAt<MyAlias>`) is callee)
if (auto v_instantiationTs = v->get_instantiationTs()) {
for (int i = 0; i < v_instantiationTs->size(); ++i) {
TypePtr substituted_type = finalize_type_data(v_instantiationTs->get_item(i)->substituted_type, current_function->genericTs);
TypePtr substituted_type = finalize_type_data(cur_f, v_instantiationTs->get_item(i)->substituted_type, current_genericTs);
v_instantiationTs->get_item(i)->mutate()->assign_resolved_type(substituted_type);
}
}
@ -243,7 +244,7 @@ protected:
}
void visit(V<ast_cast_as_operator> v) override {
TypePtr cast_to_type = finalize_type_data(v->cast_to_type, current_function->genericTs);
TypePtr cast_to_type = finalize_type_data(cur_f, v->cast_to_type, current_genericTs);
v->mutate()->assign_resolved_type(cast_to_type);
parent::visit(v->get_expr());
}
@ -276,24 +277,25 @@ 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<ast_function_declaration> v) override {
current_function = fun_ref;
void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v) override {
cur_f = fun_ref;
current_genericTs = fun_ref->genericTs;
for (int i = 0; i < v->get_num_params(); ++i) {
const LocalVarData& param_var = fun_ref->parameters[i];
TypePtr declared_type = finalize_type_data(param_var.declared_type, fun_ref->genericTs);
TypePtr declared_type = finalize_type_data(cur_f, param_var.declared_type, fun_ref->genericTs);
v->get_param(i)->mutate()->assign_param_ref(&param_var);
v->get_param(i)->mutate()->assign_resolved_type(declared_type);
param_var.mutate()->assign_resolved_type(declared_type);
}
TypePtr return_type = finalize_type_data(fun_ref->declared_return_type, fun_ref->genericTs);
TypePtr return_type = finalize_type_data(cur_f, fun_ref->declared_return_type, fun_ref->genericTs);
v->mutate()->assign_resolved_type(return_type);
fun_ref->mutate()->assign_resolved_type(return_type);
@ -308,12 +310,14 @@ public:
tolk_assert(current_scope.scopes.empty());
}
current_function = nullptr;
current_genericTs = nullptr;
cur_f = nullptr;
}
};
NameAndScopeResolver AssignSymInsideFunctionVisitor::current_scope;
const FunctionData* AssignSymInsideFunctionVisitor::current_function = nullptr;
FunctionPtr AssignSymInsideFunctionVisitor::cur_f = nullptr;
const GenericsDeclaration* AssignSymInsideFunctionVisitor::current_genericTs = nullptr;
void pipeline_resolve_identifiers_and_assign_symbols() {
AssignSymInsideFunctionVisitor visitor;
@ -324,20 +328,22 @@ void pipeline_resolve_identifiers_and_assign_symbols() {
visitor.start_visiting_function(v_func->fun_ref, v_func);
} else if (auto v_global = v->try_as<ast_global_var_declaration>()) {
TypePtr declared_type = finalize_type_data(v_global->var_ref->declared_type, nullptr);
TypePtr declared_type = finalize_type_data(nullptr, v_global->var_ref->declared_type, nullptr);
v_global->mutate()->assign_resolved_type(declared_type);
v_global->var_ref->mutate()->assign_resolved_type(declared_type);
} else if (auto v_const = v->try_as<ast_constant_declaration>(); v_const && v_const->declared_type) {
TypePtr declared_type = finalize_type_data(v_const->const_ref->declared_type, nullptr);
v_const->mutate()->assign_resolved_type(declared_type);
v_const->const_ref->mutate()->assign_resolved_type(declared_type);
} else if (auto v_const = v->try_as<ast_constant_declaration>()) {
if (v_const->declared_type) {
TypePtr declared_type = finalize_type_data(nullptr, v_const->const_ref->declared_type, nullptr);
v_const->mutate()->assign_resolved_type(declared_type);
v_const->const_ref->mutate()->assign_resolved_type(declared_type);
}
}
}
}
}
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<ast_function_declaration>());

View file

@ -35,8 +35,8 @@ void pipeline_discover_and_parse_sources(const std::string& stdlib_filename, con
void pipeline_register_global_symbols();
void pipeline_resolve_identifiers_and_assign_symbols();
void pipeline_calculate_rvalue_lvalue();
void pipeline_detect_unreachable_statements();
void pipeline_infer_types_and_calls_and_fields();
void pipeline_check_inferred_types();
void pipeline_refine_lvalue_for_mutate_arguments();
void pipeline_check_rvalue_lvalue();
void pipeline_check_pure_impure_operations();
@ -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<T>` is deeply cloned as `f<int>`
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

472
tolk/smart-casts-cfg.cpp Normal file
View file

@ -0,0 +1,472 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "smart-casts-cfg.h"
#include "ast.h"
#include "tolk.h"
/*
* This file represents internals of AST-level control flow and data flow analysis.
* Data flow is mostly used for smart casts and is calculated AT THE TIME of type inferring.
* Not before, not after, but simultaneously with type inferring, because any local variable can be smart cast,
* which affects other expressions/variables types, generics instantiation, return auto-infer, etc.
* Though it's a part of type inferring, it's extracted as a separate file to keep inferring a bit clearer.
*
* Control flow is represented NOT as a "graph with edges". Instead, it's a "structured DFS" for the AST:
* 1) at every point of inferring, we have "current flow facts" (FlowContext)
* 2) when we see an `if (...)`, we create two derived contexts (by cloning current)
* 3) after `if`, finalize them at the end and unify
* 4) if we detect unreachable code, we mark that path's context as "unreachable"
* In other words, we get the effect of a CFG but in a more direct approach. That's enough for AST-level data-flow.
*
* FlowContext contains "data-flow facts that are definitely known": variables types (original or refined),
* sign state (definitely positive, definitely zero, etc.), boolean state (definitely true, definitely false).
* Each local variable is contained there, and possibly sub-fields of tensors/objects if definitely known:
* // current facts: x is int?, t is (int, int)
* if (x != null && t.0 > 0)
* // current facts: x is int, t is (int, int), t.0 is positive
* else
* // current facts: x is null, t is (int, int), t.0 is not positive
* When branches rejoin, facts are merged back (int+null = int? and so on, here they would be equal to before if).
* Another example:
* // current facts: x is int?
* if (x == null) {
* // current facts: x is null
* x = 1;
* // current facts: x is int
* } // else branch is empty, its facts are: x is int
* // current facts (after rejoin): x is int
*
* Every expression analysis result (performed along with type inferring) returns ExprFlow:
* 1) out_flow: facts after evaluating the whole expression, no matter how it evaluates (true or false)
* 2) true_flow: the environment if expression is definitely true
* 3) false_flow: the environment if expression is definitely false
*
* Note, that globals are NOT analyzed (smart casts work for locals only). The explanation is simple:
* don't encourage to use a global twice, it costs gas, better assign it to a local.
* See SinkExpression.
*
* An important highlight about internal structure of tensors / tuples / objects and `t.1` sink expressions.
* When a tensor/object is assigned, its fields are NOT tracked individually.
* For better understanding, I'll give some examples in TypeScript (having the same behavior):
* interface User { id: number | string, ... }
* var u: User = { id: 123, ... }
* u.id // it's number|string, not number
* u = { id: 'asdf', ... }
* u.id // it's number|string, not string
* if (typeof u.id === 'string') {
* // here `u.id` is string (smart cast)
* }
* u.id = 123;
* u.id // now it's number (smart cast) (until `u.id` or `u` are reassigned)
* // but `u` still has type `{ id: number | string, ... }`, not `{ id: number, ... }`; only `u.id` is refined
* The same example, but with nullable tensor in Tolk:
* var t: (int?, ...) = (123, ...)
* t.0 // it's int?, not int
* t = (null, ...)
* t.0 // it's int?, not null
* if (t.0 == null) {
* // here `t.0` is null (smart cast)
* }
* t.0 = 123;
* t.0 // now it's int (smart cast) (until `t.0` or `t` are reassigned)
* // but `t` still has type `(int?, ...)`, not `(int, ...)`; only `t.0` is refined
*
* In the future, not only smart casts, but other data-flow analysis can be implemented.
* 1) detect signs: `if (x > 0) { ... if (x < 0)` to warn always false
* 2) detect always true/false: `if (x) { return; } ... if (!x)` to warn always true
* These potential improvements are SignState and BoolState. Now they are NOT IMPLEMENTED, though declared.
* Their purpose is to show, that data flow is not only about smart casts, but eventually for other facts also.
* (though it's not obvious whether they should be analyzed at AST level or at IR level, like constants now)
*/
namespace tolk {
std::string SinkExpression::to_string() const {
std::string result = var_ref->name;
uint64_t cur_path = index_path;
while (cur_path != 0) {
result += ".";
result += std::to_string((cur_path & 0xFF) - 1);
cur_path >>= 8;
}
return result;
}
static std::string to_string(SignState s) {
static const char* txt[6 + 1] = {"sign=unknown", ">0", "<0", "=0", ">=0", "<=0", "sign=never"};
return txt[static_cast<int>(s)];
}
static std::string to_string(BoolState s) {
static const char* txt[4 + 1] = {"unknown", "always_true", "always_false", "bool=never"};
return txt[static_cast<int>(s)];
}
// from `expr!` get `expr`
static AnyExprV unwrap_not_null_operator(AnyExprV expr) {
while (auto v_not_null = expr->try_as<ast_not_null_operator>()) {
expr = v_not_null->get_expr();
}
return expr;
}
// "type lca" for a and b is T, so that both are assignable to T
// it's used
// 1) for auto-infer return type of the function if not specified
// example: `fun f(x: int?) { ... return 1; ... return x; }`; lca(`int`,`int?`) = `int?`
// 2) for auto-infer type of ternary and `match` expressions
// example: `cond ? beginCell() : null`; lca(`builder`,`null`) = `builder?`
// 3) when two data flows rejoin
// example: `if (tensorVar != null) ... else ...` rejoin `(int,int)` and `null` into `(int,int)?`
// when lca can't be calculated (example: `(int,int)` and `(int,int,int)`), nullptr is returned
static TypePtr calculate_type_lca(TypePtr a, TypePtr b) {
if (a == b) {
return a;
}
if (a == TypeDataNever::create()) {
return b;
}
if (b == TypeDataNever::create()) {
return a;
}
if (a->can_rhs_be_assigned(b)) {
return a;
}
if (b->can_rhs_be_assigned(a)) {
return b;
}
if (a == TypeDataUnknown::create() || b == TypeDataUnknown::create()) {
return TypeDataUnknown::create();
}
if (a == TypeDataNullLiteral::create()) {
return TypeDataNullable::create(b);
}
if (b == TypeDataNullLiteral::create()) {
return TypeDataNullable::create(a);
}
const auto* tensor1 = a->try_as<TypeDataTensor>();
const auto* tensor2 = b->try_as<TypeDataTensor>();
if (tensor1 && tensor2 && tensor1->size() == tensor2->size()) {
std::vector<TypePtr> types_lca;
types_lca.reserve(tensor1->size());
for (int i = 0; i < tensor1->size(); ++i) {
TypePtr next = calculate_type_lca(tensor1->items[i], tensor2->items[i]);
if (next == nullptr) {
return nullptr;
}
types_lca.push_back(next);
}
return TypeDataTensor::create(std::move(types_lca));
}
const auto* tuple1 = a->try_as<TypeDataTypedTuple>();
const auto* tuple2 = b->try_as<TypeDataTypedTuple>();
if (tuple1 && tuple2 && tuple1->size() == tuple2->size()) {
std::vector<TypePtr> types_lca;
types_lca.reserve(tuple1->size());
for (int i = 0; i < tuple1->size(); ++i) {
TypePtr next = calculate_type_lca(tuple1->items[i], tuple2->items[i]);
if (next == nullptr) {
return nullptr;
}
types_lca.push_back(next);
}
return TypeDataTypedTuple::create(std::move(types_lca));
}
return nullptr;
}
// merge (unify) of two sign states: what sign do we definitely have
// it's used on data flow rejoin
// example: `if (x > 0) ... else ...`; lca(Positive, NonPositive) = Unknown
SignState calculate_sign_lca(SignState a, SignState b) {
using s = SignState;
// a transformation lookup table, using the following rules:
// 1) if one is Unknown, the result is Unknown ("no definite constraints")
// 2) if one is Never (can't happen), the result is the other
// example: x is known > 0 already, given code `if (x > 0) {} else {}` merges Positive (always true) and Never
// 3) handle all other combinations carefully
static constexpr SignState transformations[7][7] = {
// b= Unknown | Positive | Negative | Zero | NonNegative | NonPositive | Never |
/* a=Unknown */ {s::Unknown, s::Unknown, s::Unknown, s::Unknown, s::Unknown, s::Unknown, s::Unknown },
/* a=Positive */ {s::Unknown, s::Positive, s::Unknown, s::NonNegative, s::NonNegative, s::Unknown, s::Positive },
/* a=Negative */ {s::Unknown, s::Unknown, s::Negative, s::NonPositive, s::Unknown, s::NonPositive, s::Negative },
/* a=Zero */ {s::Unknown, s::NonNegative, s::NonPositive, s::Zero, s::NonNegative, s::NonPositive, s::Zero },
/* a=NonNegative */ {s::Unknown, s::NonNegative, s::Unknown, s::NonNegative, s::NonNegative, s::Unknown, s::NonNegative},
/* a=NonPositive */ {s::Unknown, s::Unknown, s::NonPositive, s::NonPositive, s::Unknown, s::NonPositive, s::NonPositive},
/* a=Never */ {s::Unknown, s::Positive, s::Negative, s::Zero, s::NonNegative, s::NonPositive, s::Never }
};
return transformations[static_cast<int>(a)][static_cast<int>(b)];
}
// merge (unify) two bool state: what state do we definitely have
// it's used on data flow rejoin
// example: `if (x) ... else ...`; lca(AlwaysTrue, AlwaysFalse) = Unknown
BoolState calculate_bool_lca(BoolState a, BoolState b) {
using s = BoolState;
static constexpr BoolState transformations[4][4] = {
// b= Unknown | AlwaysTrue | AlwaysFalse | Never |
/* a=Unknown */ {s::Unknown, s::Unknown, s::Unknown, s::Unknown },
/* a=AlwaysTrue */ {s::Unknown, s::AlwaysTrue, s::Unknown, s::AlwaysTrue },
/* a=AlwaysFalse */ {s::Unknown, s::Unknown, s::AlwaysFalse, s::AlwaysFalse},
/* a=Never */ {s::Unknown, s::AlwaysTrue, s::AlwaysFalse, s::Never }
};
return transformations[static_cast<int>(a)][static_cast<int>(b)];
}
// see comments above TypeInferringUnifyStrategy
// this function calculates lca or currently stored result and next
bool TypeInferringUnifyStrategy::unify_with(TypePtr next) {
if (unified_result == nullptr) {
unified_result = next;
return true;
}
if (unified_result == next) {
return true;
}
TypePtr combined = calculate_type_lca(unified_result, next);
if (!combined) {
return false;
}
unified_result = combined;
return true;
}
bool TypeInferringUnifyStrategy::unify_with_implicit_return_void() {
if (unified_result == nullptr) {
unified_result = TypeDataVoid::create();
return true;
}
return unified_result == TypeDataVoid::create();
}
// invalidate knowledge about sub-fields of a variable or its field
// example: `tensorVar = 2`, invalidate facts about `tensorVar`, `tensorVar.0`, `tensorVar.1.2`, and all others
// example: `user.id = rhs`, invalidate facts about `user.id` (sign, etc.) and `user.id.*` if exist
void FlowContext::invalidate_all_subfields(LocalVarPtr var_ref, uint64_t parent_path, uint64_t parent_mask) {
for (auto it = known_facts.begin(); it != known_facts.end();) {
bool is_self_or_field = it->first.var_ref == var_ref && (it->first.index_path & parent_mask) == parent_path;
if (is_self_or_field) {
it = known_facts.erase(it);
} else {
++it;
}
}
}
// update current type of `local_var` / `tensorVar.0` / `obj.field`
// example: `local_var = rhs`
// example: `f(mutate obj.field)`
// example: `if (t.0 != null)`, in true_flow `t.0` assigned to "not-null of current", in false_flow to null
void FlowContext::register_known_type(SinkExpression s_expr, TypePtr assigned_type) {
// having index_path = (some bytes filled in the end),
// calc index_mask: replace every filled byte with 0xFF
// example: `t.0.1`, index_path = (1<<8) + 2, index_mask = 0xFFFF
uint64_t index_path = s_expr.index_path;
uint64_t index_mask = 0;
while (index_path > 0) {
index_mask = index_mask << 8 | 0xFF;
index_path >>= 8;
}
invalidate_all_subfields(s_expr.var_ref, s_expr.index_path, index_mask);
// if just `int` assigned, we have no considerations about its sign
// so, even if something existed by the key s_expr, drop all knowledge
known_facts[s_expr] = FactsAboutExpr(assigned_type, SignState::Unknown, BoolState::Unknown);
}
// mark control flow unreachable / interrupted
void FlowContext::mark_unreachable(UnreachableKind reason) {
unreachable = true;
// currently we don't save why control flow became unreachable (it's not obvious how, there may be consequent reasons),
// but it helps debugging and reading outer code
static_cast<void>(reason);
}
// "merge" two data-flow contexts occurs on control flow rejoins (if/else branches merging, for example)
// it's generating a new context that describes "knowledge that definitely outcomes from these two"
// example: in one branch x is `int`, in x is `null`, result is `int?` unless any of them is unreachable
FlowContext FlowContext::merge_flow(FlowContext&& c1, FlowContext&& c2) {
if (!c1.unreachable && c2.unreachable) {
return merge_flow(std::move(c2), std::move(c1));
}
std::map<SinkExpression, FactsAboutExpr> unified;
if (c1.unreachable && !c2.unreachable) {
// `if (...) return; else ...;` — copy facts about common variables only from else (c2)
for (const auto& [s_expr, i2] : c2.known_facts) {
auto it1 = c1.known_facts.find(s_expr);
bool need_add = it1 != c1.known_facts.end() || s_expr.index_path != 0;
if (need_add) {
unified.emplace(s_expr, i2);
}
}
} else {
// either both reachable, or both not — merge types and restrictions of common variables and fields
for (const auto& [s_expr, i1] : c1.known_facts) {
if (auto it2 = c2.known_facts.find(s_expr); it2 != c2.known_facts.end()) {
const FactsAboutExpr& i2 = it2->second;
unified.emplace(s_expr, i1 == i2 ? i1 : FactsAboutExpr(
calculate_type_lca(i1.expr_type, i2.expr_type),
calculate_sign_lca(i1.sign_state, i2.sign_state),
calculate_bool_lca(i1.bool_state, i2.bool_state)
));
}
}
}
return FlowContext(std::move(unified), c1.unreachable && c2.unreachable);
}
// return `T`, so that `T?` = type
// what for: `if (x != null)`, to smart cast x inside if
TypePtr calculate_type_subtract_null(TypePtr type) {
if (const auto* as_nullable = type->try_as<TypeDataNullable>()) {
return as_nullable->inner;
}
// union types will be handled here
return TypeDataNever::create();
}
// given any expression vertex, extract SinkExpression is possible
// example: `x.0` is { var_ref: x, index_path: 1 }
// example: `x.1` is { var_ref: x, index_path: 2 }
// example: `x!.1` is the same
// example: `x.1.2` is { var_ref: x, index_path: 2<<8 + 3 }
// example: `x!.1!.2` is the same
// not SinkExpressions: `globalVar` / `f()` / `obj.method().1`
SinkExpression extract_sink_expression_from_vertex(AnyExprV v) {
if (auto as_ref = v->try_as<ast_reference>()) {
if (LocalVarPtr var_ref = as_ref->sym->try_as<LocalVarPtr>()) {
return SinkExpression(var_ref);
}
}
if (auto as_dot = v->try_as<ast_dot_access>(); as_dot && as_dot->is_target_indexed_access()) {
V<ast_dot_access> cur_dot = as_dot;
uint64_t index_path = 0;
while (cur_dot->is_target_indexed_access()) {
int index_at = std::get<int>(cur_dot->target);
index_path = (index_path << 8) + index_at + 1;
if (auto parent_dot = unwrap_not_null_operator(cur_dot->get_obj())->try_as<ast_dot_access>()) {
cur_dot = parent_dot;
} else {
break;
}
}
if (auto as_ref = unwrap_not_null_operator(cur_dot->get_obj())->try_as<ast_reference>()) {
if (LocalVarPtr var_ref = as_ref->sym->try_as<LocalVarPtr>()) {
return SinkExpression(var_ref, index_path);
}
}
}
if (auto as_par = v->try_as<ast_parenthesized_expression>()) {
return extract_sink_expression_from_vertex(as_par->get_expr());
}
if (auto as_assign = v->try_as<ast_assign>()) {
return extract_sink_expression_from_vertex(as_assign->get_lhs());
}
return {};
}
// given `lhs = rhs`, calculate "original" type of `lhs`
// example: `var x: int? = ...; if (x != null) { x (here) = null; }`
// "(here)" x is `int` (smart cast), but originally declared as `int?`
// example: `if (x is (int,int)?) { x!.0 = rhs }`, here `x!.0` is `int`
TypePtr calc_declared_type_before_smart_cast(AnyExprV v) {
if (auto as_ref = v->try_as<ast_reference>()) {
if (LocalVarPtr var_ref = as_ref->sym->try_as<LocalVarPtr>()) {
return var_ref->declared_type;
}
}
if (auto as_dot = v->try_as<ast_dot_access>(); as_dot && as_dot->is_target_indexed_access()) {
TypePtr obj_type = as_dot->get_obj()->inferred_type; // v already inferred; hence, index_at is correct
int index_at = std::get<int>(as_dot->target);
if (const auto* t_tensor = obj_type->try_as<TypeDataTensor>()) {
return t_tensor->items[index_at];
}
if (const auto* t_tuple = obj_type->try_as<TypeDataTypedTuple>()) {
return t_tuple->items[index_at];
}
}
return v->inferred_type;
}
// given `lhs = rhs` (and `var x = rhs`), calculate probable smart cast for lhs
// it's NOT directly type of rhs! see comment at the top of the file about internal structure of tensors/tuples.
// obvious example: `var x: int? = 5`, it's `int` (most cases are like this)
// obvious example: `var x: (int,int)? = null`, it's `null` (`x == null` is always true, `x` can be passed to any `T?`)
// not obvious example: `var x: (int?, int?)? = (3,null)`, result is `(int?,int?)`, whereas type of rhs is `(int,null)`
TypePtr calc_smart_cast_type_on_assignment(TypePtr lhs_declared_type, TypePtr rhs_inferred_type) {
// assign `T` to `T?` (or at least "assignable-to-T" to "T?")
// smart cast to `T`
if (const auto* lhs_nullable = lhs_declared_type->try_as<TypeDataNullable>()) {
if (lhs_nullable->inner->can_rhs_be_assigned(rhs_inferred_type)) {
return lhs_nullable->inner;
}
}
// assign `null` to `T?`
// smart cast to `null`
if (lhs_declared_type->try_as<TypeDataNullable>() && rhs_inferred_type == TypeDataNullLiteral::create()) {
return TypeDataNullLiteral::create();
}
// no smart cast, type is the same as declared
// example: `var x: (int?,slice?) = (1, null)`, it's `(int?,slice?)`, not `(int,null)`
return lhs_declared_type;
}
std::ostream& operator<<(std::ostream& os, const FlowContext& flow) {
os << "(" << flow.known_facts.size() << " facts) " << (flow.unreachable ? "(unreachable) " : "");
for (const auto& [s_expr, facts] : flow.known_facts) {
os << ", " << s_expr.to_string() << ": " << facts;
}
return os;
}
std::ostream& operator<<(std::ostream& os, const FactsAboutExpr& facts) {
os << facts.expr_type;
if (facts.sign_state != SignState::Unknown) {
os << " " << to_string(facts.sign_state);
}
if (facts.bool_state != BoolState::Unknown) {
os << " " << to_string(facts.bool_state);
}
return os;
}
} // namespace tolk

Some files were not shown because too many files have changed in this diff Show more