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:
commit
6eeae5d78b
110 changed files with 5744 additions and 1843 deletions
|
@ -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";
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// A part of standard library for Tolk
|
||||
tolk 0.8
|
||||
tolk 0.9
|
||||
|
||||
/**
|
||||
Gas and payment related primitives.
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
|
@ -147,5 +147,5 @@ fun main() {
|
|||
// x.0 x.1
|
||||
"""
|
||||
|
||||
@code_hash 7627024945492125068389905298530400936797031708759561372406088054030801992712
|
||||
@code_hash 61280273714870328160131559159866470128402169974050439159015534193532598351244
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:<{
|
||||
...
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:<{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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`
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
|
@ -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
|
||||
*/
|
|
@ -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
|
||||
*/
|
|
@ -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`
|
||||
*/
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
11
tolk-tester/tests/invalid-generics-13.tolk
Normal file
11
tolk-tester/tests/invalid-generics-13.tolk
Normal 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?`
|
||||
*/
|
17
tolk-tester/tests/invalid-generics-14.tolk
Normal file
17
tolk-tester/tests/invalid-generics-14.tolk
Normal 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);
|
||||
*/
|
|
@ -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
|
||||
*/
|
||||
|
|
10
tolk-tester/tests/invalid-mutate-18.tolk
Normal file
10
tolk-tester/tests/invalid-mutate-18.tolk
Normal 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
|
||||
*/
|
10
tolk-tester/tests/invalid-mutate-19.tolk
Normal file
10
tolk-tester/tests/invalid-mutate-19.tolk
Normal 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
|
||||
*/
|
13
tolk-tester/tests/invalid-mutate-20.tolk
Normal file
13
tolk-tester/tests/invalid-mutate-20.tolk
Normal 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
|
||||
*/
|
8
tolk-tester/tests/invalid-never-1.tolk
Normal file
8
tolk-tester/tests/invalid-never-1.tolk
Normal 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
|
||||
*/
|
|
@ -3,6 +3,7 @@ fun failBitwiseNotOnBool() {
|
|||
if (~eq) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
14
tolk-tester/tests/invalid-typing-14.tolk
Normal file
14
tolk-tester/tests/invalid-typing-14.tolk
Normal 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`
|
||||
*/
|
13
tolk-tester/tests/invalid-typing-15.tolk
Normal file
13
tolk-tester/tests/invalid-typing-15.tolk
Normal 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?`
|
||||
*/
|
13
tolk-tester/tests/invalid-typing-16.tolk
Normal file
13
tolk-tester/tests/invalid-typing-16.tolk
Normal 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?`
|
||||
*/
|
10
tolk-tester/tests/invalid-typing-17.tolk
Normal file
10
tolk-tester/tests/invalid-typing-17.tolk
Normal 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
|
||||
*/
|
16
tolk-tester/tests/invalid-typing-18.tolk
Normal file
16
tolk-tester/tests/invalid-typing-18.tolk
Normal 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
|
||||
*/
|
12
tolk-tester/tests/invalid-typing-19.tolk
Normal file
12
tolk-tester/tests/invalid-typing-19.tolk
Normal 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
|
||||
*/
|
15
tolk-tester/tests/invalid-typing-20.tolk
Normal file
15
tolk-tester/tests/invalid-typing-20.tolk
Normal 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`
|
||||
*/
|
14
tolk-tester/tests/invalid-typing-21.tolk
Normal file
14
tolk-tester/tests/invalid-typing-21.tolk
Normal 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`
|
||||
*/
|
9
tolk-tester/tests/invalid-typing-22.tolk
Normal file
9
tolk-tester/tests/invalid-typing-22.tolk
Normal 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`
|
||||
*/
|
15
tolk-tester/tests/invalid-typing-23.tolk
Normal file
15
tolk-tester/tests/invalid-typing-23.tolk
Normal 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?`
|
||||
*/
|
16
tolk-tester/tests/invalid-typing-24.tolk
Normal file
16
tolk-tester/tests/invalid-typing-24.tolk
Normal 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
|
||||
*/
|
14
tolk-tester/tests/invalid-typing-25.tolk
Normal file
14
tolk-tester/tests/invalid-typing-25.tolk
Normal 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
|
||||
*/
|
12
tolk-tester/tests/invalid-typing-26.tolk
Normal file
12
tolk-tester/tests/invalid-typing-26.tolk
Normal 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?`
|
||||
*/
|
18
tolk-tester/tests/invalid-typing-27.tolk
Normal file
18
tolk-tester/tests/invalid-typing-27.tolk
Normal 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?`
|
||||
*/
|
15
tolk-tester/tests/invalid-typing-28.tolk
Normal file
15
tolk-tester/tests/invalid-typing-28.tolk
Normal 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`
|
||||
*/
|
14
tolk-tester/tests/invalid-typing-29.tolk
Normal file
14
tolk-tester/tests/invalid-typing-29.tolk
Normal 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`
|
||||
*/
|
15
tolk-tester/tests/invalid-typing-30.tolk
Normal file
15
tolk-tester/tests/invalid-typing-30.tolk
Normal 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?`
|
||||
*/
|
9
tolk-tester/tests/invalid-typing-44.tolk
Normal file
9
tolk-tester/tests/invalid-typing-44.tolk
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun cantAssignIntToTensor() {
|
||||
var (x, y) = 2;
|
||||
x + y;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not assign `int` to a tensor
|
||||
*/
|
9
tolk-tester/tests/invalid-typing-45.tolk
Normal file
9
tolk-tester/tests/invalid-typing-45.tolk
Normal 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
|
||||
*/
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}>
|
||||
|
|
28
tolk-tester/tests/never-type-tests.tolk
Normal file
28
tolk-tester/tests/never-type-tests.tolk
Normal 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
|
||||
*/
|
|
@ -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
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
492
tolk-tester/tests/nullable-tensors.tolk
Normal file
492
tolk-tester/tests/nullable-tensors.tolk
Normal 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
|
||||
}>
|
||||
"""
|
||||
*/
|
109
tolk-tester/tests/nullable-types.tolk
Normal file
109
tolk-tester/tests/nullable-types.tolk
Normal 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
|
||||
*/
|
678
tolk-tester/tests/smart-cast-tests.tolk
Normal file
678
tolk-tester/tests/smart-cast-tests.tolk
Normal 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:<{
|
||||
*/
|
|
@ -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 //
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
22
tolk-tester/tests/unreachable-3.tolk
Normal file
22
tolk-tester/tests/unreachable-3.tolk
Normal 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
|
||||
*/
|
24
tolk-tester/tests/unreachable-4.tolk
Normal file
24
tolk-tester/tests/unreachable-4.tolk
Normal 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;
|
||||
*/
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
28
tolk-tester/tests/warnings-1.tolk
Normal file
28
tolk-tester/tests/warnings-1.tolk
Normal 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
|
||||
*/
|
26
tolk-tester/tests/warnings-2.tolk
Normal file
26
tolk-tester/tests/warnings-2.tolk
Normal 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
|
||||
*/
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
|
|
31
tolk/ast.cpp
31
tolk/ast.cpp
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
75
tolk/ast.h
75
tolk/ast.h
|
@ -88,6 +88,8 @@ enum ASTNodeType {
|
|||
ast_binary_operator,
|
||||
ast_ternary_operator,
|
||||
ast_cast_as_operator,
|
||||
ast_not_null_operator,
|
||||
ast_is_null_check,
|
||||
// statements
|
||||
ast_empty_statement,
|
||||
ast_sequence,
|
||||
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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*;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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>());
|
||||
|
|
586
tolk/pipe-check-inferred-types.cpp
Normal file
586
tolk/pipe-check-inferred-types.cpp
Normal 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
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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(¶m_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>());
|
||||
|
|
|
@ -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
472
tolk/smart-casts-cfg.cpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue