1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 11:12:16 +00:00

[Tolk] bool type (-1/0 int under the hood)

Comparison operators `== / >= /...` return `bool`.
Logical operators `&& ||` return bool.
Constants `true` and `false` have the `bool` type.
Lots of stdlib functions return `bool`, not `int`.

Operator `!x` supports both `int` and `bool`.
Condition of `if` accepts both `int` and `bool`.
Arithmetic operators are restricted to integers.
Logical `&&` and `||` accept both `bool` and `int`.

No arithmetic operations with bools allowed (only bitwise and logical).
This commit is contained in:
tolk-vm 2025-01-13 15:21:24 +07:00
parent 799e2d1265
commit 974d76c5f6
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
33 changed files with 764 additions and 212 deletions

View file

@ -205,7 +205,7 @@ fun stringHash(s: slice): int
/// That is, if [hash] is computed as the hash of some data, these data are hashed twice,
/// the second hashing occurring inside `CHKSIGNS`.
@pure
fun isSignatureValid(hash: int, signature: slice, publicKey: int): int
fun isSignatureValid(hash: int, signature: slice, publicKey: int): bool
asm "CHKSIGNU";
/// Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `publicKey`,
@ -214,7 +214,7 @@ fun isSignatureValid(hash: int, signature: slice, publicKey: int): int
/// The verification of Ed25519 signatures is the standard one,
/// with sha256 used to reduce [data] to the 256-bit number that is actually signed.
@pure
fun isSliceSignatureValid(data: slice, signature: slice, publicKey: int): int
fun isSliceSignatureValid(data: slice, signature: slice, publicKey: int): bool
asm "CHKSIGNS";
/// Generates a new pseudo-random unsigned 256-bit integer x.
@ -259,14 +259,14 @@ fun randomizeByLogicalTime(): void
/// otherwise the computation is aborted before visiting the `(maxCells + 1)`-st cell and
/// a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`.
@pure
fun calculateCellSize(c: cell, maxCells: int): (int, int, int, int)
fun calculateCellSize(c: cell, maxCells: int): (int, int, int, bool)
asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
/// Similar to [calculateCellSize], but accepting a `slice` [s] instead of a `cell`.
/// The returned value of `x` does not take into account the cell that contains the `slice` [s] itself;
/// however, the data bits and the cell references of [s] are accounted for in `y` and `z`.
@pure
fun calculateSliceSize(s: slice, maxCells: int): (int, int, int, int)
fun calculateSliceSize(s: slice, maxCells: int): (int, int, int, bool)
asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
/// A non-quiet version of [calculateCellSize] that throws a cell overflow exception (`8`) on failure.
@ -382,7 +382,7 @@ fun loadCoins(mutate self: slice): int
/// Loads bool (-1 or 0) from a slice
@pure
fun loadBool(mutate self: slice): int
fun loadBool(mutate self: slice): bool
asm( -> 1 0) "1 LDI";
/// Shifts a slice pointer to [len] bits forward, mutating the slice.
@ -482,7 +482,7 @@ fun storeCoins(mutate self: builder, x: int): self
/// Stores bool (-1 or 0) into a builder.
/// Attention: true value is `-1`, not 1! If you pass `1` here, TVM will throw an exception.
@pure
fun storeBool(mutate self: builder, x: int): self
fun storeBool(mutate self: builder, x: bool): self
asm(x self) "1 STI";
/// Stores dictionary (represented by TVM `cell` or `null`) into a builder.
@ -529,22 +529,22 @@ fun getRemainingBitsAndRefsCount(self: slice): (int, int)
/// Checks whether a slice is empty (i.e., contains no bits of data and no cell references).
@pure
fun isEndOfSlice(self: slice): int
fun isEndOfSlice(self: slice): bool
asm "SEMPTY";
/// Checks whether a slice has no bits of data.
@pure
fun isEndOfSliceBits(self: slice): int
fun isEndOfSliceBits(self: slice): bool
asm "SDEMPTY";
/// Checks whether a slice has no references.
@pure
fun isEndOfSliceRefs(self: slice): int
fun isEndOfSliceRefs(self: slice): bool
asm "SREMPTY";
/// Checks whether data parts of two slices coinside.
@pure
fun isSliceBitsEqual(self: slice, b: slice): int
fun isSliceBitsEqual(self: slice, b: slice): bool
asm "SDEQ";
/// Returns the number of cell references already stored in a builder.
@ -621,10 +621,10 @@ fun parseStandardAddress(s: slice): (int, int)
fun createAddressNone(): slice
asm "b{00} PUSHSLICE";
/// Returns if a slice pointer contains an empty address (`-1` for true, `0` for false, as always).
/// Returns if a slice pointer contains an empty address.
/// In other words, a slice starts with two `0` bits (TL addr_none$00).
@pure
fun addressIsNone(s: slice): int
fun addressIsNone(s: slice): bool
asm "2 PLDU" "0 EQINT";
@ -677,8 +677,8 @@ fun loadMessageFlags(mutate self: slice): int
/// Having msgFlags (4 bits), check that a message is bounced.
/// Effectively, it's `msgFlags & 1` (the lowest bit present).
@pure
fun isMessageBounced(msgFlags: int): int
asm "1 PUSHINT" "AND";
fun isMessageBounced(msgFlags: int): bool
asm "2 PUSHINT" "MODR";
/// Skip 0xFFFFFFFF prefix (when a message is bounced).
@pure

View file

@ -19,20 +19,20 @@ fun createEmptyDict(): cell
/// Checks whether a dictionary is empty.
@pure
fun dictIsEmpty(self: cell): int
fun dictIsEmpty(self: cell): bool
asm "DICTEMPTY";
@pure
fun iDictGet(self: cell, keyLen: int, key: int): (slice, int)
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, int)
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, int)
fun sDictGet(self: cell, keyLen: int, key: slice): (slice, bool)
asm(key self keyLen) "DICTGET" "NULLSWAPIFNOT";
@ -63,33 +63,33 @@ fun sDictSetRef(mutate self: cell, keyLen: int, key: slice, value: cell): void
@pure
fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): int
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): int
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): int
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): int
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, int)
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, int)
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, int)
fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, bool)
asm(key self keyLen) "DICTGETREF" "NULLSWAPIFNOT";
@ -107,28 +107,28 @@ fun sDictGetRefOrNull(self: cell, keyLen: int, key: slice): cell
@pure
fun iDictDelete(mutate self: cell, keyLen: int, key: int): int
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): int
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): int
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, int)
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, int)
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, int)
fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, bool)
asm(value key self keyLen) "DICTSETGET" "NULLSWAPIFNOT";
@ -142,15 +142,15 @@ fun uDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cel
@pure
fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, int)
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, int)
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, int)
fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, bool)
asm(key self keyLen) "DICTDELGET" "NULLSWAPIFNOT";
@ -168,129 +168,129 @@ fun sDictSetBuilder(mutate self: cell, keyLen: int, key: slice, value: builder):
@pure
fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): int
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): int
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): int
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): int
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, int)
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, int)
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, int)
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, int)
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, int)
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, int)
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, int)
fun iDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
@pure
fun uDictGetFirst(self: cell, keyLen: int): (int, slice, int)
fun uDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
@pure
fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, int)
fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, bool)
asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2";
@pure
fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, int)
fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
@pure
fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, int)
fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
@pure
fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, int)
fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, bool)
asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2";
@pure
fun iDictGetLast(self: cell, keyLen: int): (int, slice, int)
fun iDictGetLast(self: cell, keyLen: int): (int, slice, bool)
asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
@pure
fun uDictGetLast(self: cell, keyLen: int): (int, slice, int)
fun uDictGetLast(self: cell, keyLen: int): (int, slice, bool)
asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
@pure
fun sDictGetLast(self: cell, keyLen: int): (slice, slice, int)
fun sDictGetLast(self: cell, keyLen: int): (slice, slice, bool)
asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2";
@pure
fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, int)
fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
@pure
fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, int)
fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
@pure
fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, int)
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, int)
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, int)
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, int)
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, int)
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, int)
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, int)
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, int)
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, int)
fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
@ -299,13 +299,13 @@ fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int)
*/
@pure
fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, int)
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): int
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): int
fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): bool
asm(key self keyLen) "PFXDICTDEL";

View file

@ -83,7 +83,7 @@ fun test_if_else(x: int): (int, int, int, int, int) {
return (x.`~inc`(8), x + 1, x = 1, x <<= 3, x);
} else {
xx = 9;
return (x, x.`~inc`(-4), x.`~inc`(-1), x >= 1, x = x + xx);
return (x, x.`~inc`(-4), x.`~inc`(-1), (x >= 1) as int, x = x + xx);
}
}

View file

@ -1,20 +1,20 @@
fun lshift(): int {
fun lshift(): bool {
return (1 << 0) == 1;
}
fun rshift(): int {
fun rshift(): bool {
return (1 >> 0) == 1;
}
fun lshift_var(i: int): int {
fun lshift_var(i: int): bool {
return (1 << i) == 1;
}
fun rshift_var(i: int): int {
fun rshift_var(i: int): bool {
return (1 >> i) == 1;
}
fun main(x: int): int {
fun main(x: int): bool {
if (x == 0) {
return lshift();
} else if (x == 1) {
@ -31,12 +31,71 @@ fun main(x: int): int {
}
@method_id(11)
fun is_claimed(index: int): int {
fun is_claimed(index: int): bool {
var claim_bit_index: int = index % 256;
var mask: int = 1 << claim_bit_index;
return (255 & mask) == mask;
}
@method_id(12)
fun bit_not(i: int, b: bool): (int, bool, bool, bool, int, bool) {
var i2 = ~i;
var b2 = !b;
var (i3: int, b3: bool) = (i2, b2);
return (i3, b3, !i, !b, ~~~i, !!!b);
}
@method_id(13)
fun boolWithBitwiseConst() {
var found = true;
return (found & false, found | true, found ^ true, found & found);
}
global g14: int;
fun getBool() { return (g14 += 1) > 2; }
@method_id(14)
fun boolWithBitwise(b: bool) {
g14 = 0;
return (b & getBool(), !b & getBool(), b | getBool(), !b | getBool(), b ^ getBool(), !b & getBool(), g14);
}
@method_id(15)
fun boolWithBitwiseSet(b1: bool, b2: bool) {
b1 &= b2;
b2 |= true;
b1 |= b1 == false;
b2 ^= (b1 ^= b2);
return (b1, b2);
}
@method_id(16)
fun testDoUntilCodegen(i: bool, n: int) {
var cnt = 0;
do { cnt += 1; } while (i);
do { cnt += 1; } while (!!i);
do { cnt += 1; } while (n);
return (cnt, !i, !n);
}
@method_id(17)
fun testConstNegateCodegen() {
return (!0, !1, !true, !false, !!true, !!false);
}
@method_id(18)
fun testBoolNegateOptimized(x: bool) {
return (x, !x, !!x, !!!x, !!!!true);
}
fun eqX(x: bool) { return x; }
@method_id(19)
fun testBoolCompareOptimized(x: bool) {
return (x == true, x != true, eqX(x) == false, eqX(x) != false, !!(x == !false));
}
/**
method_id | in | out
@ -50,4 +109,96 @@ fun is_claimed(index: int): int {
@testcase | 11 | 1 | -1
@testcase | 11 | 256 | -1
@testcase | 11 | 8 | 0
@testcase | 12 | 0 0 | -1 -1 -1 -1 -1 -1
@testcase | 12 | -1 -1 | 0 0 0 0 0 0
@testcase | 12 | 7 0 | -8 -1 0 -1 -8 -1
@testcase | 14 | -1 | 0 0 -1 -1 0 0 6
@testcase | 14 | 0 | 0 0 -1 -1 -1 -1 6
@testcase | 15 | -1 -1 | 0 -1
@testcase | 15 | -1 0 | 0 -1
@testcase | 16 | 0 0 | 3 -1 -1
@testcase | 17 | | -1 0 0 -1 -1 0
@testcase | 18 | 0 | 0 -1 0 -1 -1
@testcase | 18 | -1 | -1 0 -1 0 -1
@testcase | 19 | 0 | 0 -1 -1 0 0
@testcase | 19 | -1 | -1 0 0 -1 -1
@fif_codegen
"""
boolWithBitwiseConst PROC:<{
//
0 PUSHINT // _3
-1 PUSHINT // _3 _5
0 PUSHINT // _3 _5 _7
-1 PUSHINT // _3 _5 _7 _8
}>
"""
@fif_codegen
"""
testDoUntilCodegen PROC:<{
// i n
0 PUSHINT // i n cnt=0
UNTIL:<{
INC // i n cnt
s2 PUSH // i n cnt i
NOT // i n cnt _6
}> // i n cnt
UNTIL:<{
INC // i n cnt
s2 PUSH // i n cnt i
NOT // i n cnt _9
}> // i n cnt
UNTIL:<{
INC // i n cnt
OVER // i n cnt n
0 EQINT // i n cnt _12
}> // i n cnt
s0 s2 XCHG // cnt n i
NOT // cnt n _13
SWAP // cnt _13 n
0 EQINT // cnt _13 _14
}>
"""
@fif_codegen
"""
testConstNegateCodegen PROC:<{
//
TRUE // _0
FALSE // _0 _1
FALSE // _0 _1 _2
TRUE // _0 _1 _2 _3
TRUE // _0 _1 _2 _3 _4
FALSE // _0 _1 _2 _3 _4 _5
}>
"""
@fif_codegen
"""
testBoolNegateOptimized PROC:<{
// x
DUP // x x
NOT // x _1
OVER // x _1 x
NOT // x _1 _2
s2 s(-1) PUXC
TRUE // x _1 x _2 _3
}>
"""
@fif_codegen
"""
testBoolCompareOptimized PROC:<{
// x
DUP // x x
NOT // x _1
OVER // x _1 x
eqX CALLDICT // x _1 _2
NOT // x _1 _3
s2 PUSH // x _1 _3 x
eqX CALLDICT // x _1 _3 _4
s3 PUSH // x _1 _3 _4 x
}>
"""
*/

View file

@ -1,6 +1,6 @@
global op: (int, int) -> int;
fun check_assoc(a: int, b: int, c: int): int {
fun check_assoc(a: int, b: int, c: int): bool {
return op(op(a, b), c) == op(a, op(b, c));
}
@ -8,8 +8,9 @@ fun unnamed_args(_: int, _: slice, _: int) {
return true;
}
fun main(x: int, y: int, z: int): int {
fun main(x: int, y: int, z: int): bool {
op = `_+_`;
if (0) { return null; }
return check_assoc(x, y, z);
}

View file

@ -2,7 +2,7 @@ fun check_assoc(op: (int, int) -> int, a: int, b: int, c: int) {
return op(op(a, b), c) == op(a, op(b, c));
}
fun main(x: int, y: int, z: int): int {
fun main(x: int, y: int, z: int): bool {
return check_assoc(`_+_`, x, y, z);
}

View file

@ -162,8 +162,8 @@ fun test13() {
}
@method_id(114)
fun test110(x: int) {
var s = beginCell().storeBool(x < 0).storeBool(0).storeBool(x).endCell().beginParse();
fun test110(x: bool) {
var s = beginCell().storeBool(x == true).storeBool(false).storeBool(x).endCell().beginParse();
return (s.loadBool(), s.loadBool(), s.loadBool());
}
@ -179,15 +179,15 @@ fun test111() {
if (s.addressIsNone()) {
s.skipBits(2);
}
if (s.loadBool() == 0) {
assert(s.loadBool() == 0) throw 444;
if (s.loadBool() == false) {
assert(!s.loadBool()) throw 444;
s.skipBouncedPrefix();
}
var op2 = s.loadMessageOp();
var q2 = s.loadMessageQueryId();
s.skipBits(64);
s.assertEndOfSlice();
assert(isMessageBounced(0x001)) throw 444;
assert(isMessageBounced(0x001) && !isMessageBounced(0x002)) throw 444;
return (op1, q1, op2, q2);
}

View file

@ -1,7 +1,7 @@
@method_id(101)
fun test1(): int {
var x = false;
if (x == true) {
var x: int = false as int;
if (x == true as int) {
x= 100500;
}
return x;

View file

@ -11,7 +11,7 @@ fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell {
fun lookupIdxByValue(idict32: cell, value: int): int {
var cur_key = -1;
do {
var (cur_key redef, cs: slice, found: int) = 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) {

View file

@ -0,0 +1,8 @@
fun failMathOnBoolean(c: cell) {
return (null == c) * 10;
}
/**
@compilation_should_fail
@stderr can not apply operator `*` to `bool` and `int`
*/

View file

@ -0,0 +1,11 @@
fun failBitwiseNotOnBool() {
var eq = 1 == 0;
if (~eq) {
return 0;
}
}
/**
@compilation_should_fail
@stderr can not apply operator `~` to `bool`
*/

View file

@ -1,9 +1,9 @@
fun main() {
var tri: (int, bool) = (10, false);
var tri: (int, int) = (10, false);
return;
}
/**
@compilation_should_fail
@stderr bool type is not supported yet
@stderr can not assign `(int, bool)` to variable of type `(int, int)`
*/

View file

@ -4,5 +4,5 @@ fun failWhenTernaryConditionNotInt(cs: slice) {
/**
@compilation_should_fail
@stderr condition of ternary operator must be an integer
@stderr can not use `slice` as a boolean condition
*/

View file

@ -1,14 +1,14 @@
import "imports/use-dicts.tolk"
fun simpleAllConst() {
return (!0, !!0 & !false, !!!0, !1, !!1, !-1, !!-1, (!5 == 0) == !0, !0 == true);
return (!0, !!0 & !false, !!!0, !1, !!1, !-1, !!-1, (!5 as int == 0) == !0, !0 == true);
}
fun compileTimeEval1(x: int) {
// todo now compiler doesn't understand that bool can't be equal to number other than 0/-1
// (but understands that it can't be positive)
// that's why for now, the last condition is evaluated at runtime
return (!x, !x > 10, !x < 10, !!x == 5, !x == -10);
return (!x, !x as int > 10, (!x as int) < 10, !!x as int == 5, !x as int == -10);
}
@method_id(101)
@ -23,13 +23,13 @@ fun withAndOr(x: int, y: int, z: int) {
var return_at_end = -1;
if (!x & !y) {
if (!z & !y) { return 10; }
else if (z | !!y) { return_at_end = 20; }
else if ((z != 0) | !!y) { return_at_end = 20; }
} else if (!!x & !!y & !z) {
if (!z & (x > 10)) { return_at_end = 30; }
if ((x != 11) & !z) { return 40; }
return_at_end = 50;
} else {
return_at_end = !x ? !y : !z | 1;
return_at_end = !x ? !y as int : (!z as int) | 1;
}
return return_at_end;
}
@ -124,6 +124,31 @@ fun testLogicalOps2(first: int) {
return (s.getRemainingBitsCount(), sum);
}
@method_id(112)
fun mixLogicalIntsAndBools(first: int, cond: bool) {
return (
(first && cond) || (!first && cond),
((first & -1) & cond as int) == ((first && true) && cond) as int,
7 && cond,
first || cond || !cond || alwaysThrows(),
cond || first || !first || alwaysThrows()
);
}
@method_id(113)
fun testConvertIfToIfnot(x: bool) {
assert(!!(x == false), 100);
assert(!x, 100);
if (x == !!false) {
return 1;
}
if (!!(x != !false)) {
return 1;
}
assert(!!x, 100);
return -4;
}
fun main() {
}
@ -160,18 +185,21 @@ fun main() {
@testcase | 110 | 500 | -1 -1 0 -1 -1 3
@testcase | 111 | 0 | 32 4
@testcase | 111 | -1 | 0 8
@testcase | 112 | 5 0 | 0 -1 0 -1 -1
@testcase | 112 | 0 -1 | -1 -1 -1 -1 -1
@testcase | 113 | 0 | 1
@fif_codegen
"""
simpleAllConst PROC:<{
//
-1 PUSHINT
TRUE
0 PUSHINT
-1 PUSHINT
0 PUSHINT
-1 PUSHINT
0 PUSHINT
-1 PUSHINT
TRUE
FALSE
TRUE
FALSE
TRUE
TRUE
TRUE
}>
@ -293,4 +321,27 @@ These are moments of future optimizations. For now, it's more than enough.
}>
"""
@fif_codegen
"""
testConvertIfToIfnot PROC:<{
// x
DUP // x x
100 THROWIF
DUP // x x
100 THROWIF
DUP // x x
IFNOTJMP:<{ // x
DROP //
1 PUSHINT // _7=1
}> // x
DUP // x x
IFNOTJMP:<{ // x
DROP //
1 PUSHINT // _8=1
}> // x
100 THROWIFNOT
-4 PUSHINT // _12=-4
}>
"""
*/

View file

@ -22,7 +22,7 @@ global `some()var`:int;
return `a`*-1*-(1)*---(1)*+just10()+-`just10`()*m1*-m1+-eq(m1)----0x1;
}
@method_id(112) fun `bitwise~ops`(flags:int):[int,int] {
@method_id(112) fun `bitwise~ops`(flags:int):[bool,bool] {
return[
(just10()-3==just10()-(4)--1)|((2==2)&(eq(eq(10)) -3==just10()--13)),
((flags&0xFF)!=0)

View file

@ -73,7 +73,7 @@ fun test7() {
var b = beginCell().storeMaybeRef(null);
var s = b.endCell().beginParse();
var c = s.loadMaybeRef();
return (null == c) * 10 + (b != null);
return (null == c) as int * 10 + (b != null) as int;
}
fun main() {
@ -139,7 +139,7 @@ fun main() {
10 MULCONST // b _13
SWAP // _13 b
ISNULL // _13 _14
0 EQINT // _13 _15
NOT // _13 _15
ADD // _16
}>
"""

View file

@ -1,4 +1,4 @@
fun justTrue(): int { return true; }
fun justTrue(): bool { return true; }
fun unary_minus_1(a: int, b: int, c: int): int{return -(a+b) *c;}
fun unary_minus_2(a: int, b: int, c: int): int{return(-(a+b))*c;}
@ -6,17 +6,17 @@ fun unary_minus_3(a: int, b: int, c: int): int{return-((a+b) *c);}
@method_id(101)
fun test1(x: int, y: int, z: int): int {
fun test1(x: int, y: int, z: int): bool {
return (x > 0) & (y > 0) & (z > 0);
}
@method_id(102)
fun test2(x: int, y: int, z: int): int {
return x > (0 & (y > 0) & (z > 0));
fun test2(x: int, y: int, z: int): bool {
return x > (0 & (y > 0) as int & (z > 0) as int);
}
@method_id(103)
fun test3(x: int, y: int, z: int): int {
fun test3(x: int, y: int, z: int): bool {
if ((x < 0) | (y < 0)) {
return z < 0;
}
@ -24,29 +24,29 @@ fun test3(x: int, y: int, z: int): int {
}
@method_id(104)
fun test4(x: int, y: int, mode: int): int {
fun test4(x: int, y: int, mode: int): bool {
if (mode == 1) {
return (x == 10) | (y == 20);
} if (mode == 2) {
return (x == 10) | (y == 20);
} else {
return x == (10 | (y == 20));
return x == (10 | (y == 20) as int);
}
}
@method_id(105)
fun test5(status: int): int {
return justTrue() & (status == 1) & ((justTrue() & status) == 1);
fun test5(status: int): bool {
return justTrue() & (status == 1) & ((justTrue() as int & status) == 1);
}
@method_id(106)
fun test6(a: int, b: int, c: int): int {
fun test6(a: int, b: int, c: int): bool {
return (unary_minus_1(a,b,c) == unary_minus_2(a,b,c)) & (unary_minus_1(a,b,c) == unary_minus_3(a,b,c));
}
@method_id(107)
fun test7(b: int): int {
var a = b == 3 ? 3 : b == 4 ? 4 : (b == 5) & 1 ? 5 : 100;
var a = b == 3 ? 3 : b == 4 ? 4 : (b == 5) & true ? 5 : 100;
return a;
}
@ -56,14 +56,14 @@ fun test8(b: int): int {
return a;
}
fun `_<p`(a: int, b: int): int { return true; }
fun `_<p`(a: int, b: int): bool { return true; }
fun main() {
// ok to parse
var c = [
(3 & 3) > 0, 3 & (3 > 0), 3 & (`_<_`(3, 0)),
3 & `_<p`(3, 0), (1 & 2) ^ (3 | 4),
1 & ((1) == 1)
(3 & 3) > 0, 3 & (3 > 0) as int, 3 & (`_<_`(3, 0)),
3 & `_<p`(3, 0) as int, (1 & 2) ^ (3 | 4),
true & ((1) == 1)
];
}

View file

@ -661,7 +661,7 @@ fun fixed248_pow(x: int, y: int): int {
return 1 << 248; // x^0 = 1
}
if (x <= 0) {
var bad: int = (x | y) < 0;
var bad: int = ((x | y) < 0) as int;
return 0 >> bad; // 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise
}
var (l, s) = log2_aux_f256(x);
@ -677,7 +677,7 @@ fun fixed248_pow(x: int, y: int): int {
// now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2
var sq: int = q + 248;
if (sq <= 0) {
return -(sq == 0); // underflow
return -((sq == 0) as int); // underflow
}
y = expm1_f257(mulrshiftr256(ll, log2_const_f256()));
return (y ~>> (9 - q)) - (-1 << sq);
@ -986,7 +986,7 @@ fun tset<X>(mutate self: tuple, idx: int, value: X): void
// fixed256 acos_prepare_slow(fixed255 x);
@inline
fun acos_prepare_slow_f255(x: int): int {
x -= (x == 0);
x -= (x == 0) as int;
var t: int = 1;
repeat (255) {
t = t * sign(x) * 2 + 1; // decode Gray code (sign(x_0), sign(x_1), ...)

View file

@ -38,7 +38,7 @@ fun foo_until(x: int): int {
}
@method_id(4)
fun test4(x: int): (int, int) {
fun test4(x: int): (int, bool) {
var s = 0;
var reached = false;
do {

View file

@ -6,7 +6,7 @@ fun main(x: int): int {
if (i > 5) {
return 1;
}
var f: int = (i * i == 64);
var f: bool = (i * i == 64);
} while (!f);
return -1;
}

View file

@ -4,7 +4,7 @@ fun test(y: int): int {
if (y > 0) {
return 1;
}
return x > 0;
return x > 0 ? -1 : 0;
}
@method_id(2)

View file

@ -18,6 +18,7 @@ set(TOLK_SOURCE
pipe-check-rvalue-lvalue.cpp
pipe-check-pure-impure.cpp
pipe-constant-folding.cpp
pipe-optimize-boolean-expr.cpp
pipe-ast-to-legacy.cpp
pipe-find-unused-symbols.cpp
pipe-generate-fif-output.cpp

View file

@ -673,25 +673,20 @@ static AnyV parse_return_statement(Lexer& lex) {
return createV<ast_return_statement>(loc, child);
}
static AnyV parse_if_statement(Lexer& lex, bool is_ifnot) {
static AnyV parse_if_statement(Lexer& lex) {
SrcLocation loc = lex.cur_location();
lex.expect(tok_if, "`if`");
lex.expect(tok_oppar, "`(`");
AnyExprV cond = parse_expr(lex);
lex.expect(tok_clpar, "`)`");
// replace if(!expr) with ifnot(expr) (this should be done later, but for now, let this be right at parsing time)
if (auto v_not = cond->try_as<ast_unary_operator>(); v_not && v_not->tok == tok_logical_not) {
is_ifnot = !is_ifnot;
cond = v_not->get_rhs();
}
V<ast_sequence> if_body = parse_sequence(lex);
V<ast_sequence> else_body = nullptr;
if (lex.tok() == tok_else) { // else if(e) { } or else { }
lex.next();
if (lex.tok() == tok_if) {
AnyV v_inner_if = parse_if_statement(lex, false);
AnyV v_inner_if = parse_if_statement(lex);
else_body = createV<ast_sequence>(v_inner_if->loc, lex.cur_location(), {v_inner_if});
} else {
else_body = parse_sequence(lex);
@ -699,7 +694,7 @@ static AnyV parse_if_statement(Lexer& lex, bool is_ifnot) {
} else { // no 'else', create empty block
else_body = createV<ast_sequence>(lex.cur_location(), lex.cur_location(), {});
}
return createV<ast_if_statement>(loc, is_ifnot, cond, if_body, else_body);
return createV<ast_if_statement>(loc, false, cond, if_body, else_body);
}
static AnyV parse_repeat_statement(Lexer& lex) {
@ -838,7 +833,7 @@ AnyV parse_statement(Lexer& lex) {
case tok_return:
return parse_return_statement(lex);
case tok_if:
return parse_if_statement(lex, false);
return parse_if_statement(lex);
case tok_repeat:
return parse_repeat_statement(lex);
case tok_do:

View file

@ -475,7 +475,7 @@ AsmOp compile_unary_plus(std::vector<VarDescr>& res, std::vector<VarDescr>& args
return AsmOp::Nop();
}
AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where, bool for_int_arg) {
tolk_assert(res.size() == 1 && args.size() == 1);
VarDescr &r = res[0], &x = args[0];
if (x.is_int_const()) {
@ -484,7 +484,9 @@ AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
return push_const(r.int_const);
}
r.val = VarDescr::ValBool;
return exec_op("0 EQINT", 1);
// for integers, `!var` is `var != 0`
// for booleans, `!var` can be shortened to `~var` (works the same for 0/-1 but consumes less)
return for_int_arg ? exec_op("0 EQINT", 1) : exec_op("NOT", 1);
}
AsmOp compile_bitwise_and(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
@ -1047,7 +1049,7 @@ AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
return exec_op(fetch ? "LDSLICEX" : "PLDSLICEX", 2, 1 + (unsigned)fetch);
}
// fun at<X>(t: tuple, index: int): X asm "INDEXVAR";
// fun tupleAt<X>(t: tuple, index: int): X asm "INDEXVAR";
AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(args.size() == 2 && res.size() == 1);
auto& y = args[1];
@ -1058,7 +1060,7 @@ AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args,
return exec_op("INDEXVAR", 2, 1);
}
// fun __isNull<X>(X arg): int
// fun __isNull<X>(X arg): bool
AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(args.size() == 1 && res.size() == 1);
res[0].val = VarDescr::ValBool;
@ -1071,6 +1073,7 @@ void define_builtins() {
TypePtr Unit = TypeDataVoid::create();
TypePtr Int = TypeDataInt::create();
TypePtr Bool = TypeDataBool::create();
TypePtr Slice = TypeDataSlice::create();
TypePtr Builder = TypeDataBuilder::create();
TypePtr Tuple = TypeDataTuple::create();
@ -1085,18 +1088,36 @@ void define_builtins() {
std::vector ParamsInt3 = {Int, Int, Int};
std::vector ParamsSliceInt = {Slice, Int};
define_builtin_func("_+_", ParamsInt2, Int, nullptr,
compile_add,
FunctionData::flagMarkedAsPure);
define_builtin_func("_-_", ParamsInt2, Int, nullptr,
compile_sub,
FunctionData::flagMarkedAsPure);
// builtin operators
// they are internally stored as functions, because at IR level, there is no difference
// between calling `userAdd(a,b)` and `_+_(a,b)`
// since they are registered in a global symtable, technically, they can even be referenced from Tolk code,
// though it's a "hidden feature" and won't work well for overloads (`==` for int and bool, for example)
// unary operators
define_builtin_func("-_", ParamsInt1, Int, nullptr,
compile_unary_minus,
FunctionData::flagMarkedAsPure);
define_builtin_func("+_", ParamsInt1, Int, nullptr,
compile_unary_plus,
FunctionData::flagMarkedAsPure);
define_builtin_func("!_", ParamsInt1, Bool, nullptr,
std::bind(compile_logical_not, _1, _2, _3, true),
FunctionData::flagMarkedAsPure);
define_builtin_func("!b_", {Bool}, Bool, nullptr, // "overloaded" separate version for bool
std::bind(compile_logical_not, _1, _2, _3, false),
FunctionData::flagMarkedAsPure);
define_builtin_func("~_", ParamsInt1, Int, nullptr,
compile_bitwise_not,
FunctionData::flagMarkedAsPure);
// binary operators
define_builtin_func("_+_", ParamsInt2, Int, nullptr,
compile_add,
FunctionData::flagMarkedAsPure);
define_builtin_func("_-_", ParamsInt2, Int, nullptr,
compile_sub,
FunctionData::flagMarkedAsPure);
define_builtin_func("_*_", ParamsInt2, Int, nullptr,
compile_mul,
FunctionData::flagMarkedAsPure);
@ -1124,25 +1145,19 @@ void define_builtins() {
define_builtin_func("_^>>_", ParamsInt2, Int, nullptr,
std::bind(compile_rshift, _1, _2, _3, 1),
FunctionData::flagMarkedAsPure);
define_builtin_func("!_", ParamsInt1, Int, nullptr,
compile_logical_not,
FunctionData::flagMarkedAsPure);
define_builtin_func("~_", ParamsInt1, Int, nullptr,
compile_bitwise_not,
FunctionData::flagMarkedAsPure);
define_builtin_func("_&_", ParamsInt2, Int, nullptr,
define_builtin_func("_&_", ParamsInt2, Int, nullptr, // also works for bool
compile_bitwise_and,
FunctionData::flagMarkedAsPure);
define_builtin_func("_|_", ParamsInt2, Int, nullptr,
define_builtin_func("_|_", ParamsInt2, Int, nullptr, // also works for bool
compile_bitwise_or,
FunctionData::flagMarkedAsPure);
define_builtin_func("_^_", ParamsInt2, Int, nullptr,
define_builtin_func("_^_", ParamsInt2, Int, nullptr, // also works for bool
compile_bitwise_xor,
FunctionData::flagMarkedAsPure);
define_builtin_func("_==_", ParamsInt2, Int, nullptr,
define_builtin_func("_==_", ParamsInt2, Int, nullptr, // also works for bool
std::bind(compile_cmp_int, _1, _2, 2),
FunctionData::flagMarkedAsPure);
define_builtin_func("_!=_", ParamsInt2, Int, nullptr,
define_builtin_func("_!=_", ParamsInt2, Int, nullptr, // also works for bool
std::bind(compile_cmp_int, _1, _2, 5),
FunctionData::flagMarkedAsPure);
define_builtin_func("_<_", ParamsInt2, Int, nullptr,
@ -1160,6 +1175,33 @@ void define_builtins() {
define_builtin_func("_<=>_", ParamsInt2, Int, nullptr,
std::bind(compile_cmp_int, _1, _2, 7),
FunctionData::flagMarkedAsPure);
// special function used for internal compilation of some lexical constructs
// for example, `throw 123;` is actually calling `__throw(123)`
define_builtin_func("__true", {}, Bool, nullptr, /* AsmOp::Const("TRUE") */
std::bind(compile_bool_const, _1, _2, true),
FunctionData::flagMarkedAsPure);
define_builtin_func("__false", {}, Bool, nullptr, /* AsmOp::Const("FALSE") */
std::bind(compile_bool_const, _1, _2, false),
FunctionData::flagMarkedAsPure);
define_builtin_func("__null", {}, typeT, declGenericT,
AsmOp::Const("PUSHNULL"),
FunctionData::flagMarkedAsPure);
define_builtin_func("__isNull", {typeT}, Bool, declGenericT,
compile_is_null,
FunctionData::flagMarkedAsPure);
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
compile_throw,
0);
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
compile_throw_arg,
0);
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
compile_throw_if_unless,
0);
// functions from stdlib marked as `builtin`, implemented at compiler level for optimizations
// (for example, `loadInt(1)` is `1 LDI`, but `loadInt(n)` for non-constant requires it be on a stack and `LDIX`)
define_builtin_func("mulDivFloor", ParamsInt3, Int, nullptr,
std::bind(compile_muldiv, _1, _2, _3, -1),
FunctionData::flagMarkedAsPure);
@ -1172,27 +1214,6 @@ void define_builtins() {
define_builtin_func("mulDivMod", ParamsInt3, TypeDataTensor::create({Int, Int}), nullptr,
AsmOp::Custom("MULDIVMOD", 3, 2),
FunctionData::flagMarkedAsPure);
define_builtin_func("__true", {}, Int, nullptr, /* AsmOp::Const("TRUE") */
std::bind(compile_bool_const, _1, _2, true),
FunctionData::flagMarkedAsPure);
define_builtin_func("__false", {}, Int, nullptr, /* AsmOp::Const("FALSE") */
std::bind(compile_bool_const, _1, _2, false),
FunctionData::flagMarkedAsPure);
define_builtin_func("__null", {}, typeT, declGenericT,
AsmOp::Const("PUSHNULL"),
FunctionData::flagMarkedAsPure);
define_builtin_func("__isNull", {typeT}, Int, declGenericT,
compile_is_null,
FunctionData::flagMarkedAsPure);
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
compile_throw,
0);
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
compile_throw_arg,
0);
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
compile_throw_if_unless,
0);
define_builtin_func("loadInt", ParamsSliceInt, Int, nullptr,
std::bind(compile_fetch_int, _1, _2, true, true),
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf,

View file

@ -586,6 +586,8 @@ static void process_do_while_statement(V<ast_do_while_statement> v, CodeBlob& co
until_cond = createV<ast_binary_operator>(cond->loc, "<", tok_lt, v_geq->get_lhs(), v_geq->get_rhs());
} else if (auto v_gt = cond->try_as<ast_binary_operator>(); v_gt && v_gt->tok == tok_gt) {
until_cond = createV<ast_binary_operator>(cond->loc, "<=", tok_geq, v_gt->get_lhs(), v_gt->get_rhs());
} else if (cond->inferred_type == TypeDataBool::create()) {
until_cond = createV<ast_unary_operator>(cond->loc, "!b", tok_logical_not, cond);
} else {
until_cond = createV<ast_unary_operator>(cond->loc, "!", tok_logical_not, cond);
}

View file

@ -23,7 +23,8 @@
* This pipe is supposed to do constant folding, like replacing `2 + 3` with `5`.
* It happens after type inferring and validity checks, one of the last ones.
*
* Currently, it just replaces `-1` (ast_unary_operator ast_int_const) with a number -1.
* Currently, it just replaces `-1` (ast_unary_operator ast_int_const) with a number -1
* and `!true` with false.
* More rich constant folding should be done some day, but even without this, IR optimizations
* (operating low-level stack variables) pretty manage to do all related optimizations.
* Constant folding in the future, done at AST level, just would slightly reduce amount of work for optimizer.
@ -39,6 +40,13 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody {
return v_int;
}
static V<ast_bool_const> create_bool_const(SrcLocation loc, bool bool_val) {
auto v_bool = createV<ast_bool_const>(loc, bool_val);
v_bool->assign_inferred_type(TypeDataBool::create());
v_bool->assign_rvalue_true();
return v_bool;
}
AnyExprV replace(V<ast_unary_operator> v) override {
parent::replace(v);
@ -58,6 +66,15 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody {
return v->get_rhs();
}
// `!true` / `!false`
if (t == tok_logical_not && v->get_rhs()->type == ast_bool_const) {
return create_bool_const(v->loc, !v->get_rhs()->as<ast_bool_const>()->bool_val);
}
// `!0`
if (t == tok_logical_not && v->get_rhs()->type == ast_int_const) {
return create_bool_const(v->loc, v->get_rhs()->as<ast_int_const>()->intval == 0);
}
return v;
}

View file

@ -110,6 +110,20 @@ static void fire_error_assign_always_null_to_variable(SrcLocation loc, const Loc
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>`" : ""));
}
// fire an error on `!cell` / `+slice`
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_cannot_apply_operator(SrcLocation loc, std::string_view operator_name, AnyExprV unary_expr) {
std::string op = static_cast<std::string>(operator_name);
throw ParseError(loc, "can not apply operator `" + op + "` to " + to_string(unary_expr->inferred_type));
}
// fire an error on `int + cell` / `slice & int`
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_cannot_apply_operator(SrcLocation loc, std::string_view operator_name, AnyExprV lhs, AnyExprV rhs) {
std::string op = static_cast<std::string>(operator_name);
throw ParseError(loc, "can not apply operator `" + op + "` to " + to_string(lhs->inferred_type) + " and " + to_string(rhs->inferred_type));
}
// 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;
@ -345,6 +359,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
return v_inferred->inferred_type == TypeDataInt::create();
}
static bool expect_boolean(AnyExprV v_inferred) {
return v_inferred->inferred_type == TypeDataBool::create();
}
static void infer_int_const(V<ast_int_const> v) {
assign_inferred_type(v, TypeDataInt::create());
}
@ -358,8 +376,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
}
static void infer_bool_const(V<ast_bool_const> v) {
// currently, Tolk has no `bool` type; `true` and `false` are integers (-1 and 0)
assign_inferred_type(v, TypeDataInt::create());
assign_inferred_type(v, TypeDataBool::create());
}
static void infer_local_vars_declaration(V<ast_local_vars_declaration>) {
@ -544,8 +561,23 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
// almost all operators implementation is hardcoded by built-in functions `_+_` and similar
std::string_view builtin_func = v->operator_name; // "+" for operator +=
switch (v->tok) {
// &= |= ^= are "overloaded" both for integers and booleans, (int &= bool) is NOT allowed
case tok_set_bitwise_and:
case tok_set_bitwise_or:
case tok_set_bitwise_xor: {
bool both_int = expect_integer(lhs) && expect_integer(rhs);
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
if (!both_int && !both_bool) {
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
break;
}
// others are mathematical: += *= ...
default:
if (!expect_integer(lhs) || !expect_integer(rhs)) {
v->error("can not apply operator `" + static_cast<std::string>(v->operator_name) + "` to " + to_string(lhs) + " and " + to_string(rhs));
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
}
assign_inferred_type(v, lhs);
@ -563,10 +595,26 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
// all operators implementation is hardcoded by built-in functions `~_` and similar
std::string_view builtin_func = v->operator_name;
switch (v->tok) {
case tok_minus:
case tok_plus:
case tok_bitwise_not:
if (!expect_integer(rhs)) {
v->error("can not apply operator `" + static_cast<std::string>(v->operator_name) + "` to " + to_string(rhs));
fire_error_cannot_apply_operator(v->loc, v->operator_name, rhs);
}
assign_inferred_type(v, TypeDataInt::create());
break;
case tok_logical_not:
if (expect_boolean(rhs)) {
builtin_func = "!b"; // "overloaded" for bool
} else if (!expect_integer(rhs)) {
fire_error_cannot_apply_operator(v->loc, v->operator_name, rhs);
}
assign_inferred_type(v, TypeDataBool::create());
break;
default:
tolk_assert(false);
}
if (!builtin_func.empty()) {
const FunctionData* builtin_sym = lookup_global_symbol(static_cast<std::string>(builtin_func) + "_")->as<FunctionData>();
@ -587,26 +635,59 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
switch (v->tok) {
// == != can compare both integers and booleans, (int == bool) is NOT allowed
case tok_eq:
case tok_neq:
case tok_neq: {
bool both_int = expect_integer(lhs) && expect_integer(rhs);
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
if (!both_int && !both_bool) {
if (lhs->inferred_type == rhs->inferred_type) { // compare slice with slice
v->error("type " + to_string(lhs) + " can not be compared with `== !=`");
} else {
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
}
assign_inferred_type(v, TypeDataBool::create());
break;
}
// < > can compare only integers
case tok_lt:
case tok_gt:
case tok_leq:
case tok_geq:
case tok_spaceship: {
if (!expect_integer(lhs) || !expect_integer(rhs)) {
v->error("comparison operators `== !=` can compare only integers, got " + to_string(lhs) + " and " + to_string(rhs));
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
assign_inferred_type(v, TypeDataInt::create());
assign_inferred_type(v, TypeDataBool::create());
break;
}
// & | ^ are "overloaded" both for integers and booleans, (int & bool) is NOT allowed
case tok_bitwise_and:
case tok_bitwise_or:
case tok_bitwise_xor: {
bool both_int = expect_integer(lhs) && expect_integer(rhs);
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
if (!both_int && !both_bool) {
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
assign_inferred_type(v, rhs); // (int & int) is int, (bool & bool) is bool
break;
}
// && || can work with integers and booleans, (int && bool) is allowed
case tok_logical_and:
case tok_logical_or: {
if (!expect_integer(lhs) || !expect_integer(rhs)) {
v->error("logical operators `&& ||` expect integer operands, got " + to_string(lhs) + " and " + to_string(rhs));
bool lhs_ok = expect_integer(lhs) || expect_boolean(lhs);
bool rhs_ok = expect_integer(rhs) || expect_boolean(rhs);
if (!lhs_ok || !rhs_ok) {
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
assign_inferred_type(v, TypeDataInt::create());
builtin_func = {};
assign_inferred_type(v, TypeDataBool::create());
builtin_func = {}; // no built-in functions, logical operators are expressed as IFs at IR level
break;
}
// others are mathematical: + * ...
default:
if (!expect_integer(lhs) || !expect_integer(rhs)) {
v->error("can not apply operator `" + static_cast<std::string>(v->operator_name) + "` to " + to_string(lhs) + " and " + to_string(rhs));
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
}
assign_inferred_type(v, TypeDataInt::create());
}
@ -619,9 +700,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
}
void infer_ternary_operator(V<ast_ternary_operator> v, TypePtr hint) {
infer_any_expr(v->get_cond());
if (!expect_integer(v->get_cond())) {
v->get_cond()->error("condition of ternary operator must be an integer, got " + to_string(v->get_cond()));
AnyExprV cond = v->get_cond();
infer_any_expr(cond);
if (!expect_integer(cond) && !expect_boolean(cond)) {
cond->error("can not use " + to_string(cond) + " as a boolean condition");
}
infer_any_expr(v->get_when_true(), hint);
infer_any_expr(v->get_when_false(), hint);
@ -983,35 +1065,39 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
}
void process_if_statement(V<ast_if_statement> v) {
infer_any_expr(v->get_cond());
if (!expect_integer(v->get_cond())) {
v->get_cond()->error("condition of `if` must be an integer, got " + to_string(v->get_cond()));
AnyExprV cond = v->get_cond();
infer_any_expr(cond);
if (!expect_integer(cond) && !expect_boolean(cond)) {
cond->error("can not use " + to_string(cond) + " as a boolean condition");
}
process_any_statement(v->get_if_body());
process_any_statement(v->get_else_body());
}
void process_repeat_statement(V<ast_repeat_statement> v) {
infer_any_expr(v->get_cond());
if (!expect_integer(v->get_cond())) {
v->get_cond()->error("condition of `repeat` must be an integer, got " + to_string(v->get_cond()));
AnyExprV cond = v->get_cond();
infer_any_expr(cond);
if (!expect_integer(cond)) {
cond->error("condition of `repeat` must be an integer, got " + to_string(cond));
}
process_any_statement(v->get_body());
}
void process_while_statement(V<ast_while_statement> v) {
infer_any_expr(v->get_cond());
if (!expect_integer(v->get_cond())) {
v->get_cond()->error("condition of `while` must be an integer, got " + to_string(v->get_cond()));
AnyExprV cond = v->get_cond();
infer_any_expr(cond);
if (!expect_integer(cond) && !expect_boolean(cond)) {
cond->error("can not use " + to_string(cond) + " as a boolean condition");
}
process_any_statement(v->get_body());
}
void process_do_while_statement(V<ast_do_while_statement> v) {
process_any_statement(v->get_body());
infer_any_expr(v->get_cond());
if (!expect_integer(v->get_cond())) {
v->get_cond()->error("condition of `while` must be an integer, got " + to_string(v->get_cond()));
AnyExprV cond = v->get_cond();
infer_any_expr(cond);
if (!expect_integer(cond) && !expect_boolean(cond)) {
cond->error("can not use " + to_string(cond) + " as a boolean condition");
}
}
@ -1027,9 +1113,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
}
void process_assert_statement(V<ast_assert_statement> v) {
infer_any_expr(v->get_cond());
if (!expect_integer(v->get_cond())) {
v->get_cond()->error("condition of `assert` must be an integer, got " + to_string(v->get_cond()));
AnyExprV cond = v->get_cond();
infer_any_expr(cond);
if (!expect_integer(cond) && !expect_boolean(cond)) {
cond->error("can not use " + to_string(cond) + " as a boolean condition");
}
infer_any_expr(v->get_thrown_code());
if (!expect_integer(v->get_thrown_code())) {

View file

@ -0,0 +1,172 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tolk.h"
#include "ast.h"
#include "ast-replacer.h"
#include "type-system.h"
/*
* This pipe does some optimizations related to booleans.
* It happens after type inferring, when we know types of all expressions.
*
* Example: `boolVar == true` -> `boolVar`.
* Example: `!!boolVar` -> `boolVar`.
* Also in unwraps parenthesis inside if condition and similar: `assert(((x)), 404)` -> `assert(x, 404)`
*
* todo some day, replace && || with & | when it's safe (currently, && always produces IFs in Fift)
* It's tricky to implement whether replacing is safe.
* For example, safe: `a > 0 && a < 10` / `a != 3 && a != 5`
* For example, unsafe: `cached && calc()` / `a > 0 && log(a)` / `b != 0 && a / b > 1` / `i >= 0 && arr[idx]` / `f != null && close(f)`
*/
namespace tolk {
static AnyExprV unwrap_parenthesis(AnyExprV v) {
while (v->type == ast_parenthesized_expression) {
v = v->as<ast_parenthesized_expression>()->get_expr();
}
return v;
}
struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody {
static V<ast_int_const> create_int_const(SrcLocation loc, td::RefInt256&& intval) {
auto v_int = createV<ast_int_const>(loc, std::move(intval), {});
v_int->assign_inferred_type(TypeDataInt::create());
v_int->assign_rvalue_true();
return v_int;
}
static V<ast_bool_const> create_bool_const(SrcLocation loc, bool bool_val) {
auto v_bool = createV<ast_bool_const>(loc, bool_val);
v_bool->assign_inferred_type(TypeDataInt::create());
v_bool->assign_rvalue_true();
return v_bool;
}
static V<ast_unary_operator> create_logical_not_for_bool(SrcLocation loc, AnyExprV rhs) {
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>());
return v_not;
}
protected:
AnyExprV replace(V<ast_unary_operator> v) override {
parent::replace(v);
if (v->tok == tok_logical_not) {
if (auto inner_not = v->get_rhs()->try_as<ast_unary_operator>(); inner_not && inner_not->tok == tok_logical_not) {
AnyExprV cond_not_not = inner_not->get_rhs();
// `!!boolVar` => `boolVar`
if (cond_not_not->inferred_type == TypeDataBool::create()) {
return cond_not_not;
}
// `!!intVar` => `intVar != 0`
if (cond_not_not->inferred_type == TypeDataInt::create()) {
auto v_zero = create_int_const(v->loc, td::make_refint(0));
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>());
return v_neq;
}
}
if (auto inner_bool = v->get_rhs()->try_as<ast_bool_const>()) {
// `!true` / `!false`
return create_bool_const(v->loc, !inner_bool->bool_val);
}
}
return v;
}
AnyExprV replace(V<ast_binary_operator> v) override {
parent::replace(v);
if (v->tok == tok_eq || v->tok == tok_neq) {
AnyExprV lhs = v->get_lhs();
AnyExprV rhs = v->get_rhs();
if (lhs->inferred_type == TypeDataBool::create() && rhs->type == ast_bool_const) {
// `boolVar == true` / `boolVar != false`
if (rhs->as<ast_bool_const>()->bool_val ^ (v->tok == tok_neq)) {
return lhs;
}
// `boolVar != true` / `boolVar == false`
return create_logical_not_for_bool(v->loc, lhs);
}
}
return v;
}
AnyV replace(V<ast_if_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_if_statement>(v->loc, v->is_ifnot, unwrap_parenthesis(v->get_cond()), v->get_if_body(), v->get_else_body());
}
// `if (!x)` -> ifnot(x)
while (auto v_cond_unary = v->get_cond()->try_as<ast_unary_operator>()) {
if (v_cond_unary->tok != tok_logical_not) {
break;
}
v = createV<ast_if_statement>(v->loc, !v->is_ifnot, v_cond_unary->get_rhs(), v->get_if_body(), v->get_else_body());
}
return v;
}
AnyV replace(V<ast_while_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_while_statement>(v->loc, unwrap_parenthesis(v->get_cond()), v->get_body());
}
return v;
}
AnyV replace(V<ast_do_while_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_do_while_statement>(v->loc, v->get_body(), unwrap_parenthesis(v->get_cond()));
}
return v;
}
AnyV replace(V<ast_assert_statement> v) override {
parent::replace(v);
if (v->get_cond()->type == ast_parenthesized_expression) {
v = createV<ast_assert_statement>(v->loc, unwrap_parenthesis(v->get_cond()), v->get_thrown_code());
}
return v;
}
public:
bool should_visit_function(const FunctionData* fun_ref) override {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
};
void pipeline_optimize_boolean_expressions() {
replace_ast_of_all_functions<OptimizerBooleanExpressionsReplacer>();
}
} // namespace tolk

View file

@ -150,9 +150,6 @@ struct TypeDataResolver {
if (un->text == "self") {
throw ParseError(un->loc, "`self` type can be used only as a return type of a function (enforcing it to be chainable)");
}
if (un->text == "bool") {
throw ParseError(un->loc, "bool type is not supported yet");
}
fire_error_unknown_type_name(un->loc, un->text);
}
return child;

View file

@ -41,6 +41,7 @@ void pipeline_refine_lvalue_for_mutate_arguments();
void pipeline_check_rvalue_lvalue();
void pipeline_check_pure_impure_operations();
void pipeline_constant_folding();
void pipeline_optimize_boolean_expressions();
void pipeline_convert_ast_to_legacy_Expr_Op();
void pipeline_find_unused_symbols();

View file

@ -64,6 +64,7 @@ int tolk_proceed(const std::string &entrypoint_filename) {
pipeline_check_rvalue_lvalue();
pipeline_check_pure_impure_operations();
pipeline_constant_folding();
pipeline_optimize_boolean_expressions();
pipeline_convert_ast_to_legacy_Expr_Op();
pipeline_find_unused_symbols();

View file

@ -76,6 +76,7 @@ public:
std::unordered_map<uint64_t, TypePtr> TypeDataTypeIdCalculation::all_unique_occurred_types;
TypePtr TypeDataInt::singleton;
TypePtr TypeDataBool::singleton;
TypePtr TypeDataCell::singleton;
TypePtr TypeDataSlice::singleton;
TypePtr TypeDataBuilder::singleton;
@ -87,6 +88,7 @@ TypePtr TypeDataVoid::singleton;
void type_system_init() {
TypeDataInt::singleton = new TypeDataInt;
TypeDataBool::singleton = new TypeDataBool;
TypeDataCell::singleton = new TypeDataCell;
TypeDataSlice::singleton = new TypeDataSlice;
TypeDataBuilder::singleton = new TypeDataBuilder;
@ -330,6 +332,16 @@ bool TypeDataInt::can_rhs_be_assigned(TypePtr rhs) const {
return false;
}
bool TypeDataBool::can_rhs_be_assigned(TypePtr rhs) const {
if (rhs == this) {
return true;
}
if (rhs == TypeDataNullLiteral::create()) {
return true;
}
return false;
}
bool TypeDataCell::can_rhs_be_assigned(TypePtr rhs) const {
if (rhs == this) {
return true;
@ -446,6 +458,10 @@ bool TypeDataInt::can_be_casted_with_as_operator(TypePtr cast_to) const {
return cast_to == this;
}
bool TypeDataBool::can_be_casted_with_as_operator(TypePtr cast_to) const {
return cast_to == this || cast_to == TypeDataInt::create();
}
bool TypeDataCell::can_be_casted_with_as_operator(TypePtr cast_to) const {
return cast_to == this;
}
@ -468,7 +484,7 @@ bool TypeDataContinuation::can_be_casted_with_as_operator(TypePtr cast_to) const
bool TypeDataNullLiteral::can_be_casted_with_as_operator(TypePtr cast_to) const {
return cast_to == this
|| cast_to == TypeDataInt::create() || cast_to == TypeDataCell::create() || cast_to == TypeDataSlice::create()
|| 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>();
}
@ -593,6 +609,9 @@ static TypePtr parse_simple_type(Lexer& lex) {
case tok_int:
lex.next();
return TypeDataInt::create();
case tok_bool:
lex.next();
return TypeDataBool::create();
case tok_cell:
lex.next();
return TypeDataCell::create();
@ -614,7 +633,6 @@ static TypePtr parse_simple_type(Lexer& lex) {
case tok_void:
lex.next();
return TypeDataVoid::create();
case tok_bool:
case tok_self:
case tok_identifier: {
SrcLocation loc = lex.cur_location();

View file

@ -120,6 +120,24 @@ public:
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
};
/*
* `bool` is TypeDataBool. TVM has no bool, only integers. Under the hood, -1 is true, 0 is false.
* From the type system point of view, int and bool are different, not-autocastable types.
*/
class TypeDataBool final : public TypeData {
TypeDataBool() : TypeData(2ULL, 0) {}
static TypePtr singleton;
friend void type_system_init();
public:
static TypePtr create() { return singleton; }
std::string as_human_readable() const override { return "bool"; }
bool can_rhs_be_assigned(TypePtr rhs) const override;
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
};
/*
* `cell` is TypeDataCell, representation of TVM cell.
*/