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

[Tolk] Nullable types T? and null safety

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

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

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

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

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

View file

@ -139,7 +139,7 @@ fun getMyOriginalBalance(): int
/// `int` — balance in nanotoncoins;
/// `cell` — a dictionary with 32-bit keys representing the balance of "extra currencies".
@pure
fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell]
fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell?]
asm "BALANCE";
/// Returns the logical time of the current transaction.
@ -154,7 +154,7 @@ fun getCurrentBlockLogicalTime(): int
/// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value.
@pure
fun getBlockchainConfigParam(x: int): cell
fun getBlockchainConfigParam(x: int): cell?
asm "CONFIGOPTPARAM";
/// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later.
@ -291,7 +291,7 @@ fun calculateSliceSizeStrict(s: slice, maxCells: int): (int, int, int)
/// otherwise the returned value is one plus the maximum of depths of cells referred to from [c].
/// If [c] is a `null` instead of a cell, returns zero.
@pure
fun getCellDepth(c: cell): int
fun getCellDepth(c: cell?): int
asm "CDEPTH";
/// Returns the depth of `slice` [s].
@ -417,12 +417,12 @@ fun getLastBits(self: slice, len: int): slice
/// Loads a dictionary (TL HashMapE structure, represented as TVM cell) from a slice.
/// Returns `null` if `nothing` constructor is used.
@pure
fun loadDict(mutate self: slice): cell
fun loadDict(mutate self: slice): cell?
asm( -> 1 0) "LDDICT";
/// Preloads a dictionary (cell) from a slice.
@pure
fun preloadDict(self: slice): cell
fun preloadDict(self: slice): cell?
asm "PLDDICT";
/// Loads a dictionary as [loadDict], but returns only the remainder of the slice.
@ -433,12 +433,12 @@ fun skipDict(mutate self: slice): self
/// Loads (Maybe ^Cell) from a slice.
/// In other words, loads 1 bit: if it's true, loads the first ref, otherwise returns `null`.
@pure
fun loadMaybeRef(mutate self: slice): cell
fun loadMaybeRef(mutate self: slice): cell?
asm( -> 1 0) "LDOPTREF";
/// Preloads (Maybe ^Cell) from a slice.
@pure
fun preloadMaybeRef(self: slice): cell
fun preloadMaybeRef(self: slice): cell?
asm "PLDOPTREF";
/// Loads (Maybe ^Cell), but returns only the remainder of the slice.
@ -497,13 +497,13 @@ fun storeBool(mutate self: builder, x: bool): self
/// Stores dictionary (represented by TVM `cell` or `null`) into a builder.
/// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise.
@pure
fun storeDict(mutate self: builder, c: cell): self
fun storeDict(mutate self: builder, c: cell?): self
asm(c self) "STDICT";
/// Stores (Maybe ^Cell) into a builder.
/// In other words, if cell is `null`, store '0' bit; otherwise, store '1' and a ref to [c].
@pure
fun storeMaybeRef(mutate self: builder, c: cell): self
fun storeMaybeRef(mutate self: builder, c: cell?): self
asm(c self) "STOPTREF";
/// Concatenates two builders.
@ -661,7 +661,7 @@ fun reserveToncoinsOnBalance(nanoTonCoins: int, reserveMode: int): void
/// Similar to [reserveToncoinsOnBalance], but also accepts a dictionary extraAmount (represented by a cell or null)
/// with extra currencies. In this way currencies other than Toncoin can be reserved.
fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell, reserveMode: int): void
fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell?, reserveMode: int): void
asm "RAWRESERVEX";

View file

@ -14,17 +14,18 @@ fun createEmptyList(): tuple
/// Adds an element to the beginning of lisp-style list.
/// Note, that it does not mutate the list: instead, it returns a new one (it's a lisp pattern).
@pure
fun listPrepend<X>(head: X, tail: tuple): tuple
fun listPrepend<X>(head: X, tail: tuple?): tuple
asm "CONS";
/// Extracts the head and the tail of lisp-style list.
@pure
fun listSplit<X>(list: tuple): (X, tuple)
fun listSplit<X>(list: tuple): (X, tuple?)
asm "UNCONS";
/// Extracts the tail and the head of lisp-style list.
/// After extracting the last element, tuple is assigned to null.
@pure
fun listNext<X>(mutate self: tuple): X
fun listNext<X>(mutate self: tuple?): X
asm( -> 1 0) "UNCONS";
/// Returns the head of lisp-style list.
@ -34,5 +35,5 @@ fun listGetHead<X>(list: tuple): X
/// Returns the tail of lisp-style list.
@pure
fun listGetTail(list: tuple): tuple
fun listGetTail(list: tuple): tuple?
asm "CDR";

View file

@ -9,288 +9,289 @@ tolk 0.8
- uDict* - dicts with unsigned integer keys
- sDict* - dicts with arbitrary slice keys
When accessing a dict element, you should not only provide a key, but provide keyLen,
since for optimization, for optimization, key length is not stored in the dictionary itself.
since for optimization, key length is not stored in the dictionary itself.
Every dictionary object (`self` parameter) can be null. TVM NULL is essentially "empty dictionary".
*/
/// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL
@pure
fun createEmptyDict(): cell
fun createEmptyDict(): cell?
asm "NEWDICT";
/// Checks whether a dictionary is empty.
@pure
fun dictIsEmpty(self: cell): bool
fun dictIsEmpty(self: cell?): bool
asm "DICTEMPTY";
@pure
fun iDictGet(self: cell, keyLen: int, key: int): (slice, bool)
fun iDictGet(self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTIGET" "NULLSWAPIFNOT";
@pure
fun uDictGet(self: cell, keyLen: int, key: int): (slice, bool)
fun uDictGet(self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTUGET" "NULLSWAPIFNOT";
@pure
fun sDictGet(self: cell, keyLen: int, key: slice): (slice, bool)
fun sDictGet(self: cell?, keyLen: int, key: slice): (slice?, bool)
asm(key self keyLen) "DICTGET" "NULLSWAPIFNOT";
@pure
fun iDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void
fun iDictSet(mutate self: cell?, keyLen: int, key: int, value: slice): void
asm(value key self keyLen) "DICTISET";
@pure
fun uDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void
fun uDictSet(mutate self: cell?, keyLen: int, key: int, value: slice): void
asm(value key self keyLen) "DICTUSET";
@pure
fun sDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): void
fun sDictSet(mutate self: cell?, keyLen: int, key: slice, value: slice): void
asm(value key self keyLen) "DICTSET";
@pure
fun iDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void
fun iDictSetRef(mutate self: cell?, keyLen: int, key: int, value: cell): void
asm(value key self keyLen) "DICTISETREF";
@pure
fun uDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void
fun uDictSetRef(mutate self: cell?, keyLen: int, key: int, value: cell): void
asm(value key self keyLen) "DICTUSETREF";
@pure
fun sDictSetRef(mutate self: cell, keyLen: int, key: slice, value: cell): void
fun sDictSetRef(mutate self: cell?, keyLen: int, key: slice, value: cell): void
asm(value key self keyLen) "DICTSETREF";
@pure
fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun iDictSetIfNotExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTIADD";
@pure
fun uDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun uDictSetIfNotExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTUADD";
@pure
fun iDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun iDictSetIfExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTIREPLACE";
@pure
fun uDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun uDictSetIfExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTUREPLACE";
@pure
fun iDictGetRef(self: cell, keyLen: int, key: int): (cell, bool)
fun iDictGetRef(self: cell?, keyLen: int, key: int): (cell?, bool)
asm(key self keyLen) "DICTIGETREF" "NULLSWAPIFNOT";
@pure
fun uDictGetRef(self: cell, keyLen: int, key: int): (cell, bool)
fun uDictGetRef(self: cell?, keyLen: int, key: int): (cell?, bool)
asm(key self keyLen) "DICTUGETREF" "NULLSWAPIFNOT";
@pure
fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, bool)
fun sDictGetRef(self: cell?, keyLen: int, key: slice): (cell?, bool)
asm(key self keyLen) "DICTGETREF" "NULLSWAPIFNOT";
@pure
fun iDictGetRefOrNull(self: cell, keyLen: int, key: int): cell
fun iDictGetRefOrNull(self: cell?, keyLen: int, key: int): cell?
asm(key self keyLen) "DICTIGETOPTREF";
@pure
fun uDictGetRefOrNull(self: cell, keyLen: int, key: int): cell
fun uDictGetRefOrNull(self: cell?, keyLen: int, key: int): cell?
asm(key self keyLen) "DICTUGETOPTREF";
@pure
fun sDictGetRefOrNull(self: cell, keyLen: int, key: slice): cell
fun sDictGetRefOrNull(self: cell?, keyLen: int, key: slice): cell?
asm(key self keyLen) "DICTGETOPTREF";
@pure
fun iDictDelete(mutate self: cell, keyLen: int, key: int): bool
fun iDictDelete(mutate self: cell?, keyLen: int, key: int): bool
asm(key self keyLen) "DICTIDEL";
@pure
fun uDictDelete(mutate self: cell, keyLen: int, key: int): bool
fun uDictDelete(mutate self: cell?, keyLen: int, key: int): bool
asm(key self keyLen) "DICTUDEL";
@pure
fun sDictDelete(mutate self: cell, keyLen: int, key: slice): bool
fun sDictDelete(mutate self: cell?, keyLen: int, key: slice): bool
asm(key self keyLen) "DICTDEL";
@pure
fun iDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool)
fun iDictSetAndGet(mutate self: cell?, keyLen: int, key: int, value: slice): (slice?, bool)
asm(value key self keyLen) "DICTISETGET" "NULLSWAPIFNOT";
@pure
fun uDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool)
fun uDictSetAndGet(mutate self: cell?, keyLen: int, key: int, value: slice): (slice?, bool)
asm(value key self keyLen) "DICTUSETGET" "NULLSWAPIFNOT";
@pure
fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, bool)
fun sDictSetAndGet(mutate self: cell?, keyLen: int, key: slice, value: slice): (slice?, bool)
asm(value key self keyLen) "DICTSETGET" "NULLSWAPIFNOT";
@pure
fun iDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell
fun iDictSetAndGetRefOrNull(mutate self: cell?, keyLen: int, key: int, value: cell): cell?
asm(value key self keyLen) "DICTISETGETOPTREF";
@pure
fun uDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell
fun uDictSetAndGetRefOrNull(mutate self: cell?, keyLen: int, key: int, value: cell): cell?
asm(value key self keyLen) "DICTUSETGETOPTREF";
@pure
fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool)
fun iDictDeleteAndGet(mutate self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTIDELGET" "NULLSWAPIFNOT";
@pure
fun uDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool)
fun uDictDeleteAndGet(mutate self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTUDELGET" "NULLSWAPIFNOT";
@pure
fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, bool)
fun sDictDeleteAndGet(mutate self: cell?, keyLen: int, key: slice): (slice?, bool)
asm(key self keyLen) "DICTDELGET" "NULLSWAPIFNOT";
@pure
fun iDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void
fun iDictSetBuilder(mutate self: cell?, keyLen: int, key: int, value: builder): void
asm(value key self keyLen) "DICTISETB";
@pure
fun uDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void
fun uDictSetBuilder(mutate self: cell?, keyLen: int, key: int, value: builder): void
asm(value key self keyLen) "DICTUSETB";
@pure
fun sDictSetBuilder(mutate self: cell, keyLen: int, key: slice, value: builder): void
fun sDictSetBuilder(mutate self: cell?, keyLen: int, key: slice, value: builder): void
asm(value key self keyLen) "DICTSETB";
@pure
fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun iDictSetBuilderIfNotExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTIADDB";
@pure
fun uDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun uDictSetBuilderIfNotExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTUADDB";
@pure
fun iDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun iDictSetBuilderIfExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTIREPLACEB";
@pure
fun uDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun uDictSetBuilderIfExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTUREPLACEB";
@pure
fun iDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun iDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
@pure
fun uDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun uDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
@pure
fun sDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (slice, slice, bool)
fun sDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (slice?, slice?, bool)
asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
@pure
fun iDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun iDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
@pure
fun uDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun uDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
@pure
fun sDictDeleteLastAndGet(mutate self: cell, keyLen: int): (slice, slice, bool)
fun sDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (slice?, slice?, bool)
asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
@pure
fun iDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
fun iDictGetFirst(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
@pure
fun uDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
fun uDictGetFirst(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
@pure
fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, bool)
fun sDictGetFirst(self: cell?, keyLen: int): (slice?, slice?, bool)
asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2";
@pure
fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
fun iDictGetFirstAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
@pure
fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
fun uDictGetFirstAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
@pure
fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, bool)
fun sDictGetFirstAsRef(self: cell?, keyLen: int): (slice?, cell?, bool)
asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2";
@pure
fun iDictGetLast(self: cell, keyLen: int): (int, slice, bool)
fun iDictGetLast(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
@pure
fun uDictGetLast(self: cell, keyLen: int): (int, slice, bool)
fun uDictGetLast(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
@pure
fun sDictGetLast(self: cell, keyLen: int): (slice, slice, bool)
fun sDictGetLast(self: cell?, keyLen: int): (slice?, slice?, bool)
asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2";
@pure
fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
fun iDictGetLastAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
@pure
fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
fun uDictGetLastAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
@pure
fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, bool)
fun sDictGetLastAsRef(self: cell?, keyLen: int): (slice?, cell?, bool)
asm (-> 1 0 2) "DICTMAXREF" "NULLSWAPIFNOT2";
@pure
fun iDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetNext(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2";
@pure
fun uDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetNext(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2";
@pure
fun iDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetNextOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2";
@pure
fun uDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetNextOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2";
@pure
fun iDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetPrev(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2";
@pure
fun uDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetPrev(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2";
@pure
fun iDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetPrevOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2";
@pure
fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetPrevOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
@ -299,13 +300,13 @@ fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
*/
@pure
fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, bool)
fun prefixDictGet(self: cell?, keyLen: int, key: slice): (slice, slice?, slice?, bool)
asm(key self keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2";
@pure
fun prefixDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): bool
fun prefixDictSet(mutate self: cell?, keyLen: int, key: slice, value: slice): bool
asm(value key self keyLen) "PFXDICTSET";
@pure
fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): bool
fun prefixDictDelete(mutate self: cell?, keyLen: int, key: slice): bool
asm(key self keyLen) "PFXDICTDEL";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -402,7 +402,7 @@ void CodeBlob::print(std::ostream& os, int flags) const {
std::vector<var_idx_t> CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name) {
std::vector<var_idx_t> ir_idx;
int stack_w = var_type->calc_width_on_stack();
int stack_w = var_type->get_width_on_stack();
ir_idx.reserve(stack_w);
if (const TypeDataTensor* t_tensor = var_type->try_as<TypeDataTensor>()) {
for (int i = 0; i < t_tensor->size(); ++i) {
@ -410,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);

View file

@ -111,23 +111,16 @@ static void diagnose_addition_in_bitshift(SrcLocation loc, std::string_view bits
}
}
// replace (a == null) and similar to isNull(a) (call of a built-in function)
static AnyExprV maybe_replace_eq_null_with_isNull_call(V<ast_binary_operator> v) {
// replace (a == null) and similar to ast_is_null_check(a) (special AST vertex)
static AnyExprV maybe_replace_eq_null_with_isNull_check(V<ast_binary_operator> v) {
bool has_null = v->get_lhs()->type == ast_null_keyword || v->get_rhs()->type == ast_null_keyword;
bool replace = has_null && (v->tok == tok_eq || v->tok == tok_neq);
if (!replace) {
return v;
}
auto v_ident = createV<ast_identifier>(v->loc, "__isNull"); // built-in function
auto v_ref = createV<ast_reference>(v->loc, v_ident, nullptr);
AnyExprV v_null = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs();
AnyExprV v_arg = createV<ast_argument>(v->loc, v_null, false);
AnyExprV v_isNull = createV<ast_function_call>(v->loc, v_ref, createV<ast_argument_list>(v->loc, {v_arg}));
if (v->tok == tok_neq) {
v_isNull = createV<ast_unary_operator>(v->loc, "!", tok_logical_not, v_isNull);
}
return v_isNull;
AnyExprV v_nullable = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs();
return createV<ast_is_null_check>(v->loc, v_nullable, v->tok == tok_neq);
}
@ -372,16 +365,31 @@ static AnyExprV parse_expr100(Lexer& lex) {
}
}
// parse E(...) (left-to-right)
// parse E(...) and E! having parsed E already (left-to-right)
static AnyExprV parse_fun_call_postfix(Lexer& lex, AnyExprV lhs) {
while (true) {
if (lex.tok() == tok_oppar) {
lhs = createV<ast_function_call>(lhs->loc, lhs, parse_argument_list(lex));
} else if (lex.tok() == tok_logical_not) {
lex.next();
lhs = createV<ast_not_null_operator>(lhs->loc, lhs);
} else {
break;
}
}
return lhs;
}
// parse E(...) and E! (left-to-right)
static AnyExprV parse_expr90(Lexer& lex) {
AnyExprV res = parse_expr100(lex);
while (lex.tok() == tok_oppar) {
res = createV<ast_function_call>(res->loc, res, parse_argument_list(lex));
if (lex.tok() == tok_oppar || lex.tok() == tok_logical_not) {
res = parse_fun_call_postfix(lex, res);
}
return res;
}
// parse E.field and E.method(...) (left-to-right)
// parse E.field and E.method(...) and E.field! (left-to-right)
static AnyExprV parse_expr80(Lexer& lex) {
AnyExprV lhs = parse_expr90(lex);
while (lex.tok() == tok_dot) {
@ -402,8 +410,8 @@ static AnyExprV parse_expr80(Lexer& lex) {
lex.unexpected("method name");
}
lhs = createV<ast_dot_access>(loc, lhs, v_ident, v_instantiationTs);
while (lex.tok() == tok_oppar) {
lhs = createV<ast_function_call>(lex.cur_location(), lhs, parse_argument_list(lex));
if (lex.tok() == tok_oppar || lex.tok() == tok_logical_not) {
lhs = parse_fun_call_postfix(lex, lhs);
}
}
return lhs;
@ -491,7 +499,7 @@ static AnyExprV parse_expr15(Lexer& lex) {
AnyExprV rhs = parse_expr17(lex);
lhs = createV<ast_binary_operator>(loc, operator_name, t, lhs, rhs);
if (t == tok_eq || t == tok_neq) {
lhs = maybe_replace_eq_null_with_isNull_call(lhs->as<ast_binary_operator>());
lhs = maybe_replace_eq_null_with_isNull_check(lhs->as<ast_binary_operator>());
}
}
return lhs;

View file

@ -108,6 +108,8 @@ protected:
virtual AnyExprV replace(V<ast_binary_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_ternary_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_cast_as_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_not_null_operator> v) { return replace_children(v); }
virtual AnyExprV replace(V<ast_is_null_check> v) { return replace_children(v); }
// statements
virtual AnyV replace(V<ast_empty_statement> v) { return replace_children(v); }
virtual AnyV replace(V<ast_sequence> v) { return replace_children(v); }
@ -144,6 +146,8 @@ protected:
case ast_binary_operator: return replace(v->as<ast_binary_operator>());
case ast_ternary_operator: return replace(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return replace(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return replace(v->as<ast_not_null_operator>());
case ast_is_null_check: return replace(v->as<ast_is_null_check>());
default:
throw UnexpectedASTNodeType(v, "ASTReplacerInFunctionBody::replace");
}
@ -174,20 +178,20 @@ protected:
}
public:
virtual bool should_visit_function(const FunctionData* fun_ref) = 0;
virtual bool should_visit_function(FunctionPtr fun_ref) = 0;
void start_replacing_in_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
void start_replacing_in_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
replace(v_function->get_body());
}
};
const std::vector<const FunctionData*>& get_all_not_builtin_functions();
const std::vector<FunctionPtr>& get_all_not_builtin_functions();
template<class BodyReplacerT>
void replace_ast_of_all_functions() {
BodyReplacerT visitor;
for (const FunctionData* fun_ref : get_all_not_builtin_functions()) {
for (FunctionPtr fun_ref : get_all_not_builtin_functions()) {
if (visitor.should_visit_function(fun_ref)) {
visitor.start_replacing_in_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
}

View file

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

View file

@ -56,6 +56,8 @@ class ASTStringifier final : public ASTVisitor {
{ast_binary_operator, "ast_binary_operator"},
{ast_ternary_operator, "ast_ternary_operator"},
{ast_cast_as_operator, "ast_cast_as_operator"},
{ast_not_null_operator, "ast_not_null_operator"},
{ast_is_null_check, "ast_is_null_check"},
// statements
{ast_empty_statement, "ast_empty_statement"},
{ast_sequence, "ast_sequence"},
@ -268,6 +270,8 @@ public:
case ast_binary_operator: return handle_vertex(v->as<ast_binary_operator>());
case ast_ternary_operator: return handle_vertex(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return handle_vertex(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return handle_vertex(v->as<ast_not_null_operator>());
case ast_is_null_check: return handle_vertex(v->as<ast_is_null_check>());
// statements
case ast_empty_statement: return handle_vertex(v->as<ast_empty_statement>());
case ast_sequence: return handle_vertex(v->as<ast_sequence>());

View file

@ -109,6 +109,8 @@ protected:
virtual void visit(V<ast_binary_operator> v) { return visit_children(v); }
virtual void visit(V<ast_ternary_operator> v) { return visit_children(v); }
virtual void visit(V<ast_cast_as_operator> v) { return visit_children(v); }
virtual void visit(V<ast_not_null_operator> v) { return visit_children(v); }
virtual void visit(V<ast_is_null_check> v) { return visit_children(v); }
// statements
virtual void visit(V<ast_empty_statement> v) { return visit_children(v); }
virtual void visit(V<ast_sequence> v) { return visit_children(v); }
@ -146,6 +148,8 @@ protected:
case ast_binary_operator: return visit(v->as<ast_binary_operator>());
case ast_ternary_operator: return visit(v->as<ast_ternary_operator>());
case ast_cast_as_operator: return visit(v->as<ast_cast_as_operator>());
case ast_not_null_operator: return visit(v->as<ast_not_null_operator>());
case ast_is_null_check: return visit(v->as<ast_is_null_check>());
// statements
case ast_empty_statement: return visit(v->as<ast_empty_statement>());
case ast_sequence: return visit(v->as<ast_sequence>());
@ -167,20 +171,20 @@ protected:
}
public:
virtual bool should_visit_function(const FunctionData* fun_ref) = 0;
virtual bool should_visit_function(FunctionPtr fun_ref) = 0;
virtual void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
virtual void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
visit(v_function->get_body());
}
};
const std::vector<const FunctionData*>& get_all_not_builtin_functions();
const std::vector<FunctionPtr>& get_all_not_builtin_functions();
template<class BodyVisitorT>
void visit_ast_of_all_functions() {
BodyVisitorT visitor;
for (const FunctionData* fun_ref : get_all_not_builtin_functions()) {
for (FunctionPtr fun_ref : get_all_not_builtin_functions()) {
if (visitor.should_visit_function(fun_ref)) {
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
}

View file

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

View file

@ -88,6 +88,8 @@ enum ASTNodeType {
ast_binary_operator,
ast_ternary_operator,
ast_cast_as_operator,
ast_not_null_operator,
ast_is_null_check,
// statements
ast_empty_statement,
ast_sequence,
@ -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)

View file

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

View file

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

View file

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

View file

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

View file

@ -32,6 +32,11 @@ struct FunctionData;
struct GlobalVarData;
struct GlobalConstData;
using LocalVarPtr = const LocalVarData*;
using FunctionPtr = const FunctionData*;
using GlobalVarPtr = const GlobalVarData*;
using GlobalConstPtr = const GlobalConstData*;
class TypeData;
using TypePtr = const TypeData*;

View file

@ -37,12 +37,38 @@ static TypePtr replace_genericT_with_deduced(TypePtr orig, const GenericsDeclara
if (idx == -1) {
throw Fatal("can not replace generic " + asT->nameT);
}
if (substitutionTs[idx] == nullptr) {
throw GenericDeduceError("can not deduce " + asT->nameT);
}
return substitutionTs[idx];
}
return child;
});
}
GenericSubstitutionsDeduceForCall::GenericSubstitutionsDeduceForCall(FunctionPtr fun_ref)
: fun_ref(fun_ref) {
substitutionTs.resize(fun_ref->genericTs->size()); // filled with nullptr (nothing deduced)
}
void GenericSubstitutionsDeduceForCall::provide_deducedT(const std::string& nameT, TypePtr deduced) {
if (deduced == TypeDataNullLiteral::create() || deduced->has_unknown_inside()) {
return; // just 'null' doesn't give sensible info
}
int idx = fun_ref->genericTs->find_nameT(nameT);
if (substitutionTs[idx] == nullptr) {
substitutionTs[idx] = deduced;
} else if (substitutionTs[idx] != deduced) {
throw GenericDeduceError(nameT + " is both " + substitutionTs[idx]->as_human_readable() + " and " + deduced->as_human_readable());
}
}
void GenericSubstitutionsDeduceForCall::provide_manually_specified(std::vector<TypePtr>&& substitutionTs) {
this->substitutionTs = std::move(substitutionTs);
this->manually_specified = true;
}
// purpose: having `f<T>(value: T)` and call `f(5)`, deduce T = int
// generally, there may be many generic Ts for declaration, and many arguments
// for every argument, `consider_next_condition()` is called
@ -51,71 +77,67 @@ static TypePtr replace_genericT_with_deduced(TypePtr orig, const GenericsDeclara
// - next condition: param_type = `T1`, arg_type = `int`, deduce T1 = int
// - next condition: param_type = `(T1, T2)`, arg_type = `(int, slice)`, deduce T1 = int, T2 = slice
// for call `f(6, cs, (8, cs))` T1 will be both `slice` and `int`, fired an error
class GenericSubstitutionsDeduceForFunctionCall final {
const FunctionData* fun_ref;
std::vector<TypePtr> substitutions;
void provideDeducedT(const std::string& nameT, TypePtr deduced) {
if (deduced == TypeDataNullLiteral::create() || deduced->has_unknown_inside()) {
return; // just 'null' doesn't give sensible info
void GenericSubstitutionsDeduceForCall::consider_next_condition(TypePtr param_type, TypePtr arg_type) {
if (const auto* asT = param_type->try_as<TypeDataGenericT>()) {
// `(arg: T)` called as `f([1, 2])` => T is [int, int]
provide_deducedT(asT->nameT, arg_type);
} else if (const auto* p_nullable = param_type->try_as<TypeDataNullable>()) {
// `arg: T?` called as `f(nullableInt)` => T is int
if (const auto* a_nullable = arg_type->try_as<TypeDataNullable>()) {
consider_next_condition(p_nullable->inner, a_nullable->inner);
}
int idx = fun_ref->genericTs->find_nameT(nameT);
if (substitutions[idx] == nullptr) {
substitutions[idx] = deduced;
} else if (substitutions[idx] != deduced) {
throw std::runtime_error(nameT + " is both " + substitutions[idx]->as_human_readable() + " and " + deduced->as_human_readable());
// `arg: T?` called as `f(int)` => T is int
else {
consider_next_condition(p_nullable->inner, arg_type);
}
}
public:
explicit GenericSubstitutionsDeduceForFunctionCall(const FunctionData* fun_ref)
: fun_ref(fun_ref) {
substitutions.resize(fun_ref->genericTs->size()); // filled with nullptr (nothing deduced)
}
void consider_next_condition(TypePtr param_type, TypePtr arg_type) {
if (const auto* asT = param_type->try_as<TypeDataGenericT>()) {
// `(arg: T)` called as `f([1, 2])` => T is [int, int]
provideDeducedT(asT->nameT, arg_type);
} else if (const auto* p_tensor = param_type->try_as<TypeDataTensor>()) {
// `arg: (int, T)` called as `f((5, cs))` => T is slice
if (const auto* a_tensor = arg_type->try_as<TypeDataTensor>(); a_tensor && a_tensor->size() == p_tensor->size()) {
for (int i = 0; i < a_tensor->size(); ++i) {
consider_next_condition(p_tensor->items[i], a_tensor->items[i]);
}
}
} else if (const auto* p_tuple = param_type->try_as<TypeDataTypedTuple>()) {
// `arg: [int, T]` called as `f([5, cs])` => T is slice
if (const auto* a_tuple = arg_type->try_as<TypeDataTypedTuple>(); a_tuple && a_tuple->size() == p_tuple->size()) {
for (int i = 0; i < a_tuple->size(); ++i) {
consider_next_condition(p_tuple->items[i], a_tuple->items[i]);
}
}
} else if (const auto* p_callable = param_type->try_as<TypeDataFunCallable>()) {
// `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int
if (const auto* a_callable = arg_type->try_as<TypeDataFunCallable>(); a_callable && a_callable->params_size() == p_callable->params_size()) {
for (int i = 0; i < a_callable->params_size(); ++i) {
consider_next_condition(p_callable->params_types[i], a_callable->params_types[i]);
}
consider_next_condition(p_callable->return_type, a_callable->return_type);
} else if (const auto* p_tensor = param_type->try_as<TypeDataTensor>()) {
// `arg: (int, T)` called as `f((5, cs))` => T is slice
if (const auto* a_tensor = arg_type->try_as<TypeDataTensor>(); a_tensor && a_tensor->size() == p_tensor->size()) {
for (int i = 0; i < a_tensor->size(); ++i) {
consider_next_condition(p_tensor->items[i], a_tensor->items[i]);
}
}
}
int get_first_not_deduced_idx() const {
for (int i = 0; i < static_cast<int>(substitutions.size()); ++i) {
if (substitutions[i] == nullptr) {
return i;
} else if (const auto* p_tuple = param_type->try_as<TypeDataTypedTuple>()) {
// `arg: [int, T]` called as `f([5, cs])` => T is slice
if (const auto* a_tuple = arg_type->try_as<TypeDataTypedTuple>(); a_tuple && a_tuple->size() == p_tuple->size()) {
for (int i = 0; i < a_tuple->size(); ++i) {
consider_next_condition(p_tuple->items[i], a_tuple->items[i]);
}
}
return -1;
} else if (const auto* p_callable = param_type->try_as<TypeDataFunCallable>()) {
// `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int
if (const auto* a_callable = arg_type->try_as<TypeDataFunCallable>(); a_callable && a_callable->params_size() == p_callable->params_size()) {
for (int i = 0; i < a_callable->params_size(); ++i) {
consider_next_condition(p_callable->params_types[i], a_callable->params_types[i]);
}
consider_next_condition(p_callable->return_type, a_callable->return_type);
}
}
}
std::vector<TypePtr> flush() {
return {std::move(substitutions)};
TypePtr GenericSubstitutionsDeduceForCall::replace_by_manually_specified(TypePtr param_type) const {
return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs);
}
TypePtr GenericSubstitutionsDeduceForCall::auto_deduce_from_argument(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;
}
// 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;
}

View file

@ -57,8 +57,46 @@ struct GenericsInstantiation {
}
};
// this class helps to deduce Ts on the fly
// purpose: having `f<T>(value: T)` and call `f(5)`, deduce T = int
// while analyzing a call, arguments are handled one by one, by `auto_deduce_from_argument()`
// this class also handles manually specified substitutions like `f<int>(5)`
class GenericSubstitutionsDeduceForCall {
FunctionPtr fun_ref;
std::vector<TypePtr> substitutionTs;
bool manually_specified = false;
void provide_deducedT(const std::string& nameT, TypePtr deduced);
void consider_next_condition(TypePtr param_type, TypePtr arg_type);
public:
explicit GenericSubstitutionsDeduceForCall(FunctionPtr fun_ref);
bool is_manually_specified() const {
return manually_specified;
}
void provide_manually_specified(std::vector<TypePtr>&& substitutionTs);
TypePtr replace_by_manually_specified(TypePtr param_type) const;
TypePtr auto_deduce_from_argument(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

View file

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

View file

@ -177,6 +177,18 @@ class CalculateRvalueLvalueVisitor final : public ASTVisitorFunctionBody {
parent::visit(v->get_expr()); // leave lvalue state unchanged, for `mutate (t.0 as int)` both `t.0 as int` and `t.0` are lvalue
}
void visit(V<ast_not_null_operator> v) override {
mark_vertex_cur_or_rvalue(v);
parent::visit(v->get_expr()); // leave lvalue state unchanged, for `mutate x!` both `x!` and `x` are lvalue
}
void visit(V<ast_is_null_check> v) override {
mark_vertex_cur_or_rvalue(v);
MarkingState saved = enter_state(MarkingState::RValue);
parent::visit(v->get_expr());
restore_state(saved);
}
void visit(V<ast_local_var_lhs> v) override {
tolk_assert(cur_state == MarkingState::LValue);
mark_vertex_cur_or_rvalue(v);
@ -198,7 +210,7 @@ class CalculateRvalueLvalueVisitor final : public ASTVisitorFunctionBody {
}
public:
bool should_visit_function(const FunctionData* fun_ref) override {
bool should_visit_function(FunctionPtr fun_ref) override {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
};
@ -207,7 +219,7 @@ void pipeline_calculate_rvalue_lvalue() {
visit_ast_of_all_functions<CalculateRvalueLvalueVisitor>();
}
void pipeline_calculate_rvalue_lvalue(const FunctionData* fun_ref) {
void pipeline_calculate_rvalue_lvalue(FunctionPtr fun_ref) {
CalculateRvalueLvalueVisitor visitor;
if (visitor.should_visit_function(fun_ref)) {
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());

View file

@ -34,7 +34,7 @@ static void fire_error_impure_operation_inside_pure_function(AnyV v) {
class CheckImpureOperationsInPureFunctionVisitor final : public ASTVisitorFunctionBody {
static void fire_if_global_var(AnyExprV v) {
if (auto v_ident = v->try_as<ast_reference>()) {
if (v_ident->sym->try_as<GlobalVarData>()) {
if (v_ident->sym->try_as<GlobalVarPtr>()) {
fire_error_impure_operation_inside_pure_function(v);
}
}
@ -81,7 +81,7 @@ class CheckImpureOperationsInPureFunctionVisitor final : public ASTVisitorFuncti
}
public:
bool should_visit_function(const FunctionData* fun_ref) override {
bool should_visit_function(FunctionPtr fun_ref) override {
return fun_ref->is_code_function() && !fun_ref->is_generic_function() && fun_ref->is_marked_as_pure();
}
};

View file

@ -37,7 +37,7 @@ static void fire_error_cannot_be_used_as_lvalue(AnyV v, const std::string& detai
}
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_modifying_immutable_variable(AnyExprV v, const LocalVarData* var_ref) {
static void fire_error_modifying_immutable_variable(AnyExprV v, LocalVarPtr var_ref) {
if (var_ref->param_idx == 0 && var_ref->name == "self") {
v->error("modifying `self`, which is immutable by default; probably, you want to declare `mutate self`");
} else {
@ -47,7 +47,7 @@ static void fire_error_modifying_immutable_variable(AnyExprV v, const LocalVarDa
// validate a function used as rvalue, like `var cb = f`
// it's not a generic function (ensured earlier at type inferring) and has some more restrictions
static void validate_function_used_as_noncall(AnyExprV v, const FunctionData* fun_ref) {
static void validate_function_used_as_noncall(AnyExprV v, FunctionPtr fun_ref) {
if (!fun_ref->arg_order.empty() || !fun_ref->ret_order.empty()) {
v->error("saving `" + fun_ref->name + "` into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack");
}
@ -97,6 +97,18 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody {
parent::visit(v->get_expr());
}
void visit(V<ast_not_null_operator> v) override {
// if `x!` is lvalue, then `x` is also lvalue, so check that `x` is ok
parent::visit(v->get_expr());
}
void visit(V<ast_is_null_check> v) override {
if (v->is_lvalue) {
fire_error_cannot_be_used_as_lvalue(v, v->is_negated ? "operator !=" : "operator ==");
}
parent::visit(v->get_expr());
}
void visit(V<ast_int_const> v) override {
if (v->is_lvalue) {
fire_error_cannot_be_used_as_lvalue(v, "literal");
@ -124,7 +136,7 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody {
void visit(V<ast_dot_access> v) override {
// a reference to a method used as rvalue, like `var v = t.tupleAt`
if (v->is_rvalue && v->is_target_fun_ref()) {
validate_function_used_as_noncall(v, std::get<const FunctionData*>(v->target));
validate_function_used_as_noncall(v, std::get<FunctionPtr>(v->target));
}
}
@ -158,17 +170,17 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody {
void visit(V<ast_reference> v) override {
if (v->is_lvalue) {
tolk_assert(v->sym);
if (const auto* var_ref = v->sym->try_as<LocalVarData>(); var_ref && var_ref->is_immutable()) {
if (LocalVarPtr var_ref = v->sym->try_as<LocalVarPtr>(); var_ref && var_ref->is_immutable()) {
fire_error_modifying_immutable_variable(v, var_ref);
} else if (v->sym->try_as<GlobalConstData>()) {
} else if (v->sym->try_as<GlobalConstPtr>()) {
v->error("modifying immutable constant");
} else if (v->sym->try_as<FunctionData>()) {
} else if (v->sym->try_as<FunctionPtr>()) {
v->error("function can't be used as lvalue");
}
}
// a reference to a function used as rvalue, like `var v = someFunction`
if (const FunctionData* fun_ref = v->sym->try_as<FunctionData>(); fun_ref && v->is_rvalue) {
if (FunctionPtr fun_ref = v->sym->try_as<FunctionPtr>(); fun_ref && v->is_rvalue) {
validate_function_used_as_noncall(v, fun_ref);
}
}
@ -186,7 +198,7 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody {
}
public:
bool should_visit_function(const FunctionData* fun_ref) override {
bool should_visit_function(FunctionPtr fun_ref) override {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
};

View file

@ -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();
}
};

View file

@ -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>());

View file

@ -36,7 +36,7 @@ namespace tolk {
static void mark_function_used_dfs(const std::unique_ptr<Op>& op);
static void mark_function_used(const FunctionData* fun_ref) {
static void mark_function_used(FunctionPtr fun_ref) {
if (!fun_ref->is_code_function() || fun_ref->is_really_used()) { // already handled
return;
}
@ -45,7 +45,7 @@ static void mark_function_used(const FunctionData* fun_ref) {
mark_function_used_dfs(std::get<FunctionBodyCode*>(fun_ref->body)->code->ops);
}
static void mark_global_var_used(const GlobalVarData* glob_ref) {
static void mark_global_var_used(GlobalVarPtr glob_ref) {
glob_ref->mutate()->assign_is_really_used();
}
@ -66,7 +66,7 @@ static void mark_function_used_dfs(const std::unique_ptr<Op>& op) {
}
void pipeline_find_unused_symbols() {
for (const FunctionData* fun_ref : G.all_functions) {
for (FunctionPtr fun_ref : G.all_functions) {
if (fun_ref->is_method_id_not_empty()) { // get methods, main and other entrypoints, regular functions with @method_id
mark_function_used(fun_ref);
}

View file

@ -39,7 +39,7 @@ void FunctionBodyAsm::set_code(std::vector<AsmOp>&& code) {
}
static void generate_output_func(const FunctionData* fun_ref) {
static void generate_output_func(FunctionPtr fun_ref) {
tolk_assert(fun_ref->is_code_function());
if (G.is_verbosity(2)) {
std::cerr << "\n\n=========================\nfunction " << fun_ref->name << " : " << fun_ref->inferred_return_type << std::endl;
@ -119,7 +119,7 @@ void pipeline_generate_fif_output_to_std_cout() {
std::cout << "PROGRAM{\n";
bool has_main_procedure = false;
for (const FunctionData* fun_ref : G.all_functions) {
for (FunctionPtr fun_ref : G.all_functions) {
if (!fun_ref->does_need_codegen()) {
if (G.is_verbosity(2) && fun_ref->is_code_function()) {
std::cerr << fun_ref->name << ": code not generated, function does not need codegen\n";
@ -143,7 +143,7 @@ void pipeline_generate_fif_output_to_std_cout() {
throw Fatal("the contract has no entrypoint; forgot `fun onInternalMessage(...)`?");
}
for (const GlobalVarData* var_ref : G.all_global_vars) {
for (GlobalVarPtr var_ref : G.all_global_vars) {
if (!var_ref->is_really_used() && G.settings.remove_unused_functions) {
if (G.is_verbosity(2)) {
std::cerr << var_ref->name << ": variable not generated, it's unused\n";
@ -154,7 +154,7 @@ void pipeline_generate_fif_output_to_std_cout() {
std::cout << std::string(2, ' ') << "DECLGLOBVAR " << var_ref->name << "\n";
}
for (const FunctionData* fun_ref : G.all_functions) {
for (FunctionPtr fun_ref : G.all_functions) {
if (!fun_ref->does_need_codegen()) {
continue;
}

View file

@ -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>());
}

View file

@ -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();
}
};

View file

@ -34,7 +34,7 @@
namespace tolk {
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_invalid_mutate_arg_passed(AnyExprV v, const FunctionData* fun_ref, const LocalVarData& p_sym, bool called_as_method, bool arg_passed_as_mutate, AnyV arg_expr) {
static void fire_error_invalid_mutate_arg_passed(AnyExprV v, FunctionPtr fun_ref, const LocalVarData& p_sym, bool called_as_method, bool arg_passed_as_mutate, AnyV arg_expr) {
std::string arg_str(arg_expr->type == ast_reference ? arg_expr->as<ast_reference>()->get_name() : "obj");
// case: `loadInt(cs, 32)`; suggest: `cs.loadInt(32)`
@ -60,7 +60,7 @@ static void fire_error_invalid_mutate_arg_passed(AnyExprV v, const FunctionData*
class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBody {
void visit(V<ast_function_call> v) override {
// v is `globalF(args)` / `globalF<int>(args)` / `obj.method(args)` / `local_var(args)` / `getF()(args)`
const FunctionData* fun_ref = v->fun_maybe;
FunctionPtr fun_ref = v->fun_maybe;
if (!fun_ref) {
parent::visit(v);
for (int i = 0; i < v->get_num_args(); ++i) {
@ -86,6 +86,8 @@ class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBod
leftmost_obj = as_par->get_expr();
} else if (auto as_cast = leftmost_obj->try_as<ast_cast_as_operator>()) {
leftmost_obj = as_cast->get_expr();
} else if (auto as_nn = leftmost_obj->try_as<ast_not_null_operator>()) {
leftmost_obj = as_nn->get_expr();
} else {
break;
}
@ -114,7 +116,7 @@ class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBod
}
public:
bool should_visit_function(const FunctionData* fun_ref) override {
bool should_visit_function(FunctionPtr fun_ref) override {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
};

View file

@ -176,8 +176,8 @@ static void register_function(V<ast_function_declaration> v) {
genericTs = construct_genericTs(v->genericsT_list);
}
if (v->is_builtin_function()) {
const Symbol* builtin_func = lookup_global_symbol(func_name);
const FunctionData* fun_ref = builtin_func ? builtin_func->as<FunctionData>() : nullptr;
const Symbol* sym = lookup_global_symbol(func_name);
FunctionPtr fun_ref = sym ? sym->try_as<FunctionPtr>() : nullptr;
if (!fun_ref || !fun_ref->is_builtin_function()) {
v->error("`builtin` used for non-builtin function");
}
@ -202,7 +202,7 @@ static void register_function(V<ast_function_declaration> v) {
f_sym->method_id = static_cast<int>(v->method_id->to_long());
} else if (v->flags & FunctionData::flagGetMethod) {
f_sym->method_id = calculate_method_id_by_func_name(func_name);
for (const FunctionData* other : G.all_get_methods) {
for (FunctionPtr other : G.all_get_methods) {
if (other->method_id == f_sym->method_id) {
v->error(PSTRING() << "GET methods hash collision: `" << other->name << "` and `" << f_sym->name << "` produce the same hash. Consider renaming one of these functions.");
}

View file

@ -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>());

View file

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

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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