Skip to content

Commit 78c05fd

Browse files
committed
[Tolk] Types bytesN and bitsN backed by TVM slice
Their purpose is exactly like `intN`: future serialization, to specify fixed-size binary fields (like TL/B bits256). Note, that unlike intN, explicit casting is required. > fun calcHash(raw: bits512) { } > calcHash(someSlice); // error > calcHash(someSlice as bits512); // ok
1 parent b086bc7 commit 78c05fd

File tree

9 files changed

+198
-1
lines changed

9 files changed

+198
-1
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
fun takeAnySlice(slice: slice) {}
2+
fun getSomeSlice() { return beginCell().endCell().beginParse(); }
3+
fun getNullableBytes16(): bytes16? { return getSomeSlice() as bytes16; }
4+
5+
const someBytes = "asdf" as bytes16;
6+
const anotherBytes: bytes16 = "asdf" as bytes16;
7+
8+
fun autoInferBytes16() {
9+
if (random()) { return someBytes; }
10+
else if (random()) { return true ? "" as bytes16 : anotherBytes; }
11+
else { return someBytes!; }
12+
}
13+
14+
@method_id(101)
15+
fun test1() {
16+
var ss = beginCell().storeInt(1, 32).storeInt(2, 32).endCell().beginParse() as bits32;
17+
__expect_type(ss, "bits32");
18+
__expect_type(ss as slice, "slice");
19+
__expect_type(ss as slice?, "slice?");
20+
__expect_type(ss as bytes128, "bytes128");
21+
__expect_type(someBytes, "bytes16");
22+
__expect_type(10>3 ? (null, ss as bits1) : (ss as bytes1, null), "(bytes1?, bits1?)");
23+
return (getRemainingBitsCount(ss as slice), loadInt(mutate ss as slice, 32), (ss as slice).loadInt(32));
24+
}
25+
26+
fun test2(a: bytes8, b: bits64) {
27+
a = b as bytes8;
28+
b = a as bits64;
29+
(a as slice).loadRef();
30+
(b as slice?)!.loadRef();
31+
}
32+
33+
@method_id(103)
34+
fun test3() {
35+
var slice = beginCell().storeInt(1, 32).storeInt(2, 32).endCell().beginParse();
36+
var bq = slice as bits16?;
37+
if (bq != null) {
38+
return (bq as slice).loadInt(32 as uint8) + (slice = bq as slice).getRemainingBitsCount();
39+
}
40+
return -1;
41+
}
42+
43+
fun main() {}
44+
45+
/**
46+
@testcase | 101 | | 64 1 2
47+
@testcase | 103 | | 33
48+
*/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fun cantUnifyBytesNAndBytesM(n: bytes8, c: bytes16) {
2+
__expect_type(random() ? n : c as bytes8, "bytes8");
3+
random() ? n : c;
4+
}
5+
6+
/**
7+
@compilation_should_fail
8+
@stderr types of ternary branches are incompatible: `bytes8` and `bytes16`
9+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fun cantUnifyBytesNAndSlice(n: slice, c: bytes16) {
2+
__expect_type(random() ? n : c as slice, "slice");
3+
__expect_type(random() ? n : c as slice?, "slice?");
4+
random() ? n : c;
5+
}
6+
7+
/**
8+
@compilation_should_fail
9+
@stderr types of ternary branches are incompatible: `slice` and `bytes16`
10+
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
fun takeAnySlice(s: slice) {}
2+
3+
fun cantPassBytesNToSlice(c: bits16) {
4+
takeAnySlice(c as slice); // ok
5+
takeAnySlice(c);
6+
}
7+
8+
/**
9+
@compilation_should_fail
10+
@stderr can not pass `bits16` to `slice`
11+
@stderr takeAnySlice(c);
12+
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
fun getAnySlice() { return beginCell().endCell().beginParse(); }
2+
3+
fun cantAssignSliceToBytesN() {
4+
var c1: bytes16 = getAnySlice() as bytes16; // ok
5+
var c2: bytes16 = getAnySlice();
6+
}
7+
8+
/**
9+
@compilation_should_fail
10+
@stderr can not assign `slice` to variable of type `bytes16`
11+
@stderr var c2: bytes16 = getAnySlice();
12+
*/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fun cantAutoCastBytesNToSlice() {
2+
var b = beginCell().storeInt(1, 32).endCell().beginParse() as bits32;
3+
return b.loadInt(32);
4+
}
5+
6+
/**
7+
@compilation_should_fail
8+
@stderr can not call method for `slice` with object of type `bits32`
9+
*/

tolk/pipe-ast-to-legacy.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,22 @@ static std::vector<var_idx_t> transition_expr_to_runtime_type_impl(std::vector<v
581581
if (original_type == TypeDataCoins::create() && target_type->try_as<TypeDataIntN>()) {
582582
return rvect;
583583
}
584+
// pass `bytes32` to `slice`
585+
// in code, it's probably done with `as` operator
586+
// no changes in rvect, since bytesN is slice at TVM level
587+
if (target_type == TypeDataSlice::create() && original_type->try_as<TypeDataBytesN>()) {
588+
return rvect;
589+
}
590+
// pass `slice` to `bytes32`
591+
// same as above
592+
if (original_type == TypeDataSlice::create() && target_type->try_as<TypeDataBytesN>()) {
593+
return rvect;
594+
}
595+
// pass `bytes32` to `bytes64` / `bits128` to `bytes16`
596+
// no changes in rvect
597+
if (original_type->try_as<TypeDataBytesN>() && target_type->try_as<TypeDataBytesN>()) {
598+
return rvect;
599+
}
584600
// pass something to `unknown`
585601
// probably, it comes from `_ = rhs`, type of `_` is unknown, it's target_type of rhs
586602
// no changes in rvect

tolk/type-system.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,17 @@ TypePtr TypeDataIntN::create(bool is_unsigned, bool is_variadic, int n_bits) {
193193
return hash.register_unique(new TypeDataIntN(hash.type_id(), is_unsigned, is_variadic, n_bits));
194194
}
195195

196+
TypePtr TypeDataBytesN::create(bool is_bits, int n_width) {
197+
TypeDataTypeIdCalculation hash(7810988137199333041ULL);
198+
hash.feed_hash(is_bits);
199+
hash.feed_hash(n_width);
200+
201+
if (TypePtr existing = hash.get_existing()) {
202+
return existing;
203+
}
204+
return hash.register_unique(new TypeDataBytesN(hash.type_id(), is_bits, n_width));
205+
}
206+
196207
TypePtr TypeDataUnresolved::create(std::string&& text, SrcLocation loc) {
197208
TypeDataTypeIdCalculation hash(3680147223540048162ULL);
198209
hash.feed_string(text);
@@ -262,6 +273,11 @@ std::string TypeDataIntN::as_human_readable() const {
262273
return s_int + std::to_string(n_bits);
263274
}
264275

276+
std::string TypeDataBytesN::as_human_readable() const {
277+
std::string s_bytes = is_bits ? "bits" : "bytes";
278+
return s_bytes + std::to_string(n_width);
279+
}
280+
265281

266282
// --------------------------------------------
267283
// traverse()
@@ -376,7 +392,7 @@ bool TypeDataSlice::can_rhs_be_assigned(TypePtr rhs) const {
376392
if (rhs == this) {
377393
return true;
378394
}
379-
return rhs == TypeDataNever::create();
395+
return rhs == TypeDataNever::create(); // note, that bytesN is NOT automatically cast to slice without `as` operator
380396
}
381397

382398
bool TypeDataBuilder::can_rhs_be_assigned(TypePtr rhs) const {
@@ -469,6 +485,15 @@ bool TypeDataIntN::can_rhs_be_assigned(TypePtr rhs) const {
469485
return rhs == TypeDataNever::create(); // `int8` is NOT assignable to `int32` without `as`
470486
}
471487

488+
bool TypeDataBytesN::can_rhs_be_assigned(TypePtr rhs) const {
489+
// `slice` is NOT assignable to bytesN without `as`
490+
// `bytes32` is NOT assignable to `bytes256` and even to `bits256` without `as`
491+
if (rhs == this) {
492+
return true;
493+
}
494+
return rhs == TypeDataNever::create();
495+
}
496+
472497
bool TypeDataCoins::can_rhs_be_assigned(TypePtr rhs) const {
473498
if (rhs == this) {
474499
return true;
@@ -541,6 +566,9 @@ bool TypeDataCell::can_be_casted_with_as_operator(TypePtr cast_to) const {
541566
}
542567

543568
bool TypeDataSlice::can_be_casted_with_as_operator(TypePtr cast_to) const {
569+
if (cast_to->try_as<TypeDataBytesN>()) { // `slice` to `bytes32` / `slice` to `bits8`
570+
return true;
571+
}
544572
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) {
545573
return can_be_casted_with_as_operator(to_nullable->inner);
546574
}
@@ -630,6 +658,16 @@ bool TypeDataIntN::can_be_casted_with_as_operator(TypePtr cast_to) const {
630658
return cast_to == TypeDataInt::create() || cast_to == TypeDataCoins::create();
631659
}
632660

661+
bool TypeDataBytesN::can_be_casted_with_as_operator(TypePtr cast_to) const {
662+
if (cast_to->try_as<TypeDataBytesN>()) { // `bytes256` as `bytes512`, `bits1` as `bytes8`
663+
return true;
664+
}
665+
if (const auto* to_nullable = cast_to->try_as<TypeDataNullable>()) { // `bytes8` as `slice?`
666+
return can_be_casted_with_as_operator(to_nullable->inner);
667+
}
668+
return cast_to == TypeDataSlice::create();
669+
}
670+
633671
bool TypeDataCoins::can_be_casted_with_as_operator(TypePtr cast_to) const {
634672
if (cast_to->try_as<TypeDataIntN>()) { // `coins` as `int8`
635673
return true;
@@ -750,6 +788,16 @@ static TypePtr parse_intN(std::string_view strN, bool is_unsigned) {
750788
return TypeDataIntN::create(is_unsigned, false, n);
751789
}
752790

791+
static TypePtr parse_bytesN(std::string_view strN, bool is_bits) {
792+
int n;
793+
auto result = std::from_chars(strN.data() + 5 - static_cast<int>(is_bits), strN.data() + strN.size(), n);
794+
bool parsed = result.ec == std::errc() && result.ptr == strN.data() + strN.size();
795+
if (!parsed || n <= 0 || n > 1024) {
796+
return nullptr; // `bytes9999`, maybe it's user-defined alias, let it be unresolved
797+
}
798+
return TypeDataBytesN::create(is_bits, n);
799+
}
800+
753801
static TypePtr parse_simple_type(Lexer& lex) {
754802
switch (lex.tok()) {
755803
case tok_self:
@@ -795,6 +843,16 @@ static TypePtr parse_simple_type(Lexer& lex) {
795843
return uintN;
796844
}
797845
}
846+
if (str.size() > 4 && str.starts_with("bits")) {
847+
if (TypePtr bitsN = parse_bytesN(str, true)) {
848+
return bitsN;
849+
}
850+
}
851+
if (str.size() > 5 && str.starts_with("bytes")) {
852+
if (TypePtr bytesN = parse_bytesN(str, false)) {
853+
return bytesN;
854+
}
855+
}
798856
return TypeDataUnresolved::create(std::string(str), loc);
799857
}
800858
case tok_null:

tolk/type-system.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,29 @@ class TypeDataCoins final : public TypeData {
409409
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
410410
};
411411

412+
/*
413+
* `bytes256`, `bits512`, `bytes8` are TypeDataBytesN. At TVM level, it's just slice.
414+
* The purpose of bytesN is to be used in struct fields, describing the way of serialization (n bytes / n bits).
415+
* In this essence, bytesN is very similar to intN.
416+
* Note, that unlike intN automatically cast to/from int, bytesN does NOT auto cast to slice (without `as`).
417+
*/
418+
class TypeDataBytesN final : public TypeData {
419+
TypeDataBytesN(uint64_t type_id, bool is_bits, int n_width)
420+
: TypeData(type_id, 0, 1)
421+
, is_bits(is_bits)
422+
, n_width(n_width) {}
423+
424+
public:
425+
const bool is_bits;
426+
const int n_width;
427+
428+
static TypePtr create(bool is_bits, int n_width);
429+
430+
std::string as_human_readable() const override;
431+
bool can_rhs_be_assigned(TypePtr rhs) const override;
432+
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
433+
};
434+
412435
/*
413436
* `unknown` is a special type, which can appear in corner cases.
414437
* The type of exception argument (which can hold any TVM value at runtime) is unknown.

0 commit comments

Comments
 (0)