mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
[Tolk] Nullable types T?
and null safety
This commit introduces nullable types `T?` that are distinct from non-nullable `T`. Example: `int?` (int or null) and `int` are different now. Previously, `null` could be assigned to any primitive type. Now, it can be assigned only to `T?`. A non-null assertion operator `!` was also introduced, similar to `!` in TypeScript and `!!` in Kotlin. If `int?` still occupies 1 stack slot, `(int,int)?` and other nullable tensors occupy N+1 slots, the last for "null precedence". `v == null` actually compares that slot. Assigning `(int,int)` to `(int,int)?` implicitly creates a null presence slot. Assigning `null` to `(int,int)?` widens this null value to 3 slots. This is called "type transitioning". All stdlib functions prototypes have been updated to reflect whether they return/accept a nullable or a strict value. This commit also contains refactoring from `const FunctionData*` to `FunctionPtr` and similar.
This commit is contained in:
parent
1389ff6789
commit
f3e620f48c
62 changed files with 2031 additions and 702 deletions
|
@ -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";
|
||||
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -206,9 +206,9 @@ fun test116() {
|
|||
|
||||
|
||||
fun main(value: int) {
|
||||
var (x: int, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
|
||||
var (x: int?, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
|
||||
if (x == null && y == null) { return null; }
|
||||
return x == null || y == null ? -1 : x + y;
|
||||
return x == null || y == null ? -1 : x! + y!;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -86,8 +86,8 @@ fun test104() {
|
|||
}
|
||||
|
||||
@method_id(105)
|
||||
fun test105(x: int, y: int): (tuple, int, (int, int), int, int) {
|
||||
var ab = (createEmptyTuple(), (x, y), tupleSize);
|
||||
fun test105(x: int, y: int): (tuple, int, (int?, int), int, int) {
|
||||
var ab = (createEmptyTuple(), (x as int?, y), tupleSize);
|
||||
ab.0.tuplePush(1);
|
||||
tuplePush(mutate ab.0, 2);
|
||||
ab.1.0 = null;
|
||||
|
@ -98,7 +98,7 @@ fun test105(x: int, y: int): (tuple, int, (int, int), int, int) {
|
|||
|
||||
@method_id(106)
|
||||
fun test106(x: int, y: int) {
|
||||
var ab = [createEmptyTuple(), [x, y], tupleSize];
|
||||
var ab = [createEmptyTuple(), [x as int?, y], tupleSize];
|
||||
ab.0.tuplePush(1);
|
||||
tuplePush(mutate ab.0, 2);
|
||||
ab.1.0 = null;
|
||||
|
@ -233,6 +233,25 @@ fun test121(zero: int) {
|
|||
return t;
|
||||
}
|
||||
|
||||
fun isFirstComponentGt0<T1,T2>(t: (T1, T2)): bool {
|
||||
return t.0 > 0;
|
||||
}
|
||||
|
||||
@method_id(122)
|
||||
fun test122(x: (int, int)) {
|
||||
return (
|
||||
isFirstComponentGt0(x), isFirstComponentGt0((2, beginCell())), isFirstComponentGt0<int,slice?>((0, null)),
|
||||
x.isFirstComponentGt0(), (2, beginCell()).isFirstComponentGt0(), (0, null).isFirstComponentGt0<int,slice?>()
|
||||
);
|
||||
}
|
||||
|
||||
@method_id(123)
|
||||
fun test123() {
|
||||
var t = [[10, 20]] as [[int,int]]?;
|
||||
t!.0.0 = t!.0.1 = 100;
|
||||
return t;
|
||||
}
|
||||
|
||||
fun main(){}
|
||||
|
||||
|
||||
|
@ -258,6 +277,8 @@ fun main(){}
|
|||
@testcase | 119 | 1 2 3 4 | 4 1 3
|
||||
@testcase | 120 | | 3 4 [ 5 6 ]
|
||||
@testcase | 121 | 0 | [ 3 ]
|
||||
@testcase | 122 | 1 2 | -1 -1 0 -1 -1 0
|
||||
@testcase | 123 | | [ [ 100 100 ] ]
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 while instantiating generic function `calcSum<int?>`
|
||||
@stderr can not apply operator `+` to `int?` and `int?`
|
||||
*/
|
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
|
||||
*/
|
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
|
||||
*/
|
|
@ -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
|
||||
|
|
|
@ -2,13 +2,13 @@ import "@stdlib/lisp-lists"
|
|||
|
||||
@method_id(101)
|
||||
fun test1() {
|
||||
var numbers: tuple = createEmptyList();
|
||||
var numbers: tuple? = createEmptyList();
|
||||
numbers = listPrepend(1, numbers);
|
||||
numbers = listPrepend(2, numbers);
|
||||
numbers = listPrepend(3, numbers);
|
||||
numbers = listPrepend(4, numbers);
|
||||
var (h: int, numbers redef) = listSplit(numbers);
|
||||
h += listGetHead(numbers);
|
||||
var (h: int, numbers redef) = listSplit(numbers!);
|
||||
h += listGetHead(numbers!);
|
||||
|
||||
_ = null;
|
||||
(_, _) = (null, null);
|
||||
|
@ -22,22 +22,22 @@ fun test1() {
|
|||
}
|
||||
|
||||
@method_id(102)
|
||||
fun test2(x: int) {
|
||||
fun test2(x: int?) {
|
||||
if (null != x) {
|
||||
var y: int = null;
|
||||
var y: int? = null;
|
||||
if (y != null) { return 10; }
|
||||
return y;
|
||||
}
|
||||
try {
|
||||
return x + 10; // will throw, since not a number
|
||||
return x! + 10; // will throw, since not a number
|
||||
} catch {
|
||||
return -1;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
fun myIsNull(x: int): int {
|
||||
return x == null ? -1 : x;
|
||||
fun myIsNull(x: int?): int {
|
||||
return x == null ? -1 : x!;
|
||||
}
|
||||
|
||||
@method_id(103)
|
||||
|
@ -64,21 +64,28 @@ fun test4(): null {
|
|||
|
||||
@method_id(105)
|
||||
fun test5() {
|
||||
var n: slice = getUntypedNull();
|
||||
return !(null == n) ? n.loadInt(32) : 100;
|
||||
var n: slice? = getUntypedNull();
|
||||
return !(null == n) ? n!.loadInt(32) : 100;
|
||||
}
|
||||
|
||||
@method_id(107)
|
||||
fun test7() {
|
||||
var b = beginCell().storeMaybeRef(null);
|
||||
var s = b.endCell().beginParse();
|
||||
var b = beginCell().storeMaybeRef(null) as builder?;
|
||||
var s = b!.endCell().beginParse();
|
||||
var c = s.loadMaybeRef();
|
||||
return (null == c) as int * 10 + (b != null) as int;
|
||||
}
|
||||
|
||||
fun test8() {
|
||||
__expect_type(null, "null");
|
||||
__expect_type([[null]], "[[null]]");
|
||||
__expect_type(null as tuple?, "tuple?");
|
||||
__expect_type(null as [int]?, "[int]?");
|
||||
__expect_type(((null)) as (int, int)?, "(int, int)?");
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// now, the compiler doesn't optimize this at compile-time, fif codegen contains ifs
|
||||
var i: int = null;
|
||||
var i: int? = null;
|
||||
if (i == null) {
|
||||
return 1;
|
||||
}
|
||||
|
@ -139,8 +146,8 @@ fun main() {
|
|||
10 MULCONST // b '13
|
||||
SWAP // '13 b
|
||||
ISNULL // '13 '14
|
||||
NOT // '13 '15
|
||||
ADD // '16
|
||||
NOT // '13 '14
|
||||
ADD // '15
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
474
tolk-tester/tests/nullable-tensors.tolk
Normal file
474
tolk-tester/tests/nullable-tensors.tolk
Normal file
|
@ -0,0 +1,474 @@
|
|||
fun getNullableInt(): int? { return 5; }
|
||||
|
||||
fun sumOfNullableTensorComponents(t: (int, int)?): int {
|
||||
if (t == null) { return 0; }
|
||||
return t!.0 + t!.1;
|
||||
}
|
||||
|
||||
fun isTensorNull(t: (int, int)?) {
|
||||
return t == null;
|
||||
}
|
||||
|
||||
fun incrementNullableTensorComponents(mutate self: (int, int)?): self {
|
||||
if (self != null) {
|
||||
self!.0 += 1;
|
||||
self!.1 += 1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
fun incrementTensorComponents(mutate self: (int, int)): self {
|
||||
self.0 += 1;
|
||||
self.1 += 1;
|
||||
return self;
|
||||
}
|
||||
|
||||
fun assignFirstComponent(mutate t: (int, int), first: int) {
|
||||
t!.0 = first;
|
||||
}
|
||||
|
||||
fun assignFirstComponentNullable(mutate t: (int, int)?, first: int) {
|
||||
if (t == null) {
|
||||
t = (first, 0);
|
||||
} else {
|
||||
t!.0 = first;
|
||||
}
|
||||
}
|
||||
|
||||
fun getNullableTensor(firstComponent: int?): (int, int)? {
|
||||
return firstComponent == null ? null : (firstComponent!, 2);
|
||||
}
|
||||
|
||||
fun sumOfTensor(x: (int, int)) {
|
||||
return x.0 + x.1;
|
||||
}
|
||||
|
||||
fun assignNullTo<T>(mutate x: T?) {
|
||||
x = null;
|
||||
}
|
||||
|
||||
fun getTensor12() {
|
||||
return (1,2);
|
||||
}
|
||||
|
||||
@method_id(101)
|
||||
fun test101(): (int, int)? {
|
||||
return (1, 2);
|
||||
}
|
||||
|
||||
@method_id(102)
|
||||
fun test102(): ((int, int)?, (int, int)?) {
|
||||
var t = (1, 2);
|
||||
return (t, null);
|
||||
}
|
||||
|
||||
@method_id(103)
|
||||
fun test103(t: (int, int)) {
|
||||
var t2: (int, int)? = t;
|
||||
return (sumOfNullableTensorComponents(t), sumOfNullableTensorComponents(t2), sumOfNullableTensorComponents(null), t2);
|
||||
}
|
||||
|
||||
@method_id(104)
|
||||
fun test104() {
|
||||
var t1_1: (int, int)? = (1, 2);
|
||||
var t1_2: (int, int)? = t1_1;
|
||||
var t1_3: (int, int)? = t1_1!;
|
||||
var t2_1: (int, int)? = null;
|
||||
var t2_2 = t2_1;
|
||||
return (t1_3, t2_2);
|
||||
}
|
||||
|
||||
@method_id(105)
|
||||
fun test105() {
|
||||
return (null as (int, slice, cell)?, (1, 2, 3) as (int, int, int)?);
|
||||
}
|
||||
|
||||
@method_id(106)
|
||||
fun test106() {
|
||||
var t: (int?, int?)? = (((((1, 2))) as (int, int)));
|
||||
return t;
|
||||
}
|
||||
|
||||
@method_id(107)
|
||||
fun test107() {
|
||||
var ab = (1, 2);
|
||||
var ab2: (int, int)? = ab;
|
||||
return (isTensorNull(ab), isTensorNull(ab2), isTensorNull(null), ab.isTensorNull(), ab2.isTensorNull(), null.isTensorNull());
|
||||
}
|
||||
|
||||
@method_id(108)
|
||||
fun test108(x1: (int, int)) {
|
||||
incrementTensorComponents(mutate x1);
|
||||
x1.incrementTensorComponents();
|
||||
var x2: (int, int)? = x1;
|
||||
x2.incrementNullableTensorComponents().incrementNullableTensorComponents();
|
||||
incrementNullableTensorComponents(mutate x2);
|
||||
var x3: (int, int)? = null;
|
||||
x3.incrementNullableTensorComponents().incrementNullableTensorComponents();
|
||||
incrementNullableTensorComponents(mutate x3);
|
||||
return (x1, x2, x3);
|
||||
}
|
||||
|
||||
fun isTensorNullGen<T1, T2>(t: (T1, T2)?) {
|
||||
return t == null;
|
||||
}
|
||||
|
||||
@method_id(109)
|
||||
fun test109() {
|
||||
var x1 = (1, 2);
|
||||
var x2: (int, int)? = x1;
|
||||
var x3: (int, int)? = x1.1 > 10 ? (1, 2) : null;
|
||||
return (
|
||||
isTensorNullGen(x1), isTensorNullGen(x2), isTensorNullGen<int,int>(null),
|
||||
isTensorNullGen<int,int>(x1), isTensorNullGen<int,int>(x3),
|
||||
x1.isTensorNullGen(), x2.isTensorNullGen(), x3.isTensorNullGen(), null.isTensorNullGen<int,int>()
|
||||
);
|
||||
}
|
||||
|
||||
global g110_1: (int, int);
|
||||
global g110_2: (int, int)?;
|
||||
|
||||
@method_id(110)
|
||||
fun test110() {
|
||||
g110_1 = getNullableTensor(1)!;
|
||||
incrementTensorComponents(mutate g110_1);
|
||||
g110_1.incrementTensorComponents();
|
||||
g110_2 = g110_1;
|
||||
g110_2.incrementNullableTensorComponents().incrementNullableTensorComponents();
|
||||
incrementNullableTensorComponents(mutate g110_2);
|
||||
var tmp = g110_2;
|
||||
g110_2 = null;
|
||||
g110_2.incrementNullableTensorComponents();
|
||||
incrementNullableTensorComponents(mutate g110_2);
|
||||
return (g110_1, g110_2, tmp);
|
||||
}
|
||||
|
||||
@method_id(111)
|
||||
fun test111() {
|
||||
var x = (1, 2);
|
||||
assignFirstComponent(mutate x, 50);
|
||||
var x2: (int, int)? = null;
|
||||
var x3 = x2;
|
||||
assignFirstComponentNullable(mutate x2, 30);
|
||||
assignFirstComponentNullable(mutate x3, 70);
|
||||
g110_1 = (1, 2);
|
||||
g110_2 = null;
|
||||
assignFirstComponent(mutate g110_1, 90);
|
||||
assignFirstComponentNullable(mutate g110_2, 100);
|
||||
return (x.0, x2!.0, x3!.0, g110_1.0, g110_2!.0);
|
||||
}
|
||||
|
||||
@method_id(112)
|
||||
fun test112() {
|
||||
var x: (int, int)? = (10, 20);
|
||||
incrementTensorComponents(mutate x!);
|
||||
x!.incrementTensorComponents();
|
||||
return x;
|
||||
}
|
||||
|
||||
@method_id(113)
|
||||
fun test113() {
|
||||
var t = [1, null]; // t.1 is always null
|
||||
return isTensorNull(t.1);
|
||||
}
|
||||
|
||||
@method_id(114)
|
||||
fun test114(): ((slice, (cell, [int, slice, tuple]))?, slice?, (int?, bool?)?) {
|
||||
var t = [[null]];
|
||||
return (t.0.0, t.0.0, t.0.0);
|
||||
}
|
||||
|
||||
@method_id(115)
|
||||
fun test115() {
|
||||
var tt = getNullableTensor(null);
|
||||
assignFirstComponentNullable(mutate tt, 5);
|
||||
return (
|
||||
getNullableTensor(1)!.incrementTensorComponents(),
|
||||
sumOfNullableTensorComponents(getNullableTensor(1).incrementNullableTensorComponents().incrementNullableTensorComponents()),
|
||||
getNullableTensor(null).incrementNullableTensorComponents(),
|
||||
tt,
|
||||
sumOfNullableTensorComponents(getNullableTensor(null))
|
||||
);
|
||||
}
|
||||
|
||||
@method_id(116)
|
||||
fun test116(returnNull: bool) {
|
||||
var t1: (int, int)? = returnNull ? null : getTensor12();
|
||||
var t2 = returnNull ? null as (int, int)? : getTensor12() as (int, int)?;
|
||||
returnNull ? null : (1, 2);
|
||||
return (t1, t2);
|
||||
}
|
||||
|
||||
@method_id(117)
|
||||
fun test117() {
|
||||
var (a, b: (int, int)?, c) = (1, null, 3);
|
||||
return (b, a, c);
|
||||
}
|
||||
|
||||
fun autoInferNullableTensor(a: int?, b: int) {
|
||||
if (a != null) {
|
||||
return (a!, b);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@method_id(118)
|
||||
fun test118(a: int?) {
|
||||
return autoInferNullableTensor(a, 10);
|
||||
}
|
||||
|
||||
@method_id(119)
|
||||
fun test119() {
|
||||
var x: (int, int)? = (1, 2);
|
||||
x = null;
|
||||
var tt: (int, (int, int)?) = (0, (1, 2));
|
||||
tt.1 = null;
|
||||
var third: (int, (int, int)?, int) = (0, (1, 2), 3);
|
||||
third.2 = 100;
|
||||
return (x, tt.1, third.1, third.2);
|
||||
}
|
||||
|
||||
@method_id(120)
|
||||
fun test120(setNull: bool) {
|
||||
var x: (int, int)? = (1, 2);
|
||||
if (setNull) {
|
||||
assignNullTo(mutate x);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@method_id(121)
|
||||
fun test121() {
|
||||
var t: [int?, [int?, int?]?] = [1, [2, 3]];
|
||||
t.1 = [3, 4];
|
||||
return t;
|
||||
}
|
||||
|
||||
@method_id(122)
|
||||
fun test122(setNull: bool) {
|
||||
var t: [int?, [int?, int?]?, int?, [int?, int?]?]? = [1, [2, 3], 4, null];
|
||||
if (setNull) {
|
||||
assignNullTo(mutate t!.1);
|
||||
} else {
|
||||
var rhs = [3, 4];
|
||||
t!!.1 = rhs;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
@method_id(123)
|
||||
fun test123() {
|
||||
var t: (int?, (int?, int?)?) = (1, (2, 3));
|
||||
t.1 = (3, 4);
|
||||
return t;
|
||||
}
|
||||
|
||||
@method_id(124)
|
||||
fun test124(setNull: bool) {
|
||||
var t: (int?, (int?, int?)?, int?, (int?, int?)?)? = (1, (2, 3), 4, null);
|
||||
if (setNull) {
|
||||
assignNullTo(mutate t!.1);
|
||||
} else {
|
||||
var rhs = (3, 4);
|
||||
t!!.1 = rhs;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
global g125: int;
|
||||
fun getT125(): (int, (int, int)?, (int?, int)?) { return (g125 += 1, null, null); }
|
||||
|
||||
@method_id(125)
|
||||
fun test125() {
|
||||
g125 = 0;
|
||||
getT125().1 = null;
|
||||
getT125().2 = (1, 2);
|
||||
(getT125()!! as (int, (int, int)?, (int?, int)?)).2 = null;
|
||||
// test that nothing left on a stack
|
||||
return g125;
|
||||
}
|
||||
|
||||
@method_id(126)
|
||||
fun test126() {
|
||||
var tt1: (int, null, int) = (1, null, 2);
|
||||
var (a: int, b: (int, int)?, c: int) = tt1;
|
||||
return (a, b, c);
|
||||
}
|
||||
|
||||
@method_id(127)
|
||||
fun test127(choice: int) {
|
||||
var tt1: (int, null, int) = (1, null, 2);
|
||||
var tt2: (int, (int, int), int) = (1, (2, 3), 4);
|
||||
var tt3: (int, (int, int)?, int) = (1, null, 5);
|
||||
var abc: (int, (int, int)?, int) = choice == 1 ? tt1 : choice == 2 ? tt2 : tt3;
|
||||
return abc;
|
||||
}
|
||||
|
||||
fun get128_1() { return (1, null, 2); }
|
||||
fun get128_2() { return null; }
|
||||
fun get128_3() { return (1, (2, 3), 4); }
|
||||
fun takeT128(abc: (int, (int, int)?, int)?) { return abc; }
|
||||
|
||||
@method_id(128)
|
||||
fun test128(choice: int) {
|
||||
if (choice == 1) {
|
||||
return takeT128(get128_1())!;
|
||||
}
|
||||
if (choice == 2) {
|
||||
return takeT128(get128_2());
|
||||
}
|
||||
return takeT128(get128_3());
|
||||
}
|
||||
|
||||
@method_id(129)
|
||||
fun test129(setNull: bool) {
|
||||
var t: (int?, int?) = (getNullableInt(), getNullableInt());
|
||||
var r1 = (t, t == null, t != null);
|
||||
t = (setNull ? null : 1, setNull ? null : 2);
|
||||
var r2 = (t, t == null, t != null);
|
||||
return (r1, r2);
|
||||
}
|
||||
|
||||
@method_id(130)
|
||||
fun test130(setNull: bool) {
|
||||
var os: (int, (int, int)?) = (1, setNull ? null : (2, 3));
|
||||
return os;
|
||||
}
|
||||
|
||||
fun getEmptyNullableTensor(getNull: bool): ()? {
|
||||
return getNull ? null : ();
|
||||
}
|
||||
|
||||
@method_id(131)
|
||||
fun test131() {
|
||||
var nonNullEmptyT = getEmptyNullableTensor(false);
|
||||
var nullEmptyT = getEmptyNullableTensor(true);
|
||||
var emptyT = nonNullEmptyT!;
|
||||
__expect_type(emptyT, "()");
|
||||
var doubleNulls1 = (null, null) as (()?, ()?);
|
||||
var doubleNulls2 = ((), ()) as (()?, ()?);
|
||||
var doubleNulls3 = ((), ()) as (()?, ()?)?;
|
||||
var stillEmpty = ((), ());
|
||||
return (nonNullEmptyT, 777, nullEmptyT, 777, emptyT, 777, nullEmptyT!, 777, doubleNulls1, doubleNulls2, 777, doubleNulls3, 777, stillEmpty);
|
||||
}
|
||||
|
||||
@method_id(132)
|
||||
fun test132() {
|
||||
var doubleNulls: (()?, ()?) = (getEmptyNullableTensor(true), getEmptyNullableTensor(false));
|
||||
var result = ((null as ()?) == null, (() as ()?) == null, doubleNulls.0 == null, doubleNulls.1 == null);
|
||||
var aln1: int? = (doubleNulls.1 = null);
|
||||
var aln2: null = (doubleNulls.1 = null);
|
||||
return (result, 777, aln1, aln2, doubleNulls.1 == null, doubleNulls);
|
||||
}
|
||||
|
||||
|
||||
fun getNormalNullableTensorWidth1(vLess100: int?): ([int?], ())? {
|
||||
if (vLess100 != null && vLess100! >= 100) {
|
||||
return null;
|
||||
}
|
||||
return ([vLess100], ()); // such a nullable tensor can store NULL in the same slot
|
||||
}
|
||||
|
||||
fun getTrickyNullableTensorWidth1(vLess100: int?): (int?, ())? {
|
||||
if (vLess100 != null && vLess100! >= 100) {
|
||||
return null;
|
||||
}
|
||||
return (vLess100, ()); // such a nullable tensor requires an extra stack slot for null presence
|
||||
}
|
||||
|
||||
fun getEvenTrickierNullableWidth1(vLess100: int?): ((), (int?, ()), ())? {
|
||||
if (vLess100 != null && vLess100! >= 100) {
|
||||
return null;
|
||||
}
|
||||
return ((), (vLess100, ()), ());
|
||||
}
|
||||
|
||||
@method_id(135)
|
||||
fun test135() {
|
||||
var n1 = getNormalNullableTensorWidth1(10); // ([10], ())
|
||||
var n2 = getNormalNullableTensorWidth1(null); // ([null], ())
|
||||
var n3 = getNormalNullableTensorWidth1(100); // null
|
||||
var t1 = getTrickyNullableTensorWidth1(10); // (10, ())
|
||||
var t2 = getTrickyNullableTensorWidth1(null); // (null, ())
|
||||
var t3 = getTrickyNullableTensorWidth1(100); // null
|
||||
var e1 = getEvenTrickierNullableWidth1(10); // ((), (10, ()), ())
|
||||
var e2 = getEvenTrickierNullableWidth1(null); // ((), (null, (), ())
|
||||
var e3 = getEvenTrickierNullableWidth1(100); // null
|
||||
return (n1, n2, n3, 777, t1, t2, t3, 777, e1, e2, e3, 777,
|
||||
n1 == null, n2 == null, n3 == null, t1 == null, t2 == null, t3 == null, e1 == null, e2 == null, e3 == null, 777,
|
||||
t1!.0 == null, t2!.0 == null, e1!.1.0 == null, e1!.1.1 == null, e2!.1.0 == null, e2!.1.1 == null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun main(){}
|
||||
|
||||
/**
|
||||
@testcase | 101 | | 1 2 -1
|
||||
@testcase | 102 | | 1 2 -1 (null) (null) 0
|
||||
@testcase | 103 | 1 2 | 3 3 0 1 2 -1
|
||||
@testcase | 104 | | 1 2 -1 (null) (null) 0
|
||||
@testcase | 105 | | (null) (null) (null) 0 1 2 3 -1
|
||||
@testcase | 106 | | 1 2 -1
|
||||
@testcase | 107 | | 0 0 -1 0 0 -1
|
||||
@testcase | 108 | 5 6 | 7 8 10 11 -1 (null) (null) 0
|
||||
@testcase | 109 | | 0 0 -1 0 -1 0 0 -1 -1
|
||||
@testcase | 110 | | 3 4 (null) (null) 0 6 7 -1
|
||||
@testcase | 111 | | 50 30 70 90 100
|
||||
@testcase | 112 | | 12 22 -1
|
||||
@testcase | 113 | | -1
|
||||
@testcase | 114 | | (null) (null) (null) 0 (null) (null) (null) 0
|
||||
@testcase | 115 | | 2 3 7 (null) (null) 0 5 0 -1 0
|
||||
@testcase | 116 | -1 | (null) (null) 0 (null) (null) 0
|
||||
@testcase | 116 | 0 | 1 2 -1 1 2 -1
|
||||
@testcase | 117 | | (null) (null) 0 1 3
|
||||
@testcase | 118 | 5 | 5 10 -1
|
||||
@testcase | 118 | null | (null) (null) 0
|
||||
@testcase | 119 | | (null) (null) 0 (null) (null) 0 1 2 -1 100
|
||||
@testcase | 120 | -1 | (null) (null) 0
|
||||
@testcase | 120 | 0 | 1 2 -1
|
||||
@testcase | 121 | | [ 1 [ 3 4 ] ]
|
||||
@testcase | 122 | 0 | [ 1 [ 3 4 ] 4 (null) ]
|
||||
@testcase | 122 | -1 | [ 1 (null) 4 (null) ]
|
||||
@testcase | 123 | | 1 3 4 -1
|
||||
@testcase | 124 | 0 | 1 3 4 -1 4 (null) (null) 0 -1
|
||||
@testcase | 124 | -1 | 1 (null) (null) 0 4 (null) (null) 0 -1
|
||||
@testcase | 125 | | 3
|
||||
@testcase | 126 | | 1 (null) (null) 0 2
|
||||
@testcase | 127 | 1 | 1 (null) (null) 0 2
|
||||
@testcase | 127 | 2 | 1 2 3 -1 4
|
||||
@testcase | 127 | 3 | 1 (null) (null) 0 5
|
||||
@testcase | 128 | 1 | 1 (null) (null) 0 2 -1
|
||||
@testcase | 128 | 2 | (null) (null) (null) (null) (null) 0
|
||||
@testcase | 128 | 3 | 1 2 3 -1 4 -1
|
||||
@testcase | 129 | 0 | 5 5 0 -1 1 2 0 -1
|
||||
@testcase | 129 | -1 | 5 5 0 -1 (null) (null) 0 -1
|
||||
@testcase | 130 | 0 | 1 2 3 -1
|
||||
@testcase | 130 | -1 | 1 (null) (null) 0
|
||||
@testcase | 131 | | -1 777 0 777 777 777 0 0 -1 -1 777 -1 -1 -1 777
|
||||
@testcase | 132 | | -1 0 -1 0 777 (null) (null) -1 0 0
|
||||
@testcase | 135 | | [ 10 ] [ (null) ] (null) 777 10 -1 (null) -1 (null) 0 777 10 -1 (null) -1 (null) 0 777 0 0 -1 0 0 -1 0 0 -1 777 0 -1 0 0 -1 0
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
isTensorNull PROC:<{
|
||||
// t.0 t.1 t.NNFlag
|
||||
2 1 BLKDROP2 // t.NNFlag
|
||||
0 EQINT // '3
|
||||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
test113 PROC:<{
|
||||
//
|
||||
1 PUSHINT // '2=1
|
||||
PUSHNULL // '2=1 '3
|
||||
PAIR // t
|
||||
1 INDEX // '5
|
||||
PUSHNULL // '5 '6
|
||||
0 PUSHINT // '5 '6 '7=0
|
||||
isTensorNull CALLDICT // '8
|
||||
}>
|
||||
"""
|
||||
*/
|
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);
|
||||
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
|
||||
*/
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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,6 +410,10 @@ 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 (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()) {
|
||||
#ifdef TOLK_DEBUG
|
||||
tolk_assert(stack_w == 1);
|
||||
|
|
|
@ -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"},
|
||||
|
@ -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>());
|
||||
}
|
||||
|
|
22
tolk/ast.cpp
22
tolk/ast.cpp
|
@ -121,7 +121,7 @@ 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 +129,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 +137,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 +149,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 +157,27 @@ 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_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 +185,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;
|
||||
}
|
||||
|
||||
|
|
68
tolk/ast.h
68
tolk/ast.h
|
@ -88,6 +88,8 @@ enum ASTNodeType {
|
|||
ast_binary_operator,
|
||||
ast_ternary_operator,
|
||||
ast_cast_as_operator,
|
||||
ast_not_null_operator,
|
||||
ast_is_null_check,
|
||||
// statements
|
||||
ast_empty_statement,
|
||||
ast_sequence,
|
||||
|
@ -408,7 +410,7 @@ private:
|
|||
V<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 +419,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 +532,12 @@ private:
|
|||
public:
|
||||
|
||||
typedef std::variant<
|
||||
const FunctionData*, // for `t.tupleAt` target is `tupleAt` global function
|
||||
FunctionPtr, // for `t.tupleAt` target is `tupleAt` global function
|
||||
int // for `t.0` target is "indexed access" 0
|
||||
> DotTarget;
|
||||
DotTarget target = static_cast<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 +562,7 @@ template<>
|
|||
// example: `getF()()` then callee is another func call (which type is TypeDataFunCallable)
|
||||
// example: `obj.method()` then callee is dot access (resolved while type inferring)
|
||||
struct Vertex<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 +572,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 +605,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 +613,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 +624,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 +643,7 @@ template<>
|
|||
// examples: `a + b` / `x & true` / `(a, b) << g()`
|
||||
// note, that `a = b` is NOT a binary operator, it's ast_assign, also `a += b`, it's ast_set_assign
|
||||
struct Vertex<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 +651,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 +686,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) {}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// ---------------------------------------------------------
|
||||
|
@ -892,7 +920,7 @@ template<>
|
|||
// ast_parameter is a parameter of a function in its declaration
|
||||
// example: `fun f(a: int, mutate b: slice)` has 2 parameters
|
||||
struct Vertex<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 +928,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 +979,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 +990,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 +1003,13 @@ template<>
|
|||
// example: `global g: int;`
|
||||
// note, that globals don't have default values, since there is no single "entrypoint" for a contract
|
||||
struct Vertex<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 +1021,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)
|
||||
|
|
|
@ -348,9 +348,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);
|
||||
|
|
|
@ -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,33 +77,19 @@ 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
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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]
|
||||
provideDeducedT(asT->nameT, arg_type);
|
||||
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);
|
||||
}
|
||||
// `arg: T?` called as `f(int)` => T is int
|
||||
else {
|
||||
consider_next_condition(p_nullable->inner, 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()) {
|
||||
|
@ -101,21 +113,31 @@ public:
|
|||
consider_next_condition(p_callable->return_type, a_callable->return_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_first_not_deduced_idx() const {
|
||||
for (int i = 0; i < static_cast<int>(substitutions.size()); ++i) {
|
||||
if (substitutions[i] == nullptr) {
|
||||
TypePtr GenericSubstitutionsDeduceForCall::replace_by_manually_specified(TypePtr param_type) const {
|
||||
return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs);
|
||||
}
|
||||
|
||||
TypePtr GenericSubstitutionsDeduceForCall::auto_deduce_from_argument(SrcLocation loc, TypePtr param_type, TypePtr arg_type) {
|
||||
try {
|
||||
if (!manually_specified) {
|
||||
consider_next_condition(param_type, arg_type);
|
||||
}
|
||||
return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs);
|
||||
} catch (const GenericDeduceError& ex) {
|
||||
throw ParseError(loc, ex.message + " for generic function `" + fun_ref->as_human_readable() + "`; instantiate it manually with `" + fun_ref->name + "<...>()`");
|
||||
}
|
||||
}
|
||||
|
||||
int GenericSubstitutionsDeduceForCall::get_first_not_deduced_idx() const {
|
||||
for (int i = 0; i < static_cast<int>(substitutionTs.size()); ++i) {
|
||||
if (substitutionTs[i] == nullptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<TypePtr> flush() {
|
||||
return {std::move(substitutions)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// clone the body of `f<T>` replacing T everywhere with a substitution
|
||||
// before: `fun f<T>(v: T) { var cp: [T] = [v]; }`
|
||||
|
@ -175,7 +197,7 @@ int GenericsDeclaration::find_nameT(std::string_view nameT) const {
|
|||
|
||||
// after creating a deep copy of `f<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);
|
||||
|
@ -198,34 +220,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(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
|
||||
|
|
|
@ -32,12 +32,22 @@
|
|||
* So, if execution reaches this pass, the input is (almost) correct, and code generation should succeed.
|
||||
* (previously, there was a check for one variable modified twice like `(t.0, t.0) = rhs`, but after changing
|
||||
* execution order of assignment to "first lhs, then lhs", it was removed for several reasons)
|
||||
*
|
||||
* A noticeable property for IR generation is "target_type" used to extend/shrink stack.
|
||||
* Example: `var a: (int,int)? = null`. This `null` has inferred_type "null literal", but target_type "nullable tensor",
|
||||
* and when it's assigned, it's "expanded" from 1 stack slot to 3 (int + int + null flag).
|
||||
* Example: `fun analyze(t: (int,int)?)` and a call `analyze((1,2))`. `(1,2)` is `(int,int)` (2 stack slots),
|
||||
* and when passed to target (3 slots, one for null flag), this null flag is implicitly added (zero value).
|
||||
* Example: `nullableInt!`; for `nullableInt` inferred_type is `int?`, and target_type is `int`
|
||||
* (this doesn't lead to stack reorganization, but in case `nullableTensor!` does)
|
||||
* (inferred_type of `nullableInt!` is `int`, and its target_type depends on its usage).
|
||||
* The same mechanism will work for union types in the future.
|
||||
*/
|
||||
|
||||
namespace tolk {
|
||||
|
||||
class LValContext;
|
||||
std::vector<var_idx_t> pre_compile_expr(AnyExprV v, CodeBlob& code, LValContext* lval_ctx = nullptr);
|
||||
std::vector<var_idx_t> pre_compile_expr(AnyExprV v, CodeBlob& code, TypePtr target_type = nullptr, LValContext* lval_ctx = nullptr);
|
||||
std::vector<var_idx_t> pre_compile_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValContext* lval_ctx);
|
||||
void process_any_statement(AnyV v, CodeBlob& code);
|
||||
|
||||
|
@ -101,8 +111,8 @@ class LValContext {
|
|||
// every global variable used as lvalue is registered here
|
||||
// example: `globalInt = 9`, implicit var is created `$tmp = 9`, and `SetGlob "globalInt" $tmp` is done after
|
||||
struct ModifiedGlobal {
|
||||
const GlobalVarData* glob_ref;
|
||||
std::vector<var_idx_t> lval_ir_idx; // typically 1, generally calc_width_on_stack() of global var (tensors)
|
||||
GlobalVarPtr glob_ref;
|
||||
std::vector<var_idx_t> lval_ir_idx; // typically 1, generally get_width_on_stack() of global var (tensors)
|
||||
|
||||
// for 1-slot globals int/cell/slice, assigning to them is just SETGLOB
|
||||
// same for tensors, if they are fully rewritten in an expression: `gTensor = (5,6)`
|
||||
|
@ -139,13 +149,13 @@ class LValContext {
|
|||
void apply(CodeBlob& code, SrcLocation loc) const {
|
||||
LValContext local_lval;
|
||||
local_lval.enter_rval_inside_lval();
|
||||
std::vector<var_idx_t> obj_ir_idx = pre_compile_expr(tensor_obj, code, &local_lval);
|
||||
std::vector<var_idx_t> obj_ir_idx = pre_compile_expr(tensor_obj, code, nullptr, &local_lval);
|
||||
const TypeDataTensor* t_tensor = tensor_obj->inferred_type->try_as<TypeDataTensor>();
|
||||
tolk_assert(t_tensor);
|
||||
int stack_width = t_tensor->items[index_at]->calc_width_on_stack();
|
||||
int stack_width = t_tensor->items[index_at]->get_width_on_stack();
|
||||
int stack_offset = 0;
|
||||
for (int i = 0; i < index_at; ++i) {
|
||||
stack_offset += t_tensor->items[i]->calc_width_on_stack();
|
||||
stack_offset += t_tensor->items[i]->get_width_on_stack();
|
||||
}
|
||||
std::vector<var_idx_t> field_ir_idx = {obj_ir_idx.begin() + stack_offset, obj_ir_idx.begin() + stack_offset + stack_width};
|
||||
tolk_assert(field_ir_idx.size() == lval_ir_idx.size());
|
||||
|
@ -167,12 +177,12 @@ class LValContext {
|
|||
void apply(CodeBlob& code, SrcLocation loc) const {
|
||||
LValContext local_lval;
|
||||
local_lval.enter_rval_inside_lval();
|
||||
std::vector<var_idx_t> tuple_ir_idx = pre_compile_expr(tuple_obj, code, &local_lval);
|
||||
std::vector<var_idx_t> tuple_ir_idx = pre_compile_expr(tuple_obj, code, nullptr, &local_lval);
|
||||
std::vector<var_idx_t> index_ir_idx = code.create_tmp_var(TypeDataInt::create(), loc, "(tuple-idx)");
|
||||
code.emplace_back(loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at));
|
||||
|
||||
vars_modification_watcher.trigger_callbacks(tuple_ir_idx, loc);
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("tupleSetAt")->as<FunctionData>();
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("tupleSetAt")->try_as<FunctionPtr>();
|
||||
code.emplace_back(loc, Op::_Call, std::vector{tuple_ir_idx}, std::vector{tuple_ir_idx[0], lval_ir_idx[0], index_ir_idx[0]}, builtin_sym);
|
||||
local_lval.after_let(std::move(tuple_ir_idx), code, loc);
|
||||
}
|
||||
|
@ -195,7 +205,7 @@ public:
|
|||
void exit_rval_inside_lval() { level_rval_inside_lval--; }
|
||||
bool is_rval_inside_lval() const { return level_rval_inside_lval > 0; }
|
||||
|
||||
void capture_global_modification(const GlobalVarData* glob_ref, std::vector<var_idx_t> lval_ir_idx) {
|
||||
void capture_global_modification(GlobalVarPtr glob_ref, std::vector<var_idx_t> lval_ir_idx) {
|
||||
modifications.emplace_back(ModifiedGlobal{glob_ref, std::move(lval_ir_idx)});
|
||||
}
|
||||
|
||||
|
@ -245,29 +255,39 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
// given `{some_expr}!`, return some_expr
|
||||
static AnyExprV unwrap_not_null_operator(AnyExprV v) {
|
||||
while (auto v_notnull = v->try_as<ast_not_null_operator>()) {
|
||||
v = v_notnull->get_expr();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// given `{some_expr}.{i}`, check it for pattern `some_var.0` / `some_var.0.1` / etc.
|
||||
// return some_var if satisfies (it may be a local or a global var, a tensor or a tuple)
|
||||
// return nullptr otherwise: `f().0` / `(v = rhs).0` / `some_var.method().0` / etc.
|
||||
static V<ast_reference> calc_sink_leftmost_obj(V<ast_dot_access> v) {
|
||||
AnyExprV leftmost_obj = v->get_obj();
|
||||
AnyExprV leftmost_obj = unwrap_not_null_operator(v->get_obj());
|
||||
while (auto v_dot = leftmost_obj->try_as<ast_dot_access>()) {
|
||||
if (!v_dot->is_target_indexed_access()) {
|
||||
break;
|
||||
}
|
||||
leftmost_obj = v_dot->get_obj();
|
||||
leftmost_obj = unwrap_not_null_operator(v_dot->get_obj());
|
||||
}
|
||||
return leftmost_obj->type == ast_reference ? leftmost_obj->as<ast_reference>() : nullptr;
|
||||
}
|
||||
|
||||
|
||||
static std::vector<std::vector<var_idx_t>> pre_compile_tensor_inner(CodeBlob& code, const std::vector<AnyExprV>& args,
|
||||
LValContext* lval_ctx) {
|
||||
const TypeDataTensor* tensor_target_type, LValContext* lval_ctx) {
|
||||
const int n = static_cast<int>(args.size());
|
||||
if (n == 0) { // just `()`
|
||||
return {};
|
||||
}
|
||||
tolk_assert(!tensor_target_type || tensor_target_type->size() == n);
|
||||
if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments
|
||||
return {pre_compile_expr(args[0], code, lval_ctx)};
|
||||
TypePtr child_target_type = tensor_target_type ? tensor_target_type->items[0] : nullptr;
|
||||
return {pre_compile_expr(args[0], code, child_target_type, lval_ctx)};
|
||||
}
|
||||
|
||||
// the purpose is to handle such cases: `return (x, x += y, x)`
|
||||
|
@ -321,7 +341,8 @@ static std::vector<std::vector<var_idx_t>> pre_compile_tensor_inner(CodeBlob& co
|
|||
|
||||
WatchingVarList watched_vars(n);
|
||||
for (int arg_idx = 0; arg_idx < n; ++arg_idx) {
|
||||
std::vector<var_idx_t> vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, lval_ctx);
|
||||
TypePtr child_target_type = tensor_target_type ? tensor_target_type->items[arg_idx] : nullptr;
|
||||
std::vector<var_idx_t> vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, child_target_type, lval_ctx);
|
||||
watched_vars.add_and_watch_modifications(std::move(vars_of_ith_arg), code);
|
||||
}
|
||||
return watched_vars.clear_and_stop_watching();
|
||||
|
@ -329,7 +350,13 @@ static std::vector<std::vector<var_idx_t>> pre_compile_tensor_inner(CodeBlob& co
|
|||
|
||||
static std::vector<var_idx_t> pre_compile_tensor(CodeBlob& code, const std::vector<AnyExprV>& args,
|
||||
LValContext* lval_ctx = nullptr) {
|
||||
std::vector<std::vector<var_idx_t>> res_lists = pre_compile_tensor_inner(code, args, lval_ctx);
|
||||
std::vector<TypePtr> types_list;
|
||||
types_list.reserve(args.size());
|
||||
for (AnyExprV item : args) {
|
||||
types_list.push_back(item->inferred_type);
|
||||
}
|
||||
const TypeDataTensor* tensor_target_type = TypeDataTensor::create(std::move(types_list))->try_as<TypeDataTensor>();
|
||||
std::vector<std::vector<var_idx_t>> res_lists = pre_compile_tensor_inner(code, args, tensor_target_type, lval_ctx);
|
||||
std::vector<var_idx_t> res;
|
||||
for (const std::vector<var_idx_t>& list : res_lists) {
|
||||
res.insert(res.end(), list.cbegin(), list.cend());
|
||||
|
@ -340,6 +367,7 @@ static std::vector<var_idx_t> pre_compile_tensor(CodeBlob& code, const std::vect
|
|||
static std::vector<var_idx_t> pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyExprV rhs, SrcLocation loc) {
|
||||
// [lhs] = [rhs]; since type checking is ok, it's the same as "lhs = rhs"
|
||||
if (lhs->type == ast_typed_tuple && rhs->type == ast_typed_tuple) {
|
||||
// note: there are no type transitions (adding nullability flag, etc.), since only 1-slot elements allowed in tuples
|
||||
LValContext local_lval;
|
||||
std::vector<var_idx_t> left = pre_compile_tensor(code, lhs->as<ast_typed_tuple>()->get_items(), &local_lval);
|
||||
vars_modification_watcher.trigger_callbacks(left, loc);
|
||||
|
@ -355,7 +383,7 @@ static std::vector<var_idx_t> pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE
|
|||
LValContext local_lval;
|
||||
std::vector<var_idx_t> left = pre_compile_tensor(code, lhs->as<ast_typed_tuple>()->get_items(), &local_lval);
|
||||
vars_modification_watcher.trigger_callbacks(left, loc);
|
||||
std::vector<var_idx_t> right = pre_compile_expr(rhs, code);
|
||||
std::vector<var_idx_t> right = pre_compile_expr(rhs, code, nullptr);
|
||||
const TypeDataTypedTuple* inferred_tuple = rhs->inferred_type->try_as<TypeDataTypedTuple>();
|
||||
std::vector<TypePtr> types_list = inferred_tuple->items;
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(TypeDataTensor::create(std::move(types_list)), rhs->loc, "(unpack-tuple)");
|
||||
|
@ -365,25 +393,25 @@ static std::vector<var_idx_t> pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE
|
|||
return right;
|
||||
}
|
||||
// small optimization: `var x = rhs` or `local_var = rhs` (90% cases), LValContext not needed actually
|
||||
if (lhs->type == ast_local_var_lhs || (lhs->type == ast_reference && lhs->as<ast_reference>()->sym->try_as<LocalVarData>())) {
|
||||
std::vector<var_idx_t> left = pre_compile_expr(lhs, code); // effectively, local_var->ir_idx
|
||||
if (lhs->type == ast_local_var_lhs || (lhs->type == ast_reference && lhs->as<ast_reference>()->sym->try_as<LocalVarPtr>())) {
|
||||
std::vector<var_idx_t> left = pre_compile_expr(lhs, code, nullptr); // effectively, local_var->ir_idx
|
||||
vars_modification_watcher.trigger_callbacks(left, loc);
|
||||
std::vector<var_idx_t> right = pre_compile_expr(rhs, code);
|
||||
std::vector<var_idx_t> right = pre_compile_expr(rhs, code, lhs->inferred_type);
|
||||
code.emplace_back(loc, Op::_Let, std::move(left), right);
|
||||
return right;
|
||||
}
|
||||
// lhs = rhs
|
||||
LValContext local_lval;
|
||||
std::vector<var_idx_t> left = pre_compile_expr(lhs, code, &local_lval);
|
||||
std::vector<var_idx_t> left = pre_compile_expr(lhs, code, nullptr, &local_lval);
|
||||
vars_modification_watcher.trigger_callbacks(left, loc);
|
||||
std::vector<var_idx_t> right = pre_compile_expr(rhs, code);
|
||||
std::vector<var_idx_t> right = pre_compile_expr(rhs, code, lhs->inferred_type);
|
||||
code.emplace_back(loc, Op::_Let, left, right);
|
||||
local_lval.after_let(std::move(left), code, loc);
|
||||
return right;
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> gen_op_call(CodeBlob& code, TypePtr ret_type, SrcLocation loc,
|
||||
std::vector<var_idx_t>&& args_vars, const FunctionData* fun_ref, const char* debug_desc) {
|
||||
std::vector<var_idx_t>&& args_vars, FunctionPtr fun_ref, const char* debug_desc) {
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(ret_type, loc, debug_desc);
|
||||
Op& op = code.emplace_back(loc, Op::_Call, rvect, std::move(args_vars), fun_ref);
|
||||
if (!fun_ref->is_marked_as_pure()) {
|
||||
|
@ -392,9 +420,161 @@ static std::vector<var_idx_t> gen_op_call(CodeBlob& code, TypePtr ret_type, SrcL
|
|||
return rvect;
|
||||
}
|
||||
|
||||
// "Transition to target (runtime) type" is the following process.
|
||||
// Imagine `fun analyze(t: (int,int)?)` and a call `analyze((1,2))`.
|
||||
// `(1,2)` (inferred_type) is 2 stack slots, but `t` (target_type) is 3 (one for null-flag).
|
||||
// So, this null flag should be implicitly added (non-zero, since a variable is not null).
|
||||
// Another example: `var t: (int, int)? = null`.
|
||||
// `null` (inferred_type) is 1 stack slots, but target_type is 3, we should add 2 nulls.
|
||||
// Another example: `var t1 = (1, null); var t2: (int, (int,int)?) = t1;`.
|
||||
// Then t1's rvect is 2 vars (1 and null), but t1's `null` should be converted to 3 stack slots (resulting in 4 total).
|
||||
// The same mechanism will work for union types in the future.
|
||||
// Here rvect is a list of IR vars for inferred_type, probably patched due to target_type.
|
||||
GNU_ATTRIBUTE_NOINLINE
|
||||
static std::vector<var_idx_t> transition_expr_to_runtime_type_impl(std::vector<var_idx_t>&& rvect, CodeBlob& code, TypePtr original_type, TypePtr target_type, SrcLocation loc) {
|
||||
// pass `T` to `T`
|
||||
// could occur for passing tensor `(..., T, ...)` to `(..., T, ...)` while traversing tensor's components
|
||||
if (target_type == original_type) {
|
||||
return rvect;
|
||||
}
|
||||
|
||||
int target_w = target_type->get_width_on_stack();
|
||||
const TypeDataNullable* t_nullable = target_type->try_as<TypeDataNullable>();
|
||||
const TypeDataNullable* o_nullable = original_type->try_as<TypeDataNullable>();
|
||||
|
||||
// pass `null` to `T?`
|
||||
// for primitives like `int?`, no changes in rvect, null occupies the same TVM slot
|
||||
// for tensors like `(int,int)?`, `null` is represented as N nulls + 1 null flag, insert N nulls
|
||||
if (t_nullable && original_type == TypeDataNullLiteral::create()) {
|
||||
tolk_assert(rvect.size() == 1);
|
||||
if (target_w == 1 && !t_nullable->is_primitive_nullable()) { // `null` to `()?`
|
||||
rvect = code.create_tmp_var(TypeDataInt::create(), loc, "(NNFlag)");
|
||||
code.emplace_back(loc, Op::_IntConst, rvect, td::make_refint(0));
|
||||
}
|
||||
if (target_w > 1) {
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as<FunctionPtr>();
|
||||
rvect.reserve(target_w + 1);
|
||||
for (int i = 1; i < target_w - 1; ++i) {
|
||||
std::vector<var_idx_t> ith_null = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null-literal)");
|
||||
code.emplace_back(loc, Op::_Call, ith_null, std::vector<var_idx_t>{}, builtin_sym);
|
||||
rvect.push_back(ith_null[0]);
|
||||
}
|
||||
std::vector<var_idx_t> null_flag_ir = code.create_tmp_var(TypeDataInt::create(), loc, "(NNFlag)");
|
||||
var_idx_t null_flag_ir_idx = null_flag_ir[0];
|
||||
code.emplace_back(loc, Op::_IntConst, std::move(null_flag_ir), td::make_refint(0));
|
||||
rvect.push_back(null_flag_ir_idx);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
// pass `T` to `T?`
|
||||
// for primitives like `int?`, no changes in rvect: `int` and `int?` occupy the same TVM slot (null is represented as NULL TVM value)
|
||||
// for passing `(int, int)` to `(int, int)?` / `(int, null)` to `(int, (int,int)?)?`, add a null flag equals to 0
|
||||
if (t_nullable && !o_nullable) {
|
||||
if (!t_nullable->is_primitive_nullable()) {
|
||||
rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, original_type, t_nullable->inner, loc);
|
||||
tolk_assert(target_w == static_cast<int>(rvect.size() + 1));
|
||||
std::vector<var_idx_t> null_flag_ir = code.create_tmp_var(TypeDataInt::create(), loc, "(NNFlag)");
|
||||
var_idx_t null_flag_ir_idx = null_flag_ir[0];
|
||||
code.emplace_back(loc, Op::_IntConst, std::move(null_flag_ir), td::make_refint(-1));
|
||||
rvect.push_back(null_flag_ir_idx);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
// pass `T1?` to `T2?`
|
||||
// for example, `int8?` to `int16?`
|
||||
// transition inner types, leaving nullable flag unchanged for tensors
|
||||
if (t_nullable && o_nullable) {
|
||||
if (target_w > 1) {
|
||||
var_idx_t null_flag_ir_idx = rvect.back();
|
||||
rvect.pop_back();
|
||||
rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, o_nullable->inner, t_nullable->inner, loc);
|
||||
rvect.push_back(null_flag_ir_idx);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
// pass `T?` to `null`
|
||||
if (target_type == TypeDataNullLiteral::create() && original_type->can_rhs_be_assigned(target_type)) {
|
||||
tolk_assert(o_nullable || original_type == TypeDataUnknown::create());
|
||||
if (o_nullable && !o_nullable->is_primitive_nullable()) {
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as<FunctionPtr>();
|
||||
rvect = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null-literal)");
|
||||
code.emplace_back(loc, Op::_Call, rvect, std::vector<var_idx_t>{}, builtin_sym);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
// pass `T?` to `T`
|
||||
// it may occur due to operator `!` or smart cast
|
||||
// for primitives like `int?`, no changes in rvect
|
||||
// for passing `(int, int)?` to `(int, int)`, drop the null flag from the tail
|
||||
if (!t_nullable && o_nullable) {
|
||||
if (!o_nullable->is_primitive_nullable()) {
|
||||
rvect.pop_back();
|
||||
rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, original_type->try_as<TypeDataNullable>()->inner, target_type, loc);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
// pass `bool` to `int`
|
||||
// in code, it's done via `as` operator, like `boolVar as int`
|
||||
// no changes in rvect, boolVar is guaranteed to be -1 or 0 at TVM level
|
||||
if (target_type == TypeDataInt::create() && original_type == TypeDataBool::create()) {
|
||||
return rvect;
|
||||
}
|
||||
// pass something to `unknown`
|
||||
// probably, it comes from `_ = rhs`, type of `_` is unknown, it's target_type of rhs
|
||||
// no changes in rvect
|
||||
if (target_type == TypeDataUnknown::create()) {
|
||||
return rvect;
|
||||
}
|
||||
// pass `unknown` to something
|
||||
// probably, it comes from `arg` in exception, it's inferred as `unknown` and could be cast to any value
|
||||
if (original_type == TypeDataUnknown::create()) {
|
||||
tolk_assert(rvect.size() == 1);
|
||||
return rvect;
|
||||
}
|
||||
// pass tensor to tensor, e.g. `(1, null)` to `(int, slice?)` / `(1, null)` to `(int, (int,int)?)`
|
||||
// every element of rhs tensor should be transitioned
|
||||
if (target_type->try_as<TypeDataTensor>() && original_type->try_as<TypeDataTensor>()) {
|
||||
const TypeDataTensor* target_tensor = target_type->try_as<TypeDataTensor>();
|
||||
const TypeDataTensor* inferred_tensor = original_type->try_as<TypeDataTensor>();
|
||||
tolk_assert(target_tensor->size() == inferred_tensor->size());
|
||||
tolk_assert(inferred_tensor->get_width_on_stack() == static_cast<int>(rvect.size()));
|
||||
std::vector<var_idx_t> result_rvect;
|
||||
result_rvect.reserve(target_w);
|
||||
int stack_offset = 0;
|
||||
for (int i = 0; i < inferred_tensor->size(); ++i) {
|
||||
int ith_w = inferred_tensor->items[i]->get_width_on_stack();
|
||||
std::vector<var_idx_t> rvect_i{rvect.begin() + stack_offset, rvect.begin() + stack_offset + ith_w};
|
||||
std::vector<var_idx_t> result_i = transition_expr_to_runtime_type_impl(std::move(rvect_i), code, inferred_tensor->items[i], target_tensor->items[i], loc);
|
||||
result_rvect.insert(result_rvect.end(), result_i.begin(), result_i.end());
|
||||
stack_offset += ith_w;
|
||||
}
|
||||
return result_rvect;
|
||||
}
|
||||
// pass tuple to tuple, e.g. `[1, null]` to `[int, int?]` / `[1, null]` to `[int, [int?,int?]?]`
|
||||
// to changes to rvect, since tuples contain only 1-slot elements
|
||||
if (target_type->try_as<TypeDataTypedTuple>() && original_type->try_as<TypeDataTypedTuple>()) {
|
||||
tolk_assert(target_type->get_width_on_stack() == original_type->get_width_on_stack());
|
||||
return rvect;
|
||||
}
|
||||
|
||||
throw Fatal("unhandled transition_expr_to_runtime_type_impl() combination");
|
||||
}
|
||||
|
||||
// invoke the function above only if potentially needed to
|
||||
// (if an expression is targeted to another type)
|
||||
#ifndef TOLK_DEBUG
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE
|
||||
#endif
|
||||
static std::vector<var_idx_t> transition_to_target_type(std::vector<var_idx_t>&& rvect, CodeBlob& code, TypePtr target_type, AnyExprV v) {
|
||||
if (target_type != nullptr && target_type != v->inferred_type) {
|
||||
rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, v->inferred_type, target_type, v->loc);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
|
||||
|
||||
std::vector<var_idx_t> pre_compile_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValContext* lval_ctx) {
|
||||
if (const auto* glob_ref = sym->try_as<GlobalVarData>()) {
|
||||
if (GlobalVarPtr glob_ref = sym->try_as<GlobalVarPtr>()) {
|
||||
// handle `globalVar = rhs` / `mutate globalVar`
|
||||
if (lval_ctx && !lval_ctx->is_rval_inside_lval()) {
|
||||
std::vector<var_idx_t> lval_ir_idx = code.create_tmp_var(glob_ref->declared_type, loc, "(lval-glob)");
|
||||
|
@ -410,7 +590,7 @@ std::vector<var_idx_t> pre_compile_symbol(SrcLocation loc, const Symbol* sym, Co
|
|||
}
|
||||
return local_ir_idx;
|
||||
}
|
||||
if (const auto* const_ref = sym->try_as<GlobalConstData>()) {
|
||||
if (GlobalConstPtr const_ref = sym->try_as<GlobalConstPtr>()) {
|
||||
if (const_ref->is_int_const()) {
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(TypeDataInt::create(), loc, "(glob-const)");
|
||||
code.emplace_back(loc, Op::_IntConst, rvect, const_ref->as_int_const());
|
||||
|
@ -421,44 +601,59 @@ std::vector<var_idx_t> pre_compile_symbol(SrcLocation loc, const Symbol* sym, Co
|
|||
return rvect;
|
||||
}
|
||||
}
|
||||
if (const auto* fun_ref = sym->try_as<FunctionData>()) {
|
||||
if (FunctionPtr fun_ref = sym->try_as<FunctionPtr>()) {
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(fun_ref->inferred_full_type, loc, "(glob-var-fun)");
|
||||
code.emplace_back(loc, Op::_GlobVar, rvect, std::vector<var_idx_t>{}, fun_ref);
|
||||
return rvect;
|
||||
}
|
||||
if (const auto* var_ref = sym->try_as<LocalVarData>()) {
|
||||
if (LocalVarPtr var_ref = sym->try_as<LocalVarPtr>()) {
|
||||
#ifdef TOLK_DEBUG
|
||||
tolk_assert(static_cast<int>(var_ref->ir_idx.size()) == var_ref->declared_type->calc_width_on_stack());
|
||||
tolk_assert(static_cast<int>(var_ref->ir_idx.size()) == var_ref->declared_type->get_width_on_stack());
|
||||
#endif
|
||||
return var_ref->ir_idx;
|
||||
}
|
||||
throw Fatal("pre_compile_symbol");
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_assignment(V<ast_assign> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_reference(V<ast_reference> v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
std::vector<var_idx_t> rvect = pre_compile_symbol(v->loc, v->sym, code, lval_ctx);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_assignment(V<ast_assign> v, CodeBlob& code, TypePtr target_type) {
|
||||
if (auto lhs_decl = v->get_lhs()->try_as<ast_local_vars_declaration>()) {
|
||||
return pre_compile_let(code, lhs_decl->get_expr(), v->get_rhs(), v->loc);
|
||||
std::vector<var_idx_t> rvect = pre_compile_let(code, lhs_decl->get_expr(), v->get_rhs(), v->loc);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
} else {
|
||||
return pre_compile_let(code, v->get_lhs(), v->get_rhs(), v->loc);
|
||||
std::vector<var_idx_t> rvect = pre_compile_let(code, v->get_lhs(), v->get_rhs(), v->loc);
|
||||
// now rvect contains rhs IR vars constructed to fit lhs (for correct assignment, lhs type was target_type for rhs)
|
||||
// but the type of `lhs = rhs` is RHS (see type inferring), so rvect now should fit rhs->inferred_type (= v->inferred_type)
|
||||
// example: `t1 = t2 = null`, we're at `t2 = null`, earlier declared t1: `int?`, t2: `(int,int)?`
|
||||
// currently "null" matches t2 (3 null slots), but type of this assignment is "plain null" (1 slot) assigned later to t1
|
||||
rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, v->get_lhs()->inferred_type, v->inferred_type, v->loc);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_set_assign(V<ast_set_assign> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_set_assign(V<ast_set_assign> v, CodeBlob& code, TypePtr target_type) {
|
||||
// for "a += b", emulate "a = a + b"
|
||||
// seems not beautiful, but it works; probably, this transformation should be done at AST level in advance
|
||||
std::string_view calc_operator = v->operator_name; // "+" for operator +=
|
||||
auto v_apply = createV<ast_binary_operator>(v->loc, calc_operator, static_cast<TokenType>(v->tok - 1), v->get_lhs(), v->get_rhs());
|
||||
v_apply->assign_inferred_type(v->inferred_type);
|
||||
v_apply->assign_fun_ref(v->fun_ref);
|
||||
return pre_compile_let(code, v->get_lhs(), v_apply, v->loc);
|
||||
|
||||
std::vector<var_idx_t> rvect = pre_compile_let(code, v->get_lhs(), v_apply, v->loc);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_binary_operator(V<ast_binary_operator> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_binary_operator(V<ast_binary_operator> v, CodeBlob& code, TypePtr target_type) {
|
||||
TokenType t = v->tok;
|
||||
|
||||
if (v->fun_ref) { // almost all operators, fun_ref was assigned at type inferring
|
||||
std::vector<var_idx_t> args_vars = pre_compile_tensor(code, {v->get_lhs(), v->get_rhs()});
|
||||
return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(binary-op)");
|
||||
std::vector<var_idx_t> rvect = gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(binary-op)");
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
if (t == tok_logical_and || t == tok_logical_or) {
|
||||
// do the following transformations:
|
||||
|
@ -470,43 +665,86 @@ static std::vector<var_idx_t> process_binary_operator(V<ast_binary_operator> v,
|
|||
v_1->mutate()->assign_inferred_type(TypeDataInt::create());
|
||||
auto v_b_ne_0 = createV<ast_binary_operator>(v->loc, "!=", tok_neq, v->get_rhs(), v_0);
|
||||
v_b_ne_0->mutate()->assign_inferred_type(TypeDataInt::create());
|
||||
v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->as<FunctionData>());
|
||||
std::vector<var_idx_t> cond = pre_compile_expr(v->get_lhs(), code);
|
||||
v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->try_as<FunctionPtr>());
|
||||
std::vector<var_idx_t> cond = pre_compile_expr(v->get_lhs(), code, nullptr);
|
||||
tolk_assert(cond.size() == 1);
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)");
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(v->inferred_type, v->loc, "(ternary)");
|
||||
Op& if_op = code.emplace_back(v->loc, Op::_If, cond);
|
||||
code.push_set_cur(if_op.block0);
|
||||
code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code));
|
||||
code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code, nullptr));
|
||||
code.close_pop_cur(v->loc);
|
||||
code.push_set_cur(if_op.block1);
|
||||
code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_0 : v_b_ne_0, code));
|
||||
code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_0 : v_b_ne_0, code, nullptr));
|
||||
code.close_pop_cur(v->loc);
|
||||
return rvect;
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
throw UnexpectedASTNodeType(v, "process_binary_operator");
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_unary_operator(V<ast_unary_operator> v, CodeBlob& code) {
|
||||
std::vector<var_idx_t> args_vars = pre_compile_tensor(code, {v->get_rhs()});
|
||||
return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(unary-op)");
|
||||
static std::vector<var_idx_t> process_unary_operator(V<ast_unary_operator> v, CodeBlob& code, TypePtr target_type) {
|
||||
std::vector<var_idx_t> rhs_vars = pre_compile_expr(v->get_rhs(), code, nullptr);
|
||||
std::vector<var_idx_t> rvect = gen_op_call(code, v->inferred_type, v->loc, std::move(rhs_vars), v->fun_ref, "(unary-op)");
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_ternary_operator(V<ast_ternary_operator> v, CodeBlob& code) {
|
||||
std::vector<var_idx_t> cond = pre_compile_expr(v->get_cond(), code);
|
||||
static std::vector<var_idx_t> process_ternary_operator(V<ast_ternary_operator> v, CodeBlob& code, TypePtr target_type) {
|
||||
std::vector<var_idx_t> cond = pre_compile_expr(v->get_cond(), code, nullptr);
|
||||
tolk_assert(cond.size() == 1);
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)");
|
||||
Op& if_op = code.emplace_back(v->loc, Op::_If, cond);
|
||||
code.push_set_cur(if_op.block0);
|
||||
code.emplace_back(v->get_when_true()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_true(), code));
|
||||
code.emplace_back(v->get_when_true()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_true(), code, v->inferred_type));
|
||||
code.close_pop_cur(v->get_when_true()->loc);
|
||||
code.push_set_cur(if_op.block1);
|
||||
code.emplace_back(v->get_when_false()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_false(), code));
|
||||
code.emplace_back(v->get_when_false()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_false(), code, v->inferred_type));
|
||||
code.close_pop_cur(v->get_when_false()->loc);
|
||||
return rvect;
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_dot_access(V<ast_dot_access> v, CodeBlob& code, LValContext* lval_ctx) {
|
||||
static std::vector<var_idx_t> process_cast_as_operator(V<ast_cast_as_operator> v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
TypePtr child_target_type = v->cast_to_type;
|
||||
std::vector<var_idx_t> rvect = pre_compile_expr(v->get_expr(), code, child_target_type, lval_ctx);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_not_null_operator(V<ast_not_null_operator> v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
TypePtr child_target_type = v->get_expr()->inferred_type;
|
||||
if (const auto* as_nullable = child_target_type->try_as<TypeDataNullable>()) {
|
||||
child_target_type = as_nullable->inner;
|
||||
}
|
||||
std::vector<var_idx_t> rvect = pre_compile_expr(v->get_expr(), code, child_target_type, lval_ctx);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_is_null_check(V<ast_is_null_check> v, CodeBlob& code, TypePtr target_type) {
|
||||
std::vector<var_idx_t> expr_ir_idx = pre_compile_expr(v->get_expr(), code, nullptr);
|
||||
std::vector<var_idx_t> isnull_ir_idx = code.create_tmp_var(TypeDataBool::create(), v->loc, "(is-null)");
|
||||
TypePtr expr_type = v->get_expr()->inferred_type;
|
||||
|
||||
if (const TypeDataNullable* t_nullable = expr_type->try_as<TypeDataNullable>()) {
|
||||
if (!t_nullable->is_primitive_nullable()) {
|
||||
std::vector<var_idx_t> zero_ir_idx = code.create_tmp_var(TypeDataInt::create(), v->loc, "(zero)");
|
||||
code.emplace_back(v->loc, Op::_IntConst, zero_ir_idx, td::make_refint(0));
|
||||
FunctionPtr eq_sym = lookup_global_symbol("_==_")->try_as<FunctionPtr>();
|
||||
code.emplace_back(v->loc, Op::_Call, isnull_ir_idx, std::vector{expr_ir_idx.back(), zero_ir_idx[0]}, eq_sym);
|
||||
} else {
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__isNull")->try_as<FunctionPtr>();
|
||||
code.emplace_back(v->loc, Op::_Call, isnull_ir_idx, expr_ir_idx, builtin_sym);
|
||||
}
|
||||
} else {
|
||||
bool always_null = expr_type == TypeDataNullLiteral::create();
|
||||
code.emplace_back(v->loc, Op::_IntConst, isnull_ir_idx, td::make_refint(always_null ? -1 : 0));
|
||||
}
|
||||
|
||||
if (v->is_negated) {
|
||||
FunctionPtr not_sym = lookup_global_symbol("!b_")->try_as<FunctionPtr>();
|
||||
code.emplace_back(v->loc, Op::_Call, isnull_ir_idx, std::vector{isnull_ir_idx}, not_sym);
|
||||
}
|
||||
return transition_to_target_type(std::move(isnull_ir_idx), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_dot_access(V<ast_dot_access> v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
// it's NOT a method call `t.tupleSize()` (since such cases are handled by process_function_call)
|
||||
// it's `t.0`, `getUser().id`, and `t.tupleSize` (as a reference, not as a call)
|
||||
if (!v->is_target_fun_ref()) {
|
||||
|
@ -516,20 +754,21 @@ static std::vector<var_idx_t> process_dot_access(V<ast_dot_access> v, CodeBlob&
|
|||
if (const auto* t_tensor = obj_type->try_as<TypeDataTensor>()) {
|
||||
// handle `tensorVar.0 = rhs` if tensors is a global, special case, then the global will be read on demand
|
||||
if (lval_ctx && !lval_ctx->is_rval_inside_lval()) {
|
||||
if (auto sink = calc_sink_leftmost_obj(v); sink && sink->sym->try_as<GlobalVarData>()) {
|
||||
if (auto sink = calc_sink_leftmost_obj(v); sink && sink->sym->try_as<GlobalVarPtr>()) {
|
||||
std::vector<var_idx_t> lval_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(lval-global-tensor)");
|
||||
lval_ctx->capture_field_of_global_modification(v->get_obj(), index_at, lval_ir_idx);
|
||||
return lval_ir_idx;
|
||||
}
|
||||
}
|
||||
// since a tensor of N elems are N vars on a stack actually, calculate offset
|
||||
std::vector<var_idx_t> lhs_vars = pre_compile_expr(v->get_obj(), code, lval_ctx);
|
||||
int stack_width = t_tensor->items[index_at]->calc_width_on_stack();
|
||||
std::vector<var_idx_t> lhs_vars = pre_compile_expr(v->get_obj(), code, nullptr, lval_ctx);
|
||||
int stack_width = t_tensor->items[index_at]->get_width_on_stack();
|
||||
int stack_offset = 0;
|
||||
for (int i = 0; i < index_at; ++i) {
|
||||
stack_offset += t_tensor->items[i]->calc_width_on_stack();
|
||||
stack_offset += t_tensor->items[i]->get_width_on_stack();
|
||||
}
|
||||
return {lhs_vars.begin() + stack_offset, lhs_vars.begin() + stack_offset + stack_width};
|
||||
std::vector<var_idx_t> rvect{lhs_vars.begin() + stack_offset, lhs_vars.begin() + stack_offset + stack_width};
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
// `tupleVar.0`
|
||||
if (obj_type->try_as<TypeDataTypedTuple>() || obj_type->try_as<TypeDataTuple>()) {
|
||||
|
@ -545,40 +784,52 @@ static std::vector<var_idx_t> process_dot_access(V<ast_dot_access> v, CodeBlob&
|
|||
code.emplace_back(v->loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at));
|
||||
std::vector<var_idx_t> field_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(tuple-field)");
|
||||
tolk_assert(tuple_ir_idx.size() == 1 && field_ir_idx.size() == 1); // tuples contain only 1-slot values
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("tupleAt")->as<FunctionData>();
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("tupleAt")->try_as<FunctionPtr>();
|
||||
code.emplace_back(v->loc, Op::_Call, field_ir_idx, std::vector{tuple_ir_idx[0], index_ir_idx[0]}, builtin_sym);
|
||||
if (lval_ctx && calc_sink_leftmost_obj(v)) { // `tupleVar.0.1 = rhs`, then `tupleVar.0` is rval inside lval
|
||||
lval_ctx->capture_tuple_index_modification(v->get_obj(), index_at, field_ir_idx);
|
||||
}
|
||||
return field_ir_idx;
|
||||
// like tensor index, `tupleVar.1` also might be smart cast, for example we're in `if (tupleVar.1 != null)`
|
||||
// but since tuple's elements are only 1-slot width (no tensors and unions), no stack transformations required
|
||||
return transition_to_target_type(std::move(field_ir_idx), code, target_type, v);
|
||||
}
|
||||
tolk_assert(false);
|
||||
}
|
||||
|
||||
// okay, v->target refs a function, like `obj.method`, filled at type inferring
|
||||
// (currently, nothing except a global function can be referenced, no object-scope methods exist)
|
||||
const FunctionData* fun_ref = std::get<const FunctionData*>(v->target);
|
||||
FunctionPtr fun_ref = std::get<FunctionPtr>(v->target);
|
||||
tolk_assert(fun_ref);
|
||||
return pre_compile_symbol(v->loc, fun_ref, code, lval_ctx);
|
||||
std::vector<var_idx_t> rvect = pre_compile_symbol(v->loc, fun_ref, code, lval_ctx);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, CodeBlob& code, TypePtr target_type) {
|
||||
// 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) {
|
||||
// it's `local_var(args)`, treat args like a tensor:
|
||||
// 1) when variables are modified like `local_var(x, x += 2, x)`, regular mechanism of watching automatically works
|
||||
// 2) when `null` is passed to `(int, int)?`, or any other type transitions, it automatically works
|
||||
std::vector<AnyExprV> args;
|
||||
args.reserve(v->get_num_args());
|
||||
for (int i = 0; i < v->get_num_args(); ++i) {
|
||||
args.push_back(v->get_arg(i)->get_expr());
|
||||
}
|
||||
std::vector<var_idx_t> args_vars = pre_compile_tensor(code, args);
|
||||
std::vector<var_idx_t> tfunc = pre_compile_expr(v->get_callee(), code);
|
||||
std::vector<TypePtr> params_types = v->get_callee()->inferred_type->try_as<TypeDataFunCallable>()->params_types;
|
||||
const TypeDataTensor* tensor_tt = TypeDataTensor::create(std::move(params_types))->try_as<TypeDataTensor>();
|
||||
std::vector<std::vector<var_idx_t>> vars_per_arg = pre_compile_tensor_inner(code, args, tensor_tt, nullptr);
|
||||
std::vector<var_idx_t> args_vars;
|
||||
for (const std::vector<var_idx_t>& list : vars_per_arg) {
|
||||
args_vars.insert(args_vars.end(), list.cbegin(), list.cend());
|
||||
}
|
||||
std::vector<var_idx_t> tfunc = pre_compile_expr(v->get_callee(), code, nullptr);
|
||||
tolk_assert(tfunc.size() == 1);
|
||||
args_vars.push_back(tfunc[0]);
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(v->inferred_type, v->loc, "(call-ind)");
|
||||
Op& op = code.emplace_back(v->loc, Op::_CallInd, rvect, std::move(args_vars));
|
||||
op.set_impure_flag();
|
||||
return rvect;
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
int delta_self = v->is_dot_call();
|
||||
|
@ -595,7 +846,11 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
|
|||
for (int i = 0; i < v->get_num_args(); ++i) {
|
||||
args.push_back(v->get_arg(i)->get_expr());
|
||||
}
|
||||
std::vector<std::vector<var_idx_t>> vars_per_arg = pre_compile_tensor_inner(code, args, nullptr);
|
||||
// the purpose of tensor_tt ("tensor target type") is to transition `null` to `(int, int)?` and so on
|
||||
// the purpose of calling `pre_compile_tensor_inner` is to have 0-th IR vars to handle return self
|
||||
std::vector<TypePtr> params_types = fun_ref->inferred_full_type->try_as<TypeDataFunCallable>()->params_types;
|
||||
const TypeDataTensor* tensor_tt = TypeDataTensor::create(std::move(params_types))->try_as<TypeDataTensor>();
|
||||
std::vector<std::vector<var_idx_t>> vars_per_arg = pre_compile_tensor_inner(code, args, tensor_tt, nullptr);
|
||||
|
||||
TypePtr op_call_type = v->inferred_type;
|
||||
TypePtr real_ret_type = v->inferred_type;
|
||||
|
@ -609,7 +864,7 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
|
|||
std::vector<TypePtr> types_list;
|
||||
for (int i = 0; i < delta_self + v->get_num_args(); ++i) {
|
||||
if (fun_ref->parameters[i].is_mutate_parameter()) {
|
||||
types_list.push_back(args[i]->inferred_type);
|
||||
types_list.push_back(fun_ref->parameters[i].declared_type);
|
||||
}
|
||||
}
|
||||
types_list.push_back(real_ret_type);
|
||||
|
@ -630,7 +885,7 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
|
|||
AnyExprV arg_i = obj_leftmost && i == 0 ? obj_leftmost : args[i];
|
||||
tolk_assert(arg_i->is_lvalue || i == 0);
|
||||
if (arg_i->is_lvalue) {
|
||||
std::vector<var_idx_t> ith_var_idx = pre_compile_expr(arg_i, code, &local_lval);
|
||||
std::vector<var_idx_t> ith_var_idx = pre_compile_expr(arg_i, code, nullptr, &local_lval);
|
||||
left.insert(left.end(), ith_var_idx.begin(), ith_var_idx.end());
|
||||
} else {
|
||||
left.insert(left.end(), vars_per_arg[0].begin(), vars_per_arg[0].end());
|
||||
|
@ -647,36 +902,39 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
|
|||
|
||||
if (obj_leftmost && fun_ref->does_return_self()) {
|
||||
if (obj_leftmost->is_lvalue) { // to handle if obj is global var, potentially re-assigned inside a chain
|
||||
rvect_apply = pre_compile_expr(obj_leftmost, code);
|
||||
rvect_apply = pre_compile_expr(obj_leftmost, code, nullptr);
|
||||
} else { // temporary object, not lvalue, pre_compile_expr
|
||||
rvect_apply = vars_per_arg[0];
|
||||
}
|
||||
}
|
||||
|
||||
return rvect_apply;
|
||||
return transition_to_target_type(std::move(rvect_apply), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_tensor(V<ast_tensor> v, CodeBlob& code, LValContext* lval_ctx) {
|
||||
return pre_compile_tensor(code, v->get_items(), lval_ctx);
|
||||
static std::vector<var_idx_t> process_tensor(V<ast_tensor> v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
// tensor is compiled "as is", for example `(1, null)` occupies 2 slots
|
||||
// and if assigned/passed to something other, like `(int, (int,int)?)`, a whole tensor is transitioned, it works
|
||||
std::vector<var_idx_t> rvect = pre_compile_tensor(code, v->get_items(), lval_ctx);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_typed_tuple(V<ast_typed_tuple> v, CodeBlob& code, LValContext* lval_ctx) {
|
||||
static std::vector<var_idx_t> process_typed_tuple(V<ast_typed_tuple> v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
if (lval_ctx) { // todo some time, make "var (a, [b,c]) = (1, [2,3])" work
|
||||
v->error("[...] can not be used as lvalue here");
|
||||
}
|
||||
std::vector<var_idx_t> left = code.create_tmp_var(v->inferred_type, v->loc, "(pack-tuple)");
|
||||
std::vector<var_idx_t> right = pre_compile_tensor(code, v->get_items(), lval_ctx);
|
||||
code.emplace_back(v->loc, Op::_Tuple, left, std::move(right));
|
||||
return left;
|
||||
return transition_to_target_type(std::move(left), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_int_const(V<ast_int_const> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_int_const(V<ast_int_const> v, CodeBlob& code, TypePtr target_type) {
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(v->inferred_type, v->loc, "(int-const)");
|
||||
code.emplace_back(v->loc, Op::_IntConst, rvect, v->intval);
|
||||
return rvect;
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_string_const(V<ast_string_const> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_string_const(V<ast_string_const> v, CodeBlob& code, TypePtr target_type) {
|
||||
ConstantValue value = eval_const_init_value(v);
|
||||
std::vector<var_idx_t> rvect = code.create_tmp_var(v->inferred_type, v->loc, "(str-const)");
|
||||
if (value.is_int()) {
|
||||
|
@ -684,27 +942,31 @@ static std::vector<var_idx_t> process_string_const(V<ast_string_const> v, CodeBl
|
|||
} else {
|
||||
code.emplace_back(v->loc, Op::_SliceConst, rvect, value.as_slice());
|
||||
}
|
||||
return rvect;
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_bool_const(V<ast_bool_const> v, CodeBlob& code) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->as<FunctionData>();
|
||||
return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(bool-const)");
|
||||
static std::vector<var_idx_t> process_bool_const(V<ast_bool_const> v, CodeBlob& code, TypePtr target_type) {
|
||||
FunctionPtr builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->try_as<FunctionPtr>();
|
||||
std::vector<var_idx_t> rvect = gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(bool-const)");
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_null_keyword(V<ast_null_keyword> v, CodeBlob& code) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("__null")->as<FunctionData>();
|
||||
return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(null-literal)");
|
||||
static std::vector<var_idx_t> process_null_keyword(V<ast_null_keyword> v, CodeBlob& code, TypePtr target_type) {
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as<FunctionPtr>();
|
||||
std::vector<var_idx_t> rvect = gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(null-literal)");
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_local_var(V<ast_local_var_lhs> v, CodeBlob& code) {
|
||||
static std::vector<var_idx_t> process_local_var(V<ast_local_var_lhs> v, CodeBlob& code, TypePtr target_type) {
|
||||
if (v->marked_as_redef) {
|
||||
return pre_compile_symbol(v->loc, v->var_ref, code, nullptr);
|
||||
std::vector<var_idx_t> rvect = pre_compile_symbol(v->loc, v->var_ref, code, nullptr);
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
tolk_assert(v->var_ref->ir_idx.empty());
|
||||
v->var_ref->mutate()->assign_ir_idx(code.create_var(v->inferred_type, v->loc, v->var_ref->name));
|
||||
return v->var_ref->ir_idx;
|
||||
std::vector<var_idx_t> rvect = v->var_ref->ir_idx;
|
||||
return transition_to_target_type(std::move(rvect), code, target_type, v);
|
||||
}
|
||||
|
||||
static std::vector<var_idx_t> process_local_vars_declaration(V<ast_local_vars_declaration>, CodeBlob&) {
|
||||
|
@ -718,42 +980,46 @@ static std::vector<var_idx_t> process_underscore(V<ast_underscore> v, CodeBlob&
|
|||
return code.create_tmp_var(v->inferred_type, v->loc, "(underscore)");
|
||||
}
|
||||
|
||||
std::vector<var_idx_t> pre_compile_expr(AnyExprV v, CodeBlob& code, LValContext* lval_ctx) {
|
||||
std::vector<var_idx_t> pre_compile_expr(AnyExprV v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) {
|
||||
switch (v->type) {
|
||||
case ast_reference:
|
||||
return pre_compile_symbol(v->loc, v->as<ast_reference>()->sym, code, lval_ctx);
|
||||
return process_reference(v->as<ast_reference>(), code, target_type, lval_ctx);
|
||||
case ast_assign:
|
||||
return process_assignment(v->as<ast_assign>(), code);
|
||||
return process_assignment(v->as<ast_assign>(), code, target_type);
|
||||
case ast_set_assign:
|
||||
return process_set_assign(v->as<ast_set_assign>(), code);
|
||||
return process_set_assign(v->as<ast_set_assign>(), code, target_type);
|
||||
case ast_binary_operator:
|
||||
return process_binary_operator(v->as<ast_binary_operator>(), code);
|
||||
return process_binary_operator(v->as<ast_binary_operator>(), code, target_type);
|
||||
case ast_unary_operator:
|
||||
return process_unary_operator(v->as<ast_unary_operator>(), code);
|
||||
return process_unary_operator(v->as<ast_unary_operator>(), code, target_type);
|
||||
case ast_ternary_operator:
|
||||
return process_ternary_operator(v->as<ast_ternary_operator>(), code);
|
||||
return process_ternary_operator(v->as<ast_ternary_operator>(), code, target_type);
|
||||
case ast_cast_as_operator:
|
||||
return pre_compile_expr(v->as<ast_cast_as_operator>()->get_expr(), code, lval_ctx);
|
||||
return process_cast_as_operator(v->as<ast_cast_as_operator>(), code, target_type, lval_ctx);
|
||||
case ast_not_null_operator:
|
||||
return process_not_null_operator(v->as<ast_not_null_operator>(), code, target_type, lval_ctx);
|
||||
case ast_is_null_check:
|
||||
return process_is_null_check(v->as<ast_is_null_check>(), code, target_type);
|
||||
case ast_dot_access:
|
||||
return process_dot_access(v->as<ast_dot_access>(), code, lval_ctx);
|
||||
return process_dot_access(v->as<ast_dot_access>(), code, target_type, lval_ctx);
|
||||
case ast_function_call:
|
||||
return process_function_call(v->as<ast_function_call>(), code);
|
||||
return process_function_call(v->as<ast_function_call>(), code, target_type);
|
||||
case ast_parenthesized_expression:
|
||||
return pre_compile_expr(v->as<ast_parenthesized_expression>()->get_expr(), code, lval_ctx);
|
||||
return pre_compile_expr(v->as<ast_parenthesized_expression>()->get_expr(), code, target_type, lval_ctx);
|
||||
case ast_tensor:
|
||||
return process_tensor(v->as<ast_tensor>(), code, lval_ctx);
|
||||
return process_tensor(v->as<ast_tensor>(), code, target_type, lval_ctx);
|
||||
case ast_typed_tuple:
|
||||
return process_typed_tuple(v->as<ast_typed_tuple>(), code, lval_ctx);
|
||||
return process_typed_tuple(v->as<ast_typed_tuple>(), code, target_type, lval_ctx);
|
||||
case ast_int_const:
|
||||
return process_int_const(v->as<ast_int_const>(), code);
|
||||
return process_int_const(v->as<ast_int_const>(), code, target_type);
|
||||
case ast_string_const:
|
||||
return process_string_const(v->as<ast_string_const>(), code);
|
||||
return process_string_const(v->as<ast_string_const>(), code, target_type);
|
||||
case ast_bool_const:
|
||||
return process_bool_const(v->as<ast_bool_const>(), code);
|
||||
return process_bool_const(v->as<ast_bool_const>(), code, target_type);
|
||||
case ast_null_keyword:
|
||||
return process_null_keyword(v->as<ast_null_keyword>(), code);
|
||||
return process_null_keyword(v->as<ast_null_keyword>(), code, target_type);
|
||||
case ast_local_var_lhs:
|
||||
return process_local_var(v->as<ast_local_var_lhs>(), code);
|
||||
return process_local_var(v->as<ast_local_var_lhs>(), code, target_type);
|
||||
case ast_local_vars_declaration:
|
||||
return process_local_vars_declaration(v->as<ast_local_vars_declaration>(), code);
|
||||
case ast_underscore:
|
||||
|
@ -784,14 +1050,14 @@ static void process_assert_statement(V<ast_assert_statement> v, CodeBlob& code)
|
|||
args[2]->mutate()->assign_inferred_type(TypeDataInt::create());
|
||||
}
|
||||
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("__throw_if_unless")->as<FunctionData>();
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__throw_if_unless")->try_as<FunctionPtr>();
|
||||
std::vector<var_idx_t> args_vars = pre_compile_tensor(code, args);
|
||||
gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)");
|
||||
}
|
||||
|
||||
static void process_catch_variable(AnyExprV v_catch_var, CodeBlob& code) {
|
||||
if (auto v_ref = v_catch_var->try_as<ast_reference>(); v_ref && v_ref->sym) { // not underscore
|
||||
const LocalVarData* var_ref = v_ref->sym->as<LocalVarData>();
|
||||
LocalVarPtr var_ref = v_ref->sym->try_as<LocalVarPtr>();
|
||||
tolk_assert(var_ref->ir_idx.empty());
|
||||
var_ref->mutate()->assign_ir_idx(code.create_var(v_catch_var->inferred_type, v_catch_var->loc, var_ref->name));
|
||||
}
|
||||
|
@ -816,7 +1082,7 @@ static void process_try_catch_statement(V<ast_try_catch_statement> v, CodeBlob&
|
|||
}
|
||||
|
||||
static void process_repeat_statement(V<ast_repeat_statement> v, CodeBlob& code) {
|
||||
std::vector<var_idx_t> tmp_vars = pre_compile_expr(v->get_cond(), code);
|
||||
std::vector<var_idx_t> tmp_vars = pre_compile_expr(v->get_cond(), code, nullptr);
|
||||
Op& repeat_op = code.emplace_back(v->loc, Op::_Repeat, tmp_vars);
|
||||
code.push_set_cur(repeat_op.block0);
|
||||
process_any_statement(v->get_body(), code);
|
||||
|
@ -824,7 +1090,7 @@ static void process_repeat_statement(V<ast_repeat_statement> v, CodeBlob& code)
|
|||
}
|
||||
|
||||
static void process_if_statement(V<ast_if_statement> v, CodeBlob& code) {
|
||||
std::vector<var_idx_t> tmp_vars = pre_compile_expr(v->get_cond(), code);
|
||||
std::vector<var_idx_t> tmp_vars = pre_compile_expr(v->get_cond(), code, nullptr);
|
||||
Op& if_op = code.emplace_back(v->loc, Op::_If, std::move(tmp_vars));
|
||||
code.push_set_cur(if_op.block0);
|
||||
process_any_statement(v->get_if_body(), code);
|
||||
|
@ -869,19 +1135,21 @@ static void process_do_while_statement(V<ast_do_while_statement> v, CodeBlob& co
|
|||
}
|
||||
until_cond->mutate()->assign_inferred_type(TypeDataInt::create());
|
||||
if (auto v_bin = until_cond->try_as<ast_binary_operator>(); v_bin && !v_bin->fun_ref) {
|
||||
v_bin->mutate()->assign_fun_ref(lookup_global_symbol("_" + static_cast<std::string>(v_bin->operator_name) + "_")->as<FunctionData>());
|
||||
v_bin->mutate()->assign_fun_ref(lookup_global_symbol("_" + static_cast<std::string>(v_bin->operator_name) + "_")->try_as<FunctionPtr>());
|
||||
} else if (auto v_un = until_cond->try_as<ast_unary_operator>(); v_un && !v_un->fun_ref) {
|
||||
v_un->mutate()->assign_fun_ref(lookup_global_symbol(static_cast<std::string>(v_un->operator_name) + "_")->as<FunctionData>());
|
||||
v_un->mutate()->assign_fun_ref(lookup_global_symbol(static_cast<std::string>(v_un->operator_name) + "_")->try_as<FunctionPtr>());
|
||||
}
|
||||
|
||||
until_op.left = pre_compile_expr(until_cond, code);
|
||||
until_op.left = pre_compile_expr(until_cond, code, nullptr);
|
||||
tolk_assert(until_op.left.size() == 1);
|
||||
code.close_pop_cur(v->get_body()->loc_end);
|
||||
}
|
||||
|
||||
static void process_while_statement(V<ast_while_statement> v, CodeBlob& code) {
|
||||
Op& while_op = code.emplace_back(v->loc, Op::_While);
|
||||
code.push_set_cur(while_op.block0);
|
||||
while_op.left = pre_compile_expr(v->get_cond(), code);
|
||||
while_op.left = pre_compile_expr(v->get_cond(), code, nullptr);
|
||||
tolk_assert(while_op.left.size() == 1);
|
||||
code.close_pop_cur(v->get_body()->loc);
|
||||
code.push_set_cur(while_op.block1);
|
||||
process_any_statement(v->get_body(), code);
|
||||
|
@ -890,18 +1158,25 @@ static void process_while_statement(V<ast_while_statement> v, CodeBlob& code) {
|
|||
|
||||
static void process_throw_statement(V<ast_throw_statement> v, CodeBlob& code) {
|
||||
if (v->has_thrown_arg()) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("__throw_arg")->as<FunctionData>();
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__throw_arg")->try_as<FunctionPtr>();
|
||||
std::vector<var_idx_t> args_vars = pre_compile_tensor(code, {v->get_thrown_arg(), v->get_thrown_code()});
|
||||
gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)");
|
||||
} else {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("__throw")->as<FunctionData>();
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("__throw")->try_as<FunctionPtr>();
|
||||
std::vector<var_idx_t> args_vars = pre_compile_tensor(code, {v->get_thrown_code()});
|
||||
gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)");
|
||||
}
|
||||
}
|
||||
|
||||
static void process_return_statement(V<ast_return_statement> v, CodeBlob& code) {
|
||||
std::vector<var_idx_t> return_vars = v->has_return_value() ? pre_compile_expr(v->get_return_value(), code) : std::vector<var_idx_t>{};
|
||||
std::vector<var_idx_t> return_vars;
|
||||
if (v->has_return_value()) {
|
||||
TypePtr child_target_type = code.fun_ref->inferred_return_type;
|
||||
if (code.fun_ref->does_return_self()) {
|
||||
child_target_type = code.fun_ref->parameters[0].declared_type;
|
||||
}
|
||||
return_vars = pre_compile_expr(v->get_return_value(), code, child_target_type);
|
||||
}
|
||||
if (code.fun_ref->does_return_self()) {
|
||||
return_vars = {};
|
||||
}
|
||||
|
@ -953,18 +1228,18 @@ void process_any_statement(AnyV v, CodeBlob& code) {
|
|||
case ast_empty_statement:
|
||||
return;
|
||||
default:
|
||||
pre_compile_expr(reinterpret_cast<AnyExprV>(v), code);
|
||||
pre_compile_expr(reinterpret_cast<AnyExprV>(v), code, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, FunctionBodyCode* code_body) {
|
||||
static void convert_function_body_to_CodeBlob(FunctionPtr fun_ref, FunctionBodyCode* code_body) {
|
||||
auto v_body = fun_ref->ast_root->as<ast_function_declaration>()->get_body()->as<ast_sequence>();
|
||||
CodeBlob* blob = new CodeBlob{fun_ref->name, fun_ref->loc, fun_ref};
|
||||
|
||||
std::vector<var_idx_t> rvect_import;
|
||||
int total_arg_width = 0;
|
||||
for (int i = 0; i < fun_ref->get_num_params(); ++i) {
|
||||
total_arg_width += fun_ref->parameters[i].declared_type->calc_width_on_stack();
|
||||
total_arg_width += fun_ref->parameters[i].declared_type->get_width_on_stack();
|
||||
}
|
||||
rvect_import.reserve(total_arg_width);
|
||||
|
||||
|
@ -990,9 +1265,9 @@ static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, Funct
|
|||
tolk_assert(vars_modification_watcher.empty());
|
||||
}
|
||||
|
||||
static void convert_asm_body_to_AsmOp(const FunctionData* fun_ref, FunctionBodyAsm* asm_body) {
|
||||
static void convert_asm_body_to_AsmOp(FunctionPtr fun_ref, FunctionBodyAsm* asm_body) {
|
||||
int cnt = fun_ref->get_num_params();
|
||||
int width = fun_ref->inferred_return_type->calc_width_on_stack();
|
||||
int width = fun_ref->inferred_return_type->get_width_on_stack();
|
||||
std::vector<AsmOp> asm_ops;
|
||||
for (AnyV v_child : fun_ref->ast_root->as<ast_function_declaration>()->get_body()->as<ast_asm_body>()->get_asm_commands()) {
|
||||
std::string_view ops = v_child->as<ast_string_const>()->str_val; // <op>\n<op>\n...
|
||||
|
@ -1023,15 +1298,15 @@ static void convert_asm_body_to_AsmOp(const FunctionData* fun_ref, FunctionBodyA
|
|||
|
||||
class UpdateArgRetOrderConsideringStackWidth final {
|
||||
public:
|
||||
static bool should_visit_function(const FunctionData* fun_ref) {
|
||||
static bool should_visit_function(FunctionPtr fun_ref) {
|
||||
return !fun_ref->is_generic_function() && (!fun_ref->ret_order.empty() || !fun_ref->arg_order.empty());
|
||||
}
|
||||
|
||||
static void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
|
||||
static void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
|
||||
int total_arg_mutate_width = 0;
|
||||
bool has_arg_width_not_1 = false;
|
||||
for (const LocalVarData& param : fun_ref->parameters) {
|
||||
int arg_width = param.declared_type->calc_width_on_stack();
|
||||
int arg_width = param.declared_type->get_width_on_stack();
|
||||
has_arg_width_not_1 |= arg_width != 1;
|
||||
total_arg_mutate_width += param.is_mutate_parameter() * arg_width;
|
||||
}
|
||||
|
@ -1045,7 +1320,7 @@ public:
|
|||
cum_arg_width.reserve(1 + fun_ref->get_num_params());
|
||||
cum_arg_width.push_back(0);
|
||||
for (const LocalVarData& param : fun_ref->parameters) {
|
||||
cum_arg_width.push_back(total_arg_width += param.declared_type->calc_width_on_stack());
|
||||
cum_arg_width.push_back(total_arg_width += param.declared_type->get_width_on_stack());
|
||||
}
|
||||
std::vector<int> arg_order;
|
||||
for (int i = 0; i < fun_ref->get_num_params(); ++i) {
|
||||
|
@ -1062,7 +1337,7 @@ public:
|
|||
// ret_order is a shuffled range 0...N
|
||||
// validate N: a function should return value and mutated arguments onto a stack
|
||||
if (!fun_ref->ret_order.empty()) {
|
||||
size_t expected_width = fun_ref->inferred_return_type->calc_width_on_stack() + total_arg_mutate_width;
|
||||
size_t expected_width = fun_ref->inferred_return_type->get_width_on_stack() + total_arg_mutate_width;
|
||||
if (expected_width != fun_ref->ret_order.size()) {
|
||||
v_function->get_body()->error("ret_order (after ->) expected to contain " + std::to_string(expected_width) + " numbers");
|
||||
}
|
||||
|
@ -1072,11 +1347,11 @@ public:
|
|||
|
||||
class ConvertASTToLegacyOpVisitor final {
|
||||
public:
|
||||
static bool should_visit_function(const FunctionData* fun_ref) {
|
||||
static bool should_visit_function(FunctionPtr fun_ref) {
|
||||
return !fun_ref->is_generic_function();
|
||||
}
|
||||
|
||||
static void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration>) {
|
||||
static void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration>) {
|
||||
tolk_assert(fun_ref->is_type_inferring_done());
|
||||
if (fun_ref->is_code_function()) {
|
||||
convert_function_body_to_CodeBlob(fun_ref, std::get<FunctionBodyCode*>(fun_ref->body));
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -88,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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -111,11 +111,11 @@ class UnreachableStatementsDetectVisitor final {
|
|||
}
|
||||
|
||||
public:
|
||||
static bool should_visit_function(const FunctionData* fun_ref) {
|
||||
static bool should_visit_function(FunctionPtr fun_ref) {
|
||||
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
|
||||
}
|
||||
|
||||
void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
|
||||
void start_visiting_function(FunctionPtr 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();
|
||||
|
@ -128,7 +128,7 @@ void pipeline_detect_unreachable_statements() {
|
|||
visit_ast_of_all_functions<UnreachableStatementsDetectVisitor>();
|
||||
}
|
||||
|
||||
void pipeline_detect_unreachable_statements(const FunctionData* fun_ref) {
|
||||
void pipeline_detect_unreachable_statements(FunctionPtr fun_ref) {
|
||||
UnreachableStatementsDetectVisitor visitor;
|
||||
if (UnreachableStatementsDetectVisitor::should_visit_function(fun_ref)) {
|
||||
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -63,9 +63,9 @@
|
|||
|
||||
namespace tolk {
|
||||
|
||||
static void infer_and_save_return_type_of_function(const FunctionData* fun_ref);
|
||||
static void infer_and_save_return_type_of_function(FunctionPtr fun_ref);
|
||||
|
||||
static TypePtr get_or_infer_return_type(const FunctionData* fun_ref) {
|
||||
static TypePtr get_or_infer_return_type(FunctionPtr fun_ref) {
|
||||
if (!fun_ref->inferred_return_type) {
|
||||
infer_and_save_return_type_of_function(fun_ref);
|
||||
}
|
||||
|
@ -83,12 +83,7 @@ static std::string to_string(AnyExprV v_with_type) {
|
|||
}
|
||||
|
||||
GNU_ATTRIBUTE_NOINLINE
|
||||
static std::string to_string(const LocalVarData& var_ref) {
|
||||
return "`" + var_ref.declared_type->as_human_readable() + "`";
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_NOINLINE
|
||||
static std::string to_string(const FunctionData* fun_ref) {
|
||||
static std::string to_string(FunctionPtr fun_ref) {
|
||||
return "`" + fun_ref->as_human_readable() + "`";
|
||||
}
|
||||
|
||||
|
@ -96,8 +91,8 @@ static std::string to_string(const FunctionData* fun_ref) {
|
|||
// asm functions generally can't handle it, they expect T to be a TVM primitive
|
||||
// (in FunC, `forall` type just couldn't be unified with non-primitives; in Tolk, generic T is expectedly inferred)
|
||||
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
|
||||
static void fire_error_calling_asm_function_with_non1_stack_width_arg(SrcLocation loc, const FunctionData* fun_ref, const std::vector<TypePtr>& substitutions, int arg_idx) {
|
||||
throw ParseError(loc, "can not call `" + fun_ref->as_human_readable() + "` with " + fun_ref->genericTs->get_nameT(arg_idx) + "=" + substitutions[arg_idx]->as_human_readable() + ", because it occupies " + std::to_string(substitutions[arg_idx]->calc_width_on_stack()) + " stack slots in TVM, not 1");
|
||||
static void fire_error_calling_asm_function_with_non1_stack_width_arg(SrcLocation loc, FunctionPtr fun_ref, const std::vector<TypePtr>& substitutions, int arg_idx) {
|
||||
throw ParseError(loc, "can not call `" + fun_ref->as_human_readable() + "` with " + fun_ref->genericTs->get_nameT(arg_idx) + "=" + substitutions[arg_idx]->as_human_readable() + ", because it occupies " + std::to_string(substitutions[arg_idx]->get_width_on_stack()) + " stack slots in TVM, not 1");
|
||||
}
|
||||
|
||||
// fire an error on `var n = null`
|
||||
|
@ -105,7 +100,7 @@ static void fire_error_calling_asm_function_with_non1_stack_width_arg(SrcLocatio
|
|||
// so, it's better to see an error on assignment, that later, on `n` usage and types mismatch
|
||||
// (most common is situation above, but generally, `var (x,n) = xn` where xn is a tensor with 2-nd always-null, can be)
|
||||
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
|
||||
static void fire_error_assign_always_null_to_variable(SrcLocation loc, const LocalVarData* assigned_var, bool is_assigned_null_literal) {
|
||||
static void fire_error_assign_always_null_to_variable(SrcLocation loc, LocalVarPtr assigned_var, bool is_assigned_null_literal) {
|
||||
std::string var_name = assigned_var->name;
|
||||
throw ParseError(loc, "can not infer type of `" + var_name + "`, it's always null; specify its type with `" + var_name + ": <type>`" + (is_assigned_null_literal ? " or use `null as <type>`" : ""));
|
||||
}
|
||||
|
@ -134,34 +129,26 @@ static void fire_error_cannot_deduce_untyped_tuple_access(SrcLocation loc, int i
|
|||
// fire an error on `untypedTupleVar.0` when inferred as (int,int), or `[int, (int,int)]`, or other non-1 width in a tuple
|
||||
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
|
||||
static void fire_error_tuple_cannot_have_non1_stack_width_elem(SrcLocation loc, TypePtr inferred_type) {
|
||||
throw ParseError(loc, "a tuple can not have " + to_string(inferred_type) + " inside, because it occupies " + std::to_string(inferred_type->calc_width_on_stack()) + " stack slots in TVM, not 1");
|
||||
throw ParseError(loc, "a tuple can not have " + to_string(inferred_type) + " inside, because it occupies " + std::to_string(inferred_type->get_width_on_stack()) + " stack slots in TVM, not 1");
|
||||
}
|
||||
|
||||
// check correctness of called arguments counts and their type matching
|
||||
static void check_function_arguments(const FunctionData* fun_ref, V<ast_argument_list> v, AnyExprV lhs_of_dot_call) {
|
||||
int delta_self = lhs_of_dot_call ? 1 : 0;
|
||||
int n_arguments = v->size() + delta_self;
|
||||
int n_parameters = fun_ref->get_num_params();
|
||||
|
||||
// Tolk doesn't have optional parameters currently, so just compare counts
|
||||
if (!n_parameters && lhs_of_dot_call) {
|
||||
v->error("`" + fun_ref->name + "` has no parameters and can not be called as method");
|
||||
}
|
||||
if (n_parameters < n_arguments) {
|
||||
v->error("too many arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self));
|
||||
}
|
||||
if (n_arguments < n_parameters) {
|
||||
v->error("too few arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self));
|
||||
}
|
||||
|
||||
if (lhs_of_dot_call) {
|
||||
if (!fun_ref->parameters[0].declared_type->can_rhs_be_assigned(lhs_of_dot_call->inferred_type)) {
|
||||
lhs_of_dot_call->error("can not call method for " + to_string(fun_ref->parameters[0]) + " with object of type " + to_string(lhs_of_dot_call));
|
||||
// check type correctness of a passed argument when calling a function/method
|
||||
static void check_function_argument(TypePtr param_type, bool is_mutate_param, AnyExprV ith_arg, bool is_obj_of_dot_call) {
|
||||
// given `f(x: int)` and a call `f(expr)`, check that expr_type is assignable to `int`
|
||||
if (!param_type->can_rhs_be_assigned(ith_arg->inferred_type)) {
|
||||
if (is_obj_of_dot_call) {
|
||||
ith_arg->error("can not call method for " + to_string(param_type) + " with object of type " + to_string(ith_arg));
|
||||
} else {
|
||||
ith_arg->error("can not pass " + to_string(ith_arg) + " to " + to_string(param_type));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < v->size(); ++i) {
|
||||
if (!fun_ref->parameters[i + delta_self].declared_type->can_rhs_be_assigned(v->get_arg(i)->inferred_type)) {
|
||||
v->get_arg(i)->error("can not pass " + to_string(v->get_arg(i)) + " to " + to_string(fun_ref->parameters[i + delta_self]));
|
||||
// given `f(x: mutate int?)` and a call `f(expr)`, check that `int?` is assignable to expr_type
|
||||
// (for instance, can't call such a function with `f(mutate intVal)`, since f can potentially assign null to it)
|
||||
if (is_mutate_param && !ith_arg->inferred_type->can_rhs_be_assigned(param_type)) {
|
||||
if (is_obj_of_dot_call) {
|
||||
ith_arg->error("can not call method for mutate " + to_string(param_type) + " with object of type " + to_string(ith_arg) + ", because mutation is not type compatible");
|
||||
} else {
|
||||
ith_arg->error("can not pass " + to_string(ith_arg) + " to mutate " + to_string(param_type) + ", because mutation is not type compatible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +176,13 @@ class TypeInferringUnifyStrategy {
|
|||
return t2;
|
||||
}
|
||||
|
||||
if (t1 == TypeDataNullLiteral::create()) {
|
||||
return TypeDataNullable::create(t2);
|
||||
}
|
||||
if (t2 == TypeDataNullLiteral::create()) {
|
||||
return TypeDataNullable::create(t1);
|
||||
}
|
||||
|
||||
const auto* tensor1 = t1->try_as<TypeDataTensor>();
|
||||
const auto* tensor2 = t2->try_as<TypeDataTensor>();
|
||||
if (tensor1 && tensor2 && tensor1->size() == tensor2->size()) {
|
||||
|
@ -256,8 +250,8 @@ public:
|
|||
// handle __expect_type(expr, "type") call
|
||||
// this is used in compiler tests
|
||||
GNU_ATTRIBUTE_NOINLINE GNU_ATTRIBUTE_COLD
|
||||
static void handle_possible_compiler_internal_call(const FunctionData* current_function, V<ast_function_call> v) {
|
||||
const FunctionData* fun_ref = v->fun_maybe;
|
||||
static void handle_possible_compiler_internal_call(FunctionPtr current_function, V<ast_function_call> v) {
|
||||
FunctionPtr fun_ref = v->fun_maybe;
|
||||
tolk_assert(fun_ref && fun_ref->is_builtin_function());
|
||||
static_cast<void>(current_function);
|
||||
|
||||
|
@ -279,7 +273,7 @@ static void handle_possible_compiler_internal_call(const FunctionData* current_f
|
|||
* 2) easy to maintain a hint (see comments at the top of the file)
|
||||
*/
|
||||
class InferCheckTypesAndCallsAndFieldsVisitor final {
|
||||
const FunctionData* current_function = nullptr;
|
||||
FunctionPtr current_function = nullptr;
|
||||
TypeInferringUnifyStrategy return_unifier;
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE
|
||||
|
@ -298,14 +292,14 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
dst->mutate()->assign_inferred_type(inferred_type);
|
||||
}
|
||||
|
||||
static void assign_inferred_type(const LocalVarData* local_var_or_param, TypePtr inferred_type) {
|
||||
static void assign_inferred_type(LocalVarPtr local_var_or_param, TypePtr inferred_type) {
|
||||
#ifdef TOLK_DEBUG
|
||||
tolk_assert(inferred_type != nullptr && !inferred_type->has_unresolved_inside() && !inferred_type->has_genericT_inside());
|
||||
#endif
|
||||
local_var_or_param->mutate()->assign_inferred_type(inferred_type);
|
||||
}
|
||||
|
||||
static void assign_inferred_type(const FunctionData* fun_ref, TypePtr inferred_return_type, TypePtr inferred_full_type) {
|
||||
static void assign_inferred_type(FunctionPtr fun_ref, TypePtr inferred_return_type, TypePtr inferred_full_type) {
|
||||
#ifdef TOLK_DEBUG
|
||||
tolk_assert(inferred_return_type != nullptr && !inferred_return_type->has_unresolved_inside() && !inferred_return_type->has_genericT_inside());
|
||||
#endif
|
||||
|
@ -365,6 +359,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
return infer_ternary_operator(v->as<ast_ternary_operator>(), hint);
|
||||
case ast_cast_as_operator:
|
||||
return infer_cast_as_operator(v->as<ast_cast_as_operator>());
|
||||
case ast_not_null_operator:
|
||||
return infer_not_null_operator(v->as<ast_not_null_operator>());
|
||||
case ast_is_null_check:
|
||||
return infer_is_null_check(v->as<ast_is_null_check>());
|
||||
case ast_parenthesized_expression:
|
||||
return infer_parenthesized(v->as<ast_parenthesized_expression>(), hint);
|
||||
case ast_reference:
|
||||
|
@ -388,14 +386,29 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
}
|
||||
|
||||
static TypePtr unwrap_nullable(TypePtr type) {
|
||||
while (const TypeDataNullable* as_nullable = type->try_as<TypeDataNullable>()) {
|
||||
type = as_nullable->inner;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static bool expect_integer(AnyExprV v_inferred) {
|
||||
return v_inferred->inferred_type == TypeDataInt::create();
|
||||
}
|
||||
|
||||
static bool expect_integer(TypePtr inferred_type) {
|
||||
return inferred_type == TypeDataInt::create();
|
||||
}
|
||||
|
||||
static bool expect_boolean(AnyExprV v_inferred) {
|
||||
return v_inferred->inferred_type == TypeDataBool::create();
|
||||
}
|
||||
|
||||
static bool expect_boolean(TypePtr inferred_type) {
|
||||
return inferred_type == TypeDataBool::create();
|
||||
}
|
||||
|
||||
static void infer_int_const(V<ast_int_const> v) {
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
}
|
||||
|
@ -467,7 +480,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
} else {
|
||||
if (rhs_type == TypeDataNullLiteral::create()) {
|
||||
fire_error_assign_always_null_to_variable(err_loc->loc, lhs_var->var_ref->try_as<LocalVarData>(), corresponding_maybe_rhs && corresponding_maybe_rhs->type == ast_null_keyword);
|
||||
fire_error_assign_always_null_to_variable(err_loc->loc, lhs_var->var_ref->try_as<LocalVarPtr>(), corresponding_maybe_rhs && corresponding_maybe_rhs->type == ast_null_keyword);
|
||||
}
|
||||
assign_inferred_type(lhs_var, rhs_type);
|
||||
assign_inferred_type(lhs_var->var_ref, rhs_type);
|
||||
|
@ -520,7 +533,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// check `untypedTuple.0 = rhs_tensor` and other non-1 width elements
|
||||
if (auto lhs_dot = lhs->try_as<ast_dot_access>()) {
|
||||
if (lhs_dot->is_target_indexed_access() && lhs_dot->get_obj()->inferred_type == TypeDataTuple::create()) {
|
||||
if (rhs_type->calc_width_on_stack() != 1) {
|
||||
if (rhs_type->get_width_on_stack() != 1) {
|
||||
fire_error_tuple_cannot_have_non1_stack_width_elem(err_loc->loc, rhs_type);
|
||||
}
|
||||
}
|
||||
|
@ -563,8 +576,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
|
||||
assign_inferred_type(v, lhs);
|
||||
if (!builtin_func.empty()) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("_" + static_cast<std::string>(builtin_func) + "_")->as<FunctionData>();
|
||||
tolk_assert(builtin_sym);
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast<std::string>(builtin_func) + "_")->try_as<FunctionPtr>();
|
||||
v->mutate()->assign_fun_ref(builtin_sym);
|
||||
}
|
||||
}
|
||||
|
@ -598,8 +610,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
if (!builtin_func.empty()) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol(static_cast<std::string>(builtin_func) + "_")->as<FunctionData>();
|
||||
tolk_assert(builtin_sym);
|
||||
FunctionPtr builtin_sym = lookup_global_symbol(static_cast<std::string>(builtin_func) + "_")->try_as<FunctionPtr>();
|
||||
v->mutate()->assign_fun_ref(builtin_sym);
|
||||
}
|
||||
}
|
||||
|
@ -617,8 +628,8 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// == != can compare both integers and booleans, (int == bool) is NOT allowed
|
||||
case tok_eq:
|
||||
case tok_neq: {
|
||||
bool both_int = expect_integer(lhs) && expect_integer(rhs);
|
||||
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
|
||||
bool both_int = expect_integer(unwrap_nullable(lhs->inferred_type)) && expect_integer(unwrap_nullable(rhs->inferred_type));
|
||||
bool both_bool = expect_boolean(unwrap_nullable(lhs->inferred_type)) && expect_boolean(unwrap_nullable(rhs->inferred_type));
|
||||
if (!both_int && !both_bool) {
|
||||
if (lhs->inferred_type == rhs->inferred_type) { // compare slice with slice
|
||||
v->error("type " + to_string(lhs) + " can not be compared with `== !=`");
|
||||
|
@ -674,8 +685,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
if (!builtin_func.empty()) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol("_" + static_cast<std::string>(builtin_func) + "_")->as<FunctionData>();
|
||||
tolk_assert(builtin_sym);
|
||||
FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast<std::string>(builtin_func) + "_")->try_as<FunctionPtr>();
|
||||
v->mutate()->assign_fun_ref(builtin_sym);
|
||||
}
|
||||
}
|
||||
|
@ -706,22 +716,38 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
assign_inferred_type(v, v->cast_to_type);
|
||||
}
|
||||
|
||||
void infer_is_null_check(V<ast_is_null_check> v) {
|
||||
infer_any_expr(v->get_expr());
|
||||
assign_inferred_type(v, TypeDataBool::create());
|
||||
}
|
||||
|
||||
void infer_not_null_operator(V<ast_not_null_operator> v) {
|
||||
infer_any_expr(v->get_expr());
|
||||
if (const auto* as_nullable = v->get_expr()->inferred_type->try_as<TypeDataNullable>()) {
|
||||
// operator `!` used for `T?`, leave `T`
|
||||
assign_inferred_type(v, as_nullable->inner);
|
||||
} else {
|
||||
// operator `!` used for non-nullable, probably a warning should be printed
|
||||
assign_inferred_type(v, v->get_expr());
|
||||
}
|
||||
}
|
||||
|
||||
void infer_parenthesized(V<ast_parenthesized_expression> v, TypePtr hint) {
|
||||
infer_any_expr(v->get_expr(), hint);
|
||||
assign_inferred_type(v, v->get_expr());
|
||||
}
|
||||
|
||||
static void infer_reference(V<ast_reference> v) {
|
||||
if (const auto* var_ref = v->sym->try_as<LocalVarData>()) {
|
||||
if (LocalVarPtr var_ref = v->sym->try_as<LocalVarPtr>()) {
|
||||
assign_inferred_type(v, var_ref->declared_type);
|
||||
|
||||
} else if (const auto* const_ref = v->sym->try_as<GlobalConstData>()) {
|
||||
} else if (GlobalConstPtr const_ref = v->sym->try_as<GlobalConstPtr>()) {
|
||||
assign_inferred_type(v, const_ref->is_int_const() ? TypeDataInt::create() : TypeDataSlice::create());
|
||||
|
||||
} else if (const auto* glob_ref = v->sym->try_as<GlobalVarData>()) {
|
||||
} else if (GlobalVarPtr glob_ref = v->sym->try_as<GlobalVarPtr>()) {
|
||||
assign_inferred_type(v, glob_ref->declared_type);
|
||||
|
||||
} else if (const auto* fun_ref = v->sym->try_as<FunctionData>()) {
|
||||
} else if (FunctionPtr fun_ref = v->sym->try_as<FunctionPtr>()) {
|
||||
// it's `globalF` / `globalF<int>` - references to functions used as non-call
|
||||
V<ast_instantiationT_list> v_instantiationTs = v->get_instantiationTs();
|
||||
|
||||
|
@ -758,7 +784,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// given `genericF<int, slice>` / `t.tupleFirst<cell>` (the user manually specified instantiation Ts),
|
||||
// validate and collect them
|
||||
// returns: [int, slice] / [cell]
|
||||
static std::vector<TypePtr> collect_fun_generic_substitutions_from_manually_specified(SrcLocation loc, const FunctionData* fun_ref, V<ast_instantiationT_list> instantiationT_list) {
|
||||
static std::vector<TypePtr> collect_fun_generic_substitutions_from_manually_specified(SrcLocation loc, FunctionPtr fun_ref, V<ast_instantiationT_list> instantiationT_list) {
|
||||
if (fun_ref->genericTs->size() != instantiationT_list->get_items().size()) {
|
||||
throw ParseError(loc, "wrong count of generic T: expected " + std::to_string(fun_ref->genericTs->size()) + ", got " + std::to_string(instantiationT_list->size()));
|
||||
}
|
||||
|
@ -778,11 +804,11 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// example: was `t.tuplePush<slice>(2)`, read <slice>, instantiate `tuplePush<slice>` (will later fail type check)
|
||||
// example: was `var cb = t.tupleFirst<int>;` (used as reference, as non-call), instantiate `tupleFirst<int>`
|
||||
// returns fun_ref to instantiated function
|
||||
static const FunctionData* check_and_instantiate_generic_function(SrcLocation loc, const FunctionData* fun_ref, std::vector<TypePtr>&& substitutionTs) {
|
||||
static FunctionPtr check_and_instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, std::vector<TypePtr>&& substitutionTs) {
|
||||
// T for asm function must be a TVM primitive (width 1), otherwise, asm would act incorrectly
|
||||
if (fun_ref->is_asm_function() || fun_ref->is_builtin_function()) {
|
||||
for (int i = 0; i < static_cast<int>(substitutionTs.size()); ++i) {
|
||||
if (substitutionTs[i]->calc_width_on_stack() != 1) {
|
||||
if (substitutionTs[i]->get_width_on_stack() != 1) {
|
||||
fire_error_calling_asm_function_with_non1_stack_width_arg(loc, fun_ref, substitutionTs, i);
|
||||
}
|
||||
}
|
||||
|
@ -836,7 +862,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
if (hint == nullptr) {
|
||||
fire_error_cannot_deduce_untyped_tuple_access(v->loc, index_at);
|
||||
}
|
||||
if (hint->calc_width_on_stack() != 1) {
|
||||
if (hint->get_width_on_stack() != 1) {
|
||||
fire_error_tuple_cannot_have_non1_stack_width_elem(v->loc, hint);
|
||||
}
|
||||
item_type = hint;
|
||||
|
@ -850,14 +876,14 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
|
||||
// for now, Tolk doesn't have fields and object-scoped methods; `t.tupleSize` is a global function `tupleSize`
|
||||
const Symbol* sym = lookup_global_symbol(field_name);
|
||||
const FunctionData* fun_ref = sym ? sym->try_as<FunctionData>() : nullptr;
|
||||
FunctionPtr fun_ref = sym ? sym->try_as<FunctionPtr>() : nullptr;
|
||||
if (!fun_ref) {
|
||||
v_ident->error("non-existing field `" + static_cast<std::string>(field_name) + "` of type " + to_string(obj_type));
|
||||
}
|
||||
|
||||
// `t.tupleSize` is ok, `cs.tupleSize` not
|
||||
if (!fun_ref->parameters[0].declared_type->can_rhs_be_assigned(obj_type)) {
|
||||
v_ident->error("referencing a method for " + to_string(fun_ref->parameters[0]) + " with object of type " + to_string(obj_type));
|
||||
v_ident->error("referencing a method for " + to_string(fun_ref->parameters[0].declared_type) + " with object of type " + to_string(obj_type));
|
||||
}
|
||||
|
||||
if (fun_ref->is_generic_function() && !v_instantiationTs) {
|
||||
|
@ -886,12 +912,12 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// v is `globalF(args)` / `globalF<int>(args)` / `obj.method(args)` / `local_var(args)` / `getF()(args)`
|
||||
int delta_self = 0;
|
||||
AnyExprV dot_obj = nullptr;
|
||||
const FunctionData* fun_ref = nullptr;
|
||||
FunctionPtr fun_ref = nullptr;
|
||||
V<ast_instantiationT_list> v_instantiationTs = nullptr;
|
||||
|
||||
if (auto v_ref = callee->try_as<ast_reference>()) {
|
||||
// `globalF()` / `globalF<int>()` / `local_var()` / `SOME_CONST()`
|
||||
fun_ref = v_ref->sym->try_as<FunctionData>(); // not null for `globalF`
|
||||
fun_ref = v_ref->sym->try_as<FunctionPtr>(); // not null for `globalF`
|
||||
v_instantiationTs = v_ref->get_instantiationTs(); // present for `globalF<int>()`
|
||||
|
||||
} else if (auto v_dot = callee->try_as<ast_dot_access>()) {
|
||||
|
@ -910,7 +936,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
} else {
|
||||
// for now, Tolk doesn't have fields and object-scoped methods; `t.tupleSize` is a global function `tupleSize`
|
||||
const Symbol* sym = lookup_global_symbol(field_name);
|
||||
fun_ref = sym ? sym->try_as<FunctionData>() : nullptr;
|
||||
fun_ref = sym ? sym->try_as<FunctionPtr>() : nullptr;
|
||||
if (!fun_ref) {
|
||||
v_dot->get_identifier()->error("non-existing method `" + static_cast<std::string>(field_name) + "` of type " + to_string(dot_obj));
|
||||
}
|
||||
|
@ -921,30 +947,26 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// fun_ref remains nullptr
|
||||
}
|
||||
|
||||
// infer argument types, looking at fun_ref's parameters as hints
|
||||
for (int i = 0; i < v->get_num_args(); ++i) {
|
||||
TypePtr param_type = fun_ref && i < fun_ref->get_num_params() - delta_self ? fun_ref->parameters[delta_self + i].declared_type : nullptr;
|
||||
auto arg_i = v->get_arg(i);
|
||||
infer_any_expr(arg_i->get_expr(), param_type && !param_type->has_genericT_inside() ? param_type : nullptr);
|
||||
assign_inferred_type(arg_i, arg_i->get_expr());
|
||||
}
|
||||
|
||||
// handle `local_var()` / `getF()()` / `5()` / `SOME_CONST()` / `obj.method()()()` / `tensorVar.0()`
|
||||
if (!fun_ref) {
|
||||
// treat callee like a usual expression, which must have "callable" inferred type
|
||||
infer_any_expr(callee);
|
||||
const TypeDataFunCallable* f_callable = callee->inferred_type->try_as<TypeDataFunCallable>();
|
||||
if (!f_callable) { // `5()` / `SOME_CONST()` / `null()`
|
||||
v->error("calling a non-function");
|
||||
v->error("calling a non-function " + to_string(callee->inferred_type));
|
||||
}
|
||||
// check arguments count and their types
|
||||
if (v->get_num_args() != static_cast<int>(f_callable->params_types.size())) {
|
||||
v->error("expected " + std::to_string(f_callable->params_types.size()) + " arguments, got " + std::to_string(v->get_arg_list()->size()));
|
||||
}
|
||||
for (int i = 0; i < v->get_num_args(); ++i) {
|
||||
if (!f_callable->params_types[i]->can_rhs_be_assigned(v->get_arg(i)->inferred_type)) {
|
||||
v->get_arg(i)->error("can not pass " + to_string(v->get_arg(i)) + " to " + to_string(f_callable->params_types[i]));
|
||||
auto arg_i = v->get_arg(i)->get_expr();
|
||||
TypePtr param_type = f_callable->params_types[i];
|
||||
infer_any_expr(arg_i, param_type);
|
||||
if (!param_type->can_rhs_be_assigned(arg_i->inferred_type)) {
|
||||
arg_i->error("can not pass " + to_string(arg_i) + " to " + to_string(param_type));
|
||||
}
|
||||
assign_inferred_type(v->get_arg(i), arg_i);
|
||||
}
|
||||
v->mutate()->assign_fun_ref(nullptr); // no fun_ref to a global function
|
||||
assign_inferred_type(v, f_callable->return_type);
|
||||
|
@ -952,30 +974,75 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
// so, we have a call `f(args)` or `obj.f(args)`, f is a global function (fun_ref) (code / asm / builtin)
|
||||
// we're going to iterate over passed arguments, check type compatibility, and (if generic) infer substitutionTs
|
||||
// at first, check arguments count (Tolk doesn't have optional parameters, so just compare counts)
|
||||
int n_arguments = v->get_num_args() + delta_self;
|
||||
int n_parameters = fun_ref->get_num_params();
|
||||
if (!n_parameters && dot_obj) {
|
||||
v->error("`" + fun_ref->name + "` has no parameters and can not be called as method");
|
||||
}
|
||||
if (n_parameters < n_arguments) {
|
||||
v->error("too many arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self));
|
||||
}
|
||||
if (n_arguments < n_parameters) {
|
||||
v->error("too few arguments in call to `" + fun_ref->name + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self));
|
||||
}
|
||||
|
||||
// now, for every passed argument, we need to infer its type, and check it against parameter type
|
||||
// for regular functions, it's obvious
|
||||
// but for generic functions, we need to infer type arguments (substitutionTs) on the fly
|
||||
// (unless Ts are specified by a user like `f<int>(args)` / `t.tupleAt<slice>()`, take them)
|
||||
GenericSubstitutionsDeduceForCall* deducingTs = fun_ref->is_generic_function() ? new GenericSubstitutionsDeduceForCall(fun_ref) : nullptr;
|
||||
if (deducingTs && v_instantiationTs) {
|
||||
deducingTs->provide_manually_specified(collect_fun_generic_substitutions_from_manually_specified(v->loc, fun_ref, v_instantiationTs));
|
||||
}
|
||||
|
||||
// loop over every argument, for `obj.method()` obj is the first one
|
||||
// if genericT deducing has a conflict, ParseError is thrown
|
||||
// note, that deducing Ts one by one is important to manage control flow (mutate params work like assignments)
|
||||
// a corner case, e.g. `f<T>(v1:T?, v2:T?)` and `f(null,2)` will fail on first argument, won't try the second one
|
||||
if (dot_obj) {
|
||||
const LocalVarData& param_0 = fun_ref->parameters[0];
|
||||
TypePtr param_type = param_0.declared_type;
|
||||
if (param_type->has_genericT_inside()) {
|
||||
param_type = deducingTs->auto_deduce_from_argument(dot_obj->loc, param_type, dot_obj->inferred_type);
|
||||
}
|
||||
check_function_argument(param_type, param_0.is_mutate_parameter(), dot_obj, true);
|
||||
}
|
||||
for (int i = 0; i < v->get_num_args(); ++i) {
|
||||
const LocalVarData& param_i = fun_ref->parameters[delta_self + i];
|
||||
AnyExprV arg_i = v->get_arg(i)->get_expr();
|
||||
TypePtr param_type = param_i.declared_type;
|
||||
if (param_type->has_genericT_inside() && deducingTs->is_manually_specified()) { // `f<int>(a)`
|
||||
param_type = deducingTs->replace_by_manually_specified(param_type);
|
||||
}
|
||||
if (param_type->has_genericT_inside()) { // `f(a)` where f is generic: use `a` to infer param type
|
||||
infer_any_expr(arg_i); // then arg_i is inferred without any hint
|
||||
param_type = deducingTs->auto_deduce_from_argument(arg_i->loc, param_type, arg_i->inferred_type);
|
||||
} else {
|
||||
infer_any_expr(arg_i, param_type); // param_type is hint, helps infer arg_i
|
||||
}
|
||||
assign_inferred_type(v->get_arg(i), arg_i); // arg itself is an expression
|
||||
check_function_argument(param_type, param_i.is_mutate_parameter(), arg_i, false);
|
||||
}
|
||||
|
||||
// if it's a generic function `f<T>`, we need to instantiate it, like `f<int>`
|
||||
// same for generic methods `t.tupleAt<T>`, need to achieve `t.tupleAt<int>`
|
||||
|
||||
if (fun_ref->is_generic_function() && v_instantiationTs) {
|
||||
// if Ts are specified by a user like `f<int>(args)` / `t.tupleAt<slice>()`, take them
|
||||
std::vector<TypePtr> substitutions = collect_fun_generic_substitutions_from_manually_specified(v->loc, fun_ref, v_instantiationTs);
|
||||
fun_ref = check_and_instantiate_generic_function(v->loc, fun_ref, std::move(substitutions));
|
||||
|
||||
} else if (fun_ref->is_generic_function()) {
|
||||
// if `f<T>` called like `f(args)`, deduce T from arg types
|
||||
std::vector<TypePtr> arg_types;
|
||||
arg_types.reserve(delta_self + v->get_num_args());
|
||||
if (dot_obj) {
|
||||
arg_types.push_back(dot_obj->inferred_type);
|
||||
if (fun_ref->is_generic_function()) {
|
||||
// if `f(args)` was called, Ts were inferred; check that all of them are known
|
||||
int idx = deducingTs->get_first_not_deduced_idx();
|
||||
if (idx != -1 && hint && fun_ref->declared_return_type->has_genericT_inside()) {
|
||||
// example: `t.tupleFirst()`, T doesn't depend on arguments, but is determined by return type
|
||||
// if used like `var x: int = t.tupleFirst()` / `t.tupleFirst() as int` / etc., use hint
|
||||
deducingTs->auto_deduce_from_argument(v->loc, fun_ref->declared_return_type, hint);
|
||||
idx = deducingTs->get_first_not_deduced_idx();
|
||||
}
|
||||
for (int i = 0; i < v->get_num_args(); ++i) {
|
||||
arg_types.push_back(v->get_arg(i)->inferred_type);
|
||||
if (idx != -1) {
|
||||
v->error("can not deduce " + fun_ref->genericTs->get_nameT(idx));
|
||||
}
|
||||
|
||||
td::Result<std::vector<TypePtr>> deduced = deduce_substitutionTs_on_generic_func_call(fun_ref, std::move(arg_types), hint);
|
||||
if (deduced.is_error()) {
|
||||
v->error(deduced.error().message().str() + " for generic function " + to_string(fun_ref));
|
||||
}
|
||||
fun_ref = check_and_instantiate_generic_function(v->loc, fun_ref, deduced.move_as_ok());
|
||||
fun_ref = check_and_instantiate_generic_function(v->loc, fun_ref, deducingTs->flush());
|
||||
delete deducingTs;
|
||||
|
||||
} else if (UNLIKELY(v_instantiationTs != nullptr)) {
|
||||
// non-generic function/method called with type arguments, like `c.cellHash<int>()` / `beginCell<builder>()`
|
||||
|
@ -988,8 +1055,6 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
v->get_callee()->as<ast_dot_access>()->mutate()->assign_target(fun_ref);
|
||||
v->get_callee()->as<ast_dot_access>()->mutate()->assign_inferred_type(fun_ref->inferred_full_type);
|
||||
}
|
||||
// check arguments count and their types
|
||||
check_function_arguments(fun_ref, v->get_arg_list(), dot_obj);
|
||||
// get return type either from user-specified declaration or infer here on demand traversing its body
|
||||
get_or_infer_return_type(fun_ref);
|
||||
TypePtr inferred_type = dot_obj && fun_ref->does_return_self() ? dot_obj->inferred_type : fun_ref->inferred_return_type;
|
||||
|
@ -1020,7 +1085,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
for (int i = 0; i < v->size(); ++i) {
|
||||
AnyExprV item = v->get_item(i);
|
||||
infer_any_expr(item, tuple_hint && i < tuple_hint->size() ? tuple_hint->items[i] : nullptr);
|
||||
if (item->inferred_type->calc_width_on_stack() != 1) {
|
||||
if (item->inferred_type->get_width_on_stack() != 1) {
|
||||
fire_error_tuple_cannot_have_non1_stack_width_elem(v->get_item(i)->loc, item->inferred_type);
|
||||
}
|
||||
types_list.emplace_back(item->inferred_type);
|
||||
|
@ -1134,7 +1199,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
v->get_thrown_code()->error("excNo of `throw` must be an integer, got " + to_string(v->get_thrown_code()));
|
||||
}
|
||||
infer_any_expr(v->get_thrown_arg());
|
||||
if (v->has_thrown_arg() && v->get_thrown_arg()->inferred_type->calc_width_on_stack() != 1) {
|
||||
if (v->has_thrown_arg() && v->get_thrown_arg()->inferred_type->get_width_on_stack() != 1) {
|
||||
v->get_thrown_arg()->error("can not throw " + to_string(v->get_thrown_arg()) + ", exception arg must occupy exactly 1 stack slot");
|
||||
}
|
||||
}
|
||||
|
@ -1153,7 +1218,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
|
||||
static void process_catch_variable(AnyExprV catch_var, TypePtr catch_var_type) {
|
||||
if (auto v_ref = catch_var->try_as<ast_reference>(); v_ref && v_ref->sym) { // not underscore
|
||||
assign_inferred_type(v_ref->sym->as<LocalVarData>(), catch_var_type);
|
||||
assign_inferred_type(v_ref->sym->try_as<LocalVarPtr>(), catch_var_type);
|
||||
}
|
||||
assign_inferred_type(catch_var, catch_var_type);
|
||||
}
|
||||
|
@ -1163,7 +1228,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
|
||||
// `catch` has exactly 2 variables: excNo and arg (when missing, they are implicit underscores)
|
||||
// `arg` is a curious thing, it can be any TVM primitive, so assign unknown to it
|
||||
// hence, using `fInt(arg)` (int from parameter is a hint) or `arg as slice` works well
|
||||
// hence, using `fInt(arg)` (int from parameter is a target type) or `arg as slice` works well
|
||||
// it's not truly correct, because `arg as (int,int)` also compiles, but can never happen, but let it be user responsibility
|
||||
tolk_assert(v->get_catch_expr()->size() == 2);
|
||||
std::vector<TypePtr> types_list = {TypeDataInt::create(), TypeDataUnknown::create()};
|
||||
|
@ -1175,7 +1240,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
public:
|
||||
static void assign_fun_full_type(const FunctionData* fun_ref, TypePtr inferred_return_type) {
|
||||
static void assign_fun_full_type(FunctionPtr fun_ref, TypePtr inferred_return_type) {
|
||||
// calculate function full type `fun(params) -> ret_type`
|
||||
std::vector<TypePtr> params_types;
|
||||
params_types.reserve(fun_ref->get_num_params());
|
||||
|
@ -1185,7 +1250,7 @@ public:
|
|||
assign_inferred_type(fun_ref, inferred_return_type, TypeDataFunCallable::create(std::move(params_types), inferred_return_type));
|
||||
}
|
||||
|
||||
void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
|
||||
void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
|
||||
if (fun_ref->is_code_function()) {
|
||||
current_function = fun_ref;
|
||||
process_any_statement(v_function->get_body());
|
||||
|
@ -1212,12 +1277,12 @@ public:
|
|||
|
||||
class LaunchInferTypesAndMethodsOnce final {
|
||||
public:
|
||||
static bool should_visit_function(const FunctionData* fun_ref) {
|
||||
static bool should_visit_function(FunctionPtr fun_ref) {
|
||||
// since inferring can be requested on demand, prevent second execution from a regular pipeline launcher
|
||||
return !fun_ref->is_type_inferring_done() && !fun_ref->is_generic_function();
|
||||
}
|
||||
|
||||
static void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
|
||||
static void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
|
||||
InferCheckTypesAndCallsAndFieldsVisitor visitor;
|
||||
visitor.start_visiting_function(fun_ref, v_function);
|
||||
}
|
||||
|
@ -1227,8 +1292,8 @@ public:
|
|||
// example: `fun f() { return g(); } fun g() { ... }`
|
||||
// when analyzing `f()`, we need to infer what fun_ref=g returns
|
||||
// (if `g` is generic, it was already instantiated, so fun_ref=g<int> is here)
|
||||
static void infer_and_save_return_type_of_function(const FunctionData* fun_ref) {
|
||||
static std::vector<const FunctionData*> called_stack;
|
||||
static void infer_and_save_return_type_of_function(FunctionPtr fun_ref) {
|
||||
static std::vector<FunctionPtr> called_stack;
|
||||
|
||||
tolk_assert(!fun_ref->is_generic_function() && !fun_ref->is_type_inferring_done());
|
||||
// if `g` has return type declared, like `fun g(): int { ... }`, don't traverse its body
|
||||
|
@ -1255,7 +1320,7 @@ void pipeline_infer_types_and_calls_and_fields() {
|
|||
visit_ast_of_all_functions<LaunchInferTypesAndMethodsOnce>();
|
||||
}
|
||||
|
||||
void pipeline_infer_types_and_calls_and_fields(const FunctionData* fun_ref) {
|
||||
void pipeline_infer_types_and_calls_and_fields(FunctionPtr fun_ref) {
|
||||
InferCheckTypesAndCallsAndFieldsVisitor visitor;
|
||||
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
|
||||
}
|
||||
|
|
|
@ -53,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;
|
||||
}
|
||||
|
||||
|
@ -75,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,12 +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());
|
||||
}
|
||||
// `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;
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ struct NameAndScopeResolver {
|
|||
return G.symtable.lookup(name);
|
||||
}
|
||||
|
||||
void add_local_var(const LocalVarData* v_sym) {
|
||||
void add_local_var(LocalVarPtr v_sym) {
|
||||
if (UNLIKELY(scopes.empty())) {
|
||||
throw Fatal("unexpected scope_level = 0");
|
||||
}
|
||||
|
@ -168,9 +168,9 @@ static TypePtr finalize_type_data(TypePtr type_data, const GenericsDeclaration*
|
|||
class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody {
|
||||
// more correctly this field shouldn't be static, but currently there is no need to make it a part of state
|
||||
static NameAndScopeResolver current_scope;
|
||||
static const FunctionData* current_function;
|
||||
static FunctionPtr current_function;
|
||||
|
||||
static const LocalVarData* create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) {
|
||||
static LocalVarPtr create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) {
|
||||
LocalVarData* v_sym = new LocalVarData(static_cast<std::string>(name), loc, declared_type, immutable * LocalVarData::flagImmutable, -1);
|
||||
current_scope.add_local_var(v_sym);
|
||||
return v_sym;
|
||||
|
@ -178,7 +178,7 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody {
|
|||
|
||||
static void process_catch_variable(AnyExprV catch_var) {
|
||||
if (auto v_ref = catch_var->try_as<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);
|
||||
}
|
||||
}
|
||||
|
@ -190,14 +190,14 @@ protected:
|
|||
if (sym == nullptr) {
|
||||
v->error("`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");
|
||||
}
|
||||
v->mutate()->assign_var_ref(var_ref);
|
||||
} else {
|
||||
TypePtr declared_type = finalize_type_data(v->declared_type, current_function->genericTs);
|
||||
const LocalVarData* var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable);
|
||||
LocalVarPtr var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable);
|
||||
v->mutate()->assign_resolved_type(declared_type);
|
||||
v->mutate()->assign_var_ref(var_ref);
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ protected:
|
|||
v->mutate()->assign_sym(sym);
|
||||
|
||||
// for global functions, global vars and constants, `import` must exist
|
||||
if (!sym->try_as<LocalVarData>()) {
|
||||
if (!sym->try_as<LocalVarPtr>()) {
|
||||
check_import_exists_when_using_sym(v, sym);
|
||||
}
|
||||
|
||||
|
@ -276,14 +276,14 @@ protected:
|
|||
}
|
||||
|
||||
public:
|
||||
bool should_visit_function(const FunctionData* fun_ref) override {
|
||||
bool should_visit_function(FunctionPtr fun_ref) override {
|
||||
// this pipe is done just after parsing
|
||||
// visit both asm and code functions, resolve identifiers in parameter/return types everywhere
|
||||
// for generic functions, unresolved "T" will be replaced by TypeDataGenericT
|
||||
return true;
|
||||
}
|
||||
|
||||
void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v) override {
|
||||
void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v) override {
|
||||
current_function = fun_ref;
|
||||
|
||||
for (int i = 0; i < v->get_num_params(); ++i) {
|
||||
|
@ -313,7 +313,7 @@ public:
|
|||
};
|
||||
|
||||
NameAndScopeResolver AssignSymInsideFunctionVisitor::current_scope;
|
||||
const FunctionData* AssignSymInsideFunctionVisitor::current_function = nullptr;
|
||||
FunctionPtr AssignSymInsideFunctionVisitor::current_function = nullptr;
|
||||
|
||||
void pipeline_resolve_identifiers_and_assign_symbols() {
|
||||
AssignSymInsideFunctionVisitor visitor;
|
||||
|
@ -337,7 +337,7 @@ void pipeline_resolve_identifiers_and_assign_symbols() {
|
|||
}
|
||||
}
|
||||
|
||||
void pipeline_resolve_identifiers_and_assign_symbols(const FunctionData* fun_ref) {
|
||||
void pipeline_resolve_identifiers_and_assign_symbols(FunctionPtr fun_ref) {
|
||||
AssignSymInsideFunctionVisitor visitor;
|
||||
if (visitor.should_visit_function(fun_ref)) {
|
||||
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -120,7 +120,7 @@ static void fire_error_redefinition_of_symbol(SrcLocation loc, const Symbol* pre
|
|||
throw ParseError(loc, "redefinition of built-in symbol");
|
||||
}
|
||||
|
||||
void GlobalSymbolTable::add_function(const FunctionData* f_sym) {
|
||||
void GlobalSymbolTable::add_function(FunctionPtr f_sym) {
|
||||
auto key = key_hash(f_sym->name);
|
||||
auto [it, inserted] = entries.emplace(key, f_sym);
|
||||
if (!inserted) {
|
||||
|
@ -128,7 +128,7 @@ void GlobalSymbolTable::add_function(const FunctionData* f_sym) {
|
|||
}
|
||||
}
|
||||
|
||||
void GlobalSymbolTable::add_global_var(const GlobalVarData* g_sym) {
|
||||
void GlobalSymbolTable::add_global_var(GlobalVarPtr g_sym) {
|
||||
auto key = key_hash(g_sym->name);
|
||||
auto [it, inserted] = entries.emplace(key, g_sym);
|
||||
if (!inserted) {
|
||||
|
@ -136,7 +136,7 @@ void GlobalSymbolTable::add_global_var(const GlobalVarData* g_sym) {
|
|||
}
|
||||
}
|
||||
|
||||
void GlobalSymbolTable::add_global_const(const GlobalConstData* c_sym) {
|
||||
void GlobalSymbolTable::add_global_const(GlobalConstPtr c_sym) {
|
||||
auto key = key_hash(c_sym->name);
|
||||
auto [it, inserted] = entries.emplace(key, c_sym);
|
||||
if (!inserted) {
|
||||
|
|
|
@ -37,17 +37,12 @@ struct Symbol {
|
|||
|
||||
virtual ~Symbol() = default;
|
||||
|
||||
template<class T>
|
||||
const T* as() const {
|
||||
template<class ConstTPtr>
|
||||
ConstTPtr try_as() const {
|
||||
#ifdef TOLK_DEBUG
|
||||
assert(dynamic_cast<const T*>(this) != nullptr);
|
||||
assert(this != nullptr);
|
||||
#endif
|
||||
return dynamic_cast<const T*>(this);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
const T* try_as() const {
|
||||
return dynamic_cast<const T*>(this);
|
||||
return dynamic_cast<ConstTPtr>(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -229,9 +224,9 @@ class GlobalSymbolTable {
|
|||
}
|
||||
|
||||
public:
|
||||
void add_function(const FunctionData* f_sym);
|
||||
void add_global_var(const GlobalVarData* g_sym);
|
||||
void add_global_const(const GlobalConstData* c_sym);
|
||||
void add_function(FunctionPtr f_sym);
|
||||
void add_global_var(GlobalVarPtr g_sym);
|
||||
void add_global_const(GlobalConstPtr c_sym);
|
||||
|
||||
const Symbol* lookup(std::string_view name) const {
|
||||
const auto it = entries.find(key_hash(name));
|
||||
|
|
18
tolk/tolk.h
18
tolk/tolk.h
|
@ -45,7 +45,7 @@ typedef int const_idx_t;
|
|||
|
||||
struct TmpVar {
|
||||
var_idx_t ir_idx; // every var in IR represents 1 stack slot
|
||||
TypePtr v_type; // calc_width_on_stack() is 1
|
||||
TypePtr v_type; // get_width_on_stack() is 1
|
||||
std::string name; // "x" for vars originated from user sources; "x.0" for tensor components; empty for implicitly created tmp vars
|
||||
SrcLocation loc; // location of var declaration in sources or where a tmp var was originated
|
||||
#ifdef TOLK_DEBUG
|
||||
|
@ -283,8 +283,8 @@ struct Op {
|
|||
enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 };
|
||||
int flags;
|
||||
std::unique_ptr<Op> next;
|
||||
const FunctionData* f_sym = nullptr;
|
||||
const GlobalVarData* g_sym = nullptr;
|
||||
FunctionPtr f_sym = nullptr;
|
||||
GlobalVarPtr g_sym = nullptr;
|
||||
SrcLocation where;
|
||||
VarDescrList var_info;
|
||||
std::vector<VarDescr> args;
|
||||
|
@ -313,19 +313,19 @@ struct Op {
|
|||
: cl(_cl), flags(0), f_sym(nullptr), where(_where), left(std::move(_left)), right(std::move(_right)) {
|
||||
}
|
||||
Op(SrcLocation _where, OpKind _cl, const std::vector<var_idx_t>& _left, const std::vector<var_idx_t>& _right,
|
||||
const FunctionData* _fun)
|
||||
FunctionPtr _fun)
|
||||
: cl(_cl), flags(0), f_sym(_fun), where(_where), left(_left), right(_right) {
|
||||
}
|
||||
Op(SrcLocation _where, OpKind _cl, std::vector<var_idx_t>&& _left, std::vector<var_idx_t>&& _right,
|
||||
const FunctionData* _fun)
|
||||
FunctionPtr _fun)
|
||||
: cl(_cl), flags(0), f_sym(_fun), where(_where), left(std::move(_left)), right(std::move(_right)) {
|
||||
}
|
||||
Op(SrcLocation _where, OpKind _cl, const std::vector<var_idx_t>& _left, const std::vector<var_idx_t>& _right,
|
||||
const GlobalVarData* _gvar)
|
||||
GlobalVarPtr _gvar)
|
||||
: cl(_cl), flags(0), g_sym(_gvar), where(_where), left(_left), right(_right) {
|
||||
}
|
||||
Op(SrcLocation _where, OpKind _cl, std::vector<var_idx_t>&& _left, std::vector<var_idx_t>&& _right,
|
||||
const GlobalVarData* _gvar)
|
||||
GlobalVarPtr _gvar)
|
||||
: cl(_cl), flags(0), g_sym(_gvar), where(_where), left(std::move(_left)), right(std::move(_right)) {
|
||||
}
|
||||
|
||||
|
@ -1083,7 +1083,7 @@ struct FunctionBodyAsm {
|
|||
|
||||
struct CodeBlob {
|
||||
int var_cnt, in_var_cnt;
|
||||
const FunctionData* fun_ref;
|
||||
FunctionPtr fun_ref;
|
||||
std::string name;
|
||||
SrcLocation loc;
|
||||
std::vector<TmpVar> vars;
|
||||
|
@ -1094,7 +1094,7 @@ struct CodeBlob {
|
|||
#endif
|
||||
std::stack<std::unique_ptr<Op>*> cur_ops_stack;
|
||||
bool require_callxargs = false;
|
||||
CodeBlob(std::string name, SrcLocation loc, const FunctionData* fun_ref)
|
||||
CodeBlob(std::string name, SrcLocation loc, FunctionPtr fun_ref)
|
||||
: var_cnt(0), in_var_cnt(0), fun_ref(fun_ref), name(std::move(name)), loc(loc), cur_ops(&ops) {
|
||||
}
|
||||
template <typename... Args>
|
||||
|
|
|
@ -108,6 +108,19 @@ void type_system_init() {
|
|||
// and creates an object only if it isn't found in a global hashtable
|
||||
//
|
||||
|
||||
TypePtr TypeDataNullable::create(TypePtr inner) {
|
||||
TypeDataTypeIdCalculation hash(1774084920039440885ULL);
|
||||
hash.feed_child(inner);
|
||||
|
||||
if (TypePtr existing = hash.get_existing()) {
|
||||
return existing;
|
||||
}
|
||||
// most types (int?, slice?, etc.), when nullable, still occupy 1 stack slot (holding TVM NULL at runtime)
|
||||
// but for example for `(int, int)` we need an extra stack slot "null flag"
|
||||
int width_on_stack = inner->can_hold_tvm_null_instead() ? 1 : inner->get_width_on_stack() + 1;
|
||||
return hash.register_unique(new TypeDataNullable(hash.type_id(), hash.children_flags(), width_on_stack, inner));
|
||||
}
|
||||
|
||||
TypePtr TypeDataFunCallable::create(std::vector<TypePtr>&& params_types, TypePtr return_type) {
|
||||
TypeDataTypeIdCalculation hash(3184039965511020991ULL);
|
||||
for (TypePtr param : params_types) {
|
||||
|
@ -143,7 +156,11 @@ TypePtr TypeDataTensor::create(std::vector<TypePtr>&& items) {
|
|||
if (TypePtr existing = hash.get_existing()) {
|
||||
return existing;
|
||||
}
|
||||
return hash.register_unique(new TypeDataTensor(hash.type_id(), hash.children_flags(), std::move(items)));
|
||||
int width_on_stack = 0;
|
||||
for (TypePtr item : items) {
|
||||
width_on_stack += item->get_width_on_stack();
|
||||
}
|
||||
return hash.register_unique(new TypeDataTensor(hash.type_id(), hash.children_flags(), width_on_stack, std::move(items)));
|
||||
}
|
||||
|
||||
TypePtr TypeDataTypedTuple::create(std::vector<TypePtr>&& items) {
|
||||
|
@ -178,6 +195,12 @@ TypePtr TypeDataUnresolved::create(std::string&& text, SrcLocation loc) {
|
|||
// only non-trivial implementations are here; trivial are defined in .h file
|
||||
//
|
||||
|
||||
std::string TypeDataNullable::as_human_readable() const {
|
||||
std::string nested = inner->as_human_readable();
|
||||
bool embrace = inner->try_as<TypeDataFunCallable>();
|
||||
return embrace ? "(" + nested + ")?" : nested + "?";
|
||||
}
|
||||
|
||||
std::string TypeDataFunCallable::as_human_readable() const {
|
||||
std::string result = "(";
|
||||
for (TypePtr param : params_types) {
|
||||
|
@ -223,6 +246,11 @@ std::string TypeDataTypedTuple::as_human_readable() const {
|
|||
// only non-trivial implementations are here; by default (no children), `callback(this)` is executed
|
||||
//
|
||||
|
||||
void TypeDataNullable::traverse(const TraverserCallbackT& callback) const {
|
||||
callback(this);
|
||||
inner->traverse(callback);
|
||||
}
|
||||
|
||||
void TypeDataFunCallable::traverse(const TraverserCallbackT& callback) const {
|
||||
callback(this);
|
||||
for (TypePtr param : params_types) {
|
||||
|
@ -254,6 +282,10 @@ void TypeDataTypedTuple::traverse(const TraverserCallbackT& callback) const {
|
|||
// only non-trivial implementations are here; by default (no children), `return callback(this)` is executed
|
||||
//
|
||||
|
||||
TypePtr TypeDataNullable::replace_children_custom(const ReplacerCallbackT& callback) const {
|
||||
return callback(create(inner->replace_children_custom(callback)));
|
||||
}
|
||||
|
||||
TypePtr TypeDataFunCallable::replace_children_custom(const ReplacerCallbackT& callback) const {
|
||||
std::vector<TypePtr> mapped;
|
||||
mapped.reserve(params_types.size());
|
||||
|
@ -282,53 +314,17 @@ TypePtr TypeDataTypedTuple::replace_children_custom(const ReplacerCallbackT& cal
|
|||
}
|
||||
|
||||
|
||||
// --------------------------------------------
|
||||
// calc_width_on_stack()
|
||||
//
|
||||
// returns the number of stack slots occupied by a variable of this type
|
||||
// only non-trivial implementations are here; by default (most types) occupy 1 stack slot
|
||||
//
|
||||
|
||||
int TypeDataGenericT::calc_width_on_stack() const {
|
||||
// this function is invoked only in functions with generics already instantiated
|
||||
assert(false);
|
||||
return -999999;
|
||||
}
|
||||
|
||||
int TypeDataTensor::calc_width_on_stack() const {
|
||||
int sum = 0;
|
||||
for (TypePtr item : items) {
|
||||
sum += item->calc_width_on_stack();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int TypeDataUnresolved::calc_width_on_stack() const {
|
||||
// since early pipeline stages, no unresolved types left
|
||||
assert(false);
|
||||
return -999999;
|
||||
}
|
||||
|
||||
int TypeDataVoid::calc_width_on_stack() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------
|
||||
// can_rhs_be_assigned()
|
||||
//
|
||||
// on `var lhs: <lhs_type> = rhs`, having inferred rhs_type, check that it can be assigned without any casts
|
||||
// the same goes for passing arguments, returning values, etc. — where the "receiver" (lhs) checks "applier" (rhs)
|
||||
// for now, `null` can be assigned to any TVM primitive, be later we'll have T? types and null safety
|
||||
//
|
||||
|
||||
bool TypeDataInt::can_rhs_be_assigned(TypePtr rhs) const {
|
||||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -336,9 +332,6 @@ bool TypeDataBool::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -346,9 +339,6 @@ bool TypeDataCell::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -356,9 +346,6 @@ bool TypeDataSlice::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -366,9 +353,6 @@ bool TypeDataBuilder::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -376,9 +360,6 @@ bool TypeDataTuple::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -386,9 +367,6 @@ bool TypeDataContinuation::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -396,6 +374,19 @@ bool TypeDataNullLiteral::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
return rhs == this;
|
||||
}
|
||||
|
||||
bool TypeDataNullable::can_rhs_be_assigned(TypePtr rhs) const {
|
||||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
if (const TypeDataNullable* rhs_nullable = rhs->try_as<TypeDataNullable>()) {
|
||||
return inner->can_rhs_be_assigned(rhs_nullable->inner);
|
||||
}
|
||||
return inner->can_rhs_be_assigned(rhs);
|
||||
}
|
||||
|
||||
bool TypeDataFunCallable::can_rhs_be_assigned(TypePtr rhs) const {
|
||||
return rhs == this;
|
||||
}
|
||||
|
@ -414,7 +405,6 @@ bool TypeDataTensor::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
// note, that tensors can not accept null
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -427,9 +417,6 @@ bool TypeDataTypedTuple::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -455,41 +442,69 @@ bool TypeDataVoid::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
//
|
||||
|
||||
bool TypeDataInt::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) { // `int` as `int?`
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataBool::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this || cast_to == TypeDataInt::create();
|
||||
}
|
||||
|
||||
bool TypeDataCell::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataSlice::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataBuilder::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataTuple::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataContinuation::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataNullLiteral::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
return cast_to == this
|
||||
|| cast_to == TypeDataInt::create() || cast_to == TypeDataBool::create() || cast_to == TypeDataCell::create() || cast_to == TypeDataSlice::create()
|
||||
|| cast_to == TypeDataBuilder::create() || cast_to == TypeDataContinuation::create() || cast_to == TypeDataTuple::create()
|
||||
|| cast_to->try_as<TypeDataTypedTuple>();
|
||||
return cast_to == this || cast_to->try_as<TypeDataNullable>();
|
||||
}
|
||||
|
||||
bool TypeDataNullable::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return inner->can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeDataFunCallable::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return this == cast_to;
|
||||
}
|
||||
|
||||
|
@ -506,6 +521,9 @@ bool TypeDataTensor::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -518,14 +536,15 @@ bool TypeDataTypedTuple::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
|
||||
return can_be_casted_with_as_operator(to_nullable->inner);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeDataUnknown::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
// 'unknown' can be cast to any type
|
||||
// (though it's not valid for exception arguments when casting them to non-1 stack width,
|
||||
// but to ensure it, we need a special type "unknown TVM primitive", which is overwhelming I think)
|
||||
return true;
|
||||
// 'unknown' can be cast to any TVM value
|
||||
return cast_to->get_width_on_stack() == 1;
|
||||
}
|
||||
|
||||
bool TypeDataUnresolved::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
|
@ -537,12 +556,45 @@ bool TypeDataVoid::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
|||
}
|
||||
|
||||
|
||||
// --------------------------------------------
|
||||
// can_hold_tvm_null_instead()
|
||||
//
|
||||
// assigning `null` to a primitive variable like `int?` / `cell?` can store TVM NULL inside the same slot
|
||||
// (that's why the default implementation is just "return true", and most of types occupy 1 slot)
|
||||
// but for complex variables, like `(int, int)?`, "null presence" is kept in a separate slot (UTag for union types)
|
||||
// though still, tricky situations like `(int, ())?` can still "embed" TVM NULL in parallel with original value
|
||||
//
|
||||
|
||||
bool TypeDataNullable::can_hold_tvm_null_instead() const {
|
||||
if (get_width_on_stack() != 1) { // `(int, int)?` / `()?` can not hold null instead
|
||||
return false; // only `int?` / `cell?` / `StructWith1IntField?` can
|
||||
} // and some tricky situations like `(int, ())?`, but not `(int?, ())?`
|
||||
return !inner->can_hold_tvm_null_instead();
|
||||
}
|
||||
|
||||
bool TypeDataTensor::can_hold_tvm_null_instead() const {
|
||||
if (get_width_on_stack() != 1) { // `(int, int)` / `()` can not hold null instead, since null is 1 slot
|
||||
return false; // only `((), int)` and similar can:
|
||||
} // one item is width 1 (and not nullable), others are 0
|
||||
for (TypePtr item : items) {
|
||||
if (item->get_width_on_stack() == 1 && !item->can_hold_tvm_null_instead()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeDataVoid::can_hold_tvm_null_instead() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------
|
||||
// parsing type from tokens
|
||||
//
|
||||
// here we implement parsing types (mostly after colon) to TypeData
|
||||
// example: `var v: int` is TypeDataInt
|
||||
// example: `var v: (builder, [cell])` is TypeDataTensor(TypeDataBuilder, TypeDataTypedTuple(TypeDataCell))
|
||||
// example: `var v: (builder?, [cell])` is TypeDataTensor(TypeDataNullable(TypeDataBuilder), TypeDataTypedTuple(TypeDataCell))
|
||||
// example: `fun f(): ()` is TypeDataTensor() (an empty one)
|
||||
//
|
||||
// note, that unrecognized type names (MyEnum, MyStruct, T) are parsed as TypeDataUnresolved,
|
||||
|
@ -633,7 +685,8 @@ static TypePtr parse_type_nullable(Lexer& lex) {
|
|||
TypePtr result = parse_simple_type(lex);
|
||||
|
||||
if (lex.tok() == tok_question) {
|
||||
lex.error("nullable types are not supported yet");
|
||||
lex.next();
|
||||
result = TypeDataNullable::create(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -50,6 +50,8 @@ class TypeData {
|
|||
const uint64_t type_id;
|
||||
// bits of flag_mask, to store often-used properties and return them without tree traversing
|
||||
const int flags;
|
||||
// how many slots on a stack this type occupies (calculated on creation), e.g. `int`=1, `(int,int)`=2, `(int,int)?`=3
|
||||
const int width_on_stack;
|
||||
|
||||
friend class TypeDataTypeIdCalculation;
|
||||
|
||||
|
@ -60,9 +62,10 @@ protected:
|
|||
flag_contains_unresolved_inside = 1 << 3,
|
||||
};
|
||||
|
||||
explicit TypeData(uint64_t type_id, int flags_with_children)
|
||||
explicit TypeData(uint64_t type_id, int flags_with_children, int width_on_stack)
|
||||
: type_id(type_id)
|
||||
, flags(flags_with_children) {
|
||||
, flags(flags_with_children)
|
||||
, width_on_stack(width_on_stack) {
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -74,6 +77,7 @@ public:
|
|||
}
|
||||
|
||||
uint64_t get_type_id() const { return type_id; }
|
||||
int get_width_on_stack() const { return width_on_stack; }
|
||||
|
||||
bool has_unknown_inside() const { return flags & flag_contains_unknown_inside; }
|
||||
bool has_genericT_inside() const { return flags & flag_contains_genericT_inside; }
|
||||
|
@ -86,6 +90,10 @@ public:
|
|||
virtual bool can_rhs_be_assigned(TypePtr rhs) const = 0;
|
||||
virtual bool can_be_casted_with_as_operator(TypePtr cast_to) const = 0;
|
||||
|
||||
virtual bool can_hold_tvm_null_instead() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void traverse(const TraverserCallbackT& callback) const {
|
||||
callback(this);
|
||||
}
|
||||
|
@ -93,17 +101,13 @@ public:
|
|||
virtual TypePtr replace_children_custom(const ReplacerCallbackT& callback) const {
|
||||
return callback(this);
|
||||
}
|
||||
|
||||
virtual int calc_width_on_stack() const {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* `int` is TypeDataInt, representation of TVM int.
|
||||
*/
|
||||
class TypeDataInt final : public TypeData {
|
||||
TypeDataInt() : TypeData(1ULL, 0) {}
|
||||
TypeDataInt() : TypeData(1ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -121,7 +125,7 @@ public:
|
|||
* From the type system point of view, int and bool are different, not-autocastable types.
|
||||
*/
|
||||
class TypeDataBool final : public TypeData {
|
||||
TypeDataBool() : TypeData(2ULL, 0) {}
|
||||
TypeDataBool() : TypeData(2ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -138,7 +142,7 @@ public:
|
|||
* `cell` is TypeDataCell, representation of TVM cell.
|
||||
*/
|
||||
class TypeDataCell final : public TypeData {
|
||||
TypeDataCell() : TypeData(3ULL, 0) {}
|
||||
TypeDataCell() : TypeData(3ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -155,7 +159,7 @@ public:
|
|||
* `slice` is TypeDataSlice, representation of TVM slice.
|
||||
*/
|
||||
class TypeDataSlice final : public TypeData {
|
||||
TypeDataSlice() : TypeData(4ULL, 0) {}
|
||||
TypeDataSlice() : TypeData(4ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -172,7 +176,7 @@ public:
|
|||
* `builder` is TypeDataBuilder, representation of TVM builder.
|
||||
*/
|
||||
class TypeDataBuilder final : public TypeData {
|
||||
TypeDataBuilder() : TypeData(5ULL, 0) {}
|
||||
TypeDataBuilder() : TypeData(5ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -191,7 +195,7 @@ public:
|
|||
* so getting its element results in TypeDataUnknown (which must be assigned/cast explicitly).
|
||||
*/
|
||||
class TypeDataTuple final : public TypeData {
|
||||
TypeDataTuple() : TypeData(6ULL, 0) {}
|
||||
TypeDataTuple() : TypeData(6ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -209,7 +213,7 @@ public:
|
|||
* It's like "untyped callable", not compatible with other types.
|
||||
*/
|
||||
class TypeDataContinuation final : public TypeData {
|
||||
TypeDataContinuation() : TypeData(7ULL, 0) {}
|
||||
TypeDataContinuation() : TypeData(7ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -224,12 +228,12 @@ public:
|
|||
|
||||
/*
|
||||
* `null` has TypeDataNullLiteral type.
|
||||
* Currently, it can be assigned to int/slice/etc., but later Tolk will have T? types and null safety.
|
||||
* It can be assigned only to nullable types (`int?`, etc.), to ensure null safety.
|
||||
* Note, that `var i = null`, though valid (i would be constant null), fires an "always-null" compilation error
|
||||
* (it's much better for user to see an error here than when he passes this variable somewhere).
|
||||
*/
|
||||
class TypeDataNullLiteral final : public TypeData {
|
||||
TypeDataNullLiteral() : TypeData(8ULL, 0) {}
|
||||
TypeDataNullLiteral() : TypeData(8ULL, 0, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -242,6 +246,30 @@ public:
|
|||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
};
|
||||
|
||||
/*
|
||||
* `T?` is "nullable T".
|
||||
* It can be converted to T either with ! (non-null assertion operator) or with smart casts.
|
||||
*/
|
||||
class TypeDataNullable final : public TypeData {
|
||||
TypeDataNullable(uint64_t type_id, int children_flags, int width_on_stack, TypePtr inner)
|
||||
: TypeData(type_id, children_flags, width_on_stack)
|
||||
, inner(inner) {}
|
||||
|
||||
public:
|
||||
const TypePtr inner;
|
||||
|
||||
static TypePtr create(TypePtr inner);
|
||||
|
||||
bool is_primitive_nullable() const { return get_width_on_stack() == 1 && inner->get_width_on_stack() == 1; }
|
||||
|
||||
std::string as_human_readable() const override;
|
||||
bool can_rhs_be_assigned(TypePtr rhs) const override;
|
||||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
void traverse(const TraverserCallbackT& callback) const override;
|
||||
TypePtr replace_children_custom(const ReplacerCallbackT& callback) const override;
|
||||
bool can_hold_tvm_null_instead() const override;
|
||||
};
|
||||
|
||||
/*
|
||||
* `fun(int, int) -> void` is TypeDataFunCallable, think of is as a typed continuation.
|
||||
* A type of function `fun f(x: int) { return x; }` is actually `fun(int) -> int`.
|
||||
|
@ -249,7 +277,7 @@ public:
|
|||
*/
|
||||
class TypeDataFunCallable final : public TypeData {
|
||||
TypeDataFunCallable(uint64_t type_id, int children_flags, std::vector<TypePtr>&& params_types, TypePtr return_type)
|
||||
: TypeData(type_id, children_flags)
|
||||
: TypeData(type_id, children_flags, 1)
|
||||
, params_types(std::move(params_types))
|
||||
, return_type(return_type) {}
|
||||
|
||||
|
@ -275,7 +303,7 @@ public:
|
|||
*/
|
||||
class TypeDataGenericT final : public TypeData {
|
||||
TypeDataGenericT(uint64_t type_id, std::string&& nameT)
|
||||
: TypeData(type_id, flag_contains_genericT_inside)
|
||||
: TypeData(type_id, flag_contains_genericT_inside, -999999) // width undefined until instantiated
|
||||
, nameT(std::move(nameT)) {}
|
||||
|
||||
public:
|
||||
|
@ -286,7 +314,6 @@ public:
|
|||
std::string as_human_readable() const override { return nameT; }
|
||||
bool can_rhs_be_assigned(TypePtr rhs) const override;
|
||||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
int calc_width_on_stack() const override;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -296,8 +323,8 @@ public:
|
|||
* A tensor can be empty.
|
||||
*/
|
||||
class TypeDataTensor final : public TypeData {
|
||||
TypeDataTensor(uint64_t type_id, int children_flags, std::vector<TypePtr>&& items)
|
||||
: TypeData(type_id, children_flags)
|
||||
TypeDataTensor(uint64_t type_id, int children_flags, int width_on_stack, std::vector<TypePtr>&& items)
|
||||
: TypeData(type_id, children_flags, width_on_stack)
|
||||
, items(std::move(items)) {}
|
||||
|
||||
public:
|
||||
|
@ -312,7 +339,7 @@ public:
|
|||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
void traverse(const TraverserCallbackT& callback) const override;
|
||||
TypePtr replace_children_custom(const ReplacerCallbackT& callback) const override;
|
||||
int calc_width_on_stack() const override;
|
||||
bool can_hold_tvm_null_instead() const override;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -322,7 +349,7 @@ public:
|
|||
*/
|
||||
class TypeDataTypedTuple final : public TypeData {
|
||||
TypeDataTypedTuple(uint64_t type_id, int children_flags, std::vector<TypePtr>&& items)
|
||||
: TypeData(type_id, children_flags)
|
||||
: TypeData(type_id, children_flags, 1)
|
||||
, items(std::move(items)) {}
|
||||
|
||||
public:
|
||||
|
@ -346,7 +373,7 @@ public:
|
|||
* The only thing available to do with unknown is to cast it: `catch (excNo, arg) { var i = arg as int; }`
|
||||
*/
|
||||
class TypeDataUnknown final : public TypeData {
|
||||
TypeDataUnknown() : TypeData(20ULL, flag_contains_unknown_inside) {}
|
||||
TypeDataUnknown() : TypeData(20ULL, flag_contains_unknown_inside, 1) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -367,7 +394,7 @@ public:
|
|||
*/
|
||||
class TypeDataUnresolved final : public TypeData {
|
||||
TypeDataUnresolved(uint64_t type_id, std::string&& text, SrcLocation loc)
|
||||
: TypeData(type_id, flag_contains_unresolved_inside)
|
||||
: TypeData(type_id, flag_contains_unresolved_inside, -999999)
|
||||
, text(std::move(text))
|
||||
, loc(loc) {}
|
||||
|
||||
|
@ -380,7 +407,6 @@ public:
|
|||
std::string as_human_readable() const override { return text + "*"; }
|
||||
bool can_rhs_be_assigned(TypePtr rhs) const override;
|
||||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
int calc_width_on_stack() const override;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -389,7 +415,7 @@ public:
|
|||
* Empty tensor is not compatible with void, although at IR level they are similar, 0 stack slots.
|
||||
*/
|
||||
class TypeDataVoid final : public TypeData {
|
||||
TypeDataVoid() : TypeData(10ULL, 0) {}
|
||||
TypeDataVoid() : TypeData(10ULL, 0, 0) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
@ -400,7 +426,7 @@ public:
|
|||
std::string as_human_readable() const override { return "void"; }
|
||||
bool can_rhs_be_assigned(TypePtr rhs) const override;
|
||||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
int calc_width_on_stack() const override;
|
||||
bool can_hold_tvm_null_instead() const override;
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue