diff --git a/docs/v3/documentation/smart-contracts/tolk/changelog.md b/docs/v3/documentation/smart-contracts/tolk/changelog.md index f3f62f0da3..585ccaaaf8 100644 --- a/docs/v3/documentation/smart-contracts/tolk/changelog.md +++ b/docs/v3/documentation/smart-contracts/tolk/changelog.md @@ -3,6 +3,16 @@ When new versions of Tolk are released, they will be mentioned here. +## v0.9 + +1. Nullable types `int?`, `cell?`, etc.; null safety +2. Standard library (asm definitions) updated to reflect nullability +3. Smart casts, like in TypeScript in Kotlin +4. Operator `!` (non-null assertion) +5. Code after `throw` is treated unreachable +6. The `never` type + + ## v0.8 1. Syntax `tensorVar.0` and `tupleVar.0` (both for reading and writing) diff --git a/docs/v3/documentation/smart-contracts/tolk/overview.mdx b/docs/v3/documentation/smart-contracts/tolk/overview.mdx index 70ab7ad2fb..e9b428bd99 100644 --- a/docs/v3/documentation/smart-contracts/tolk/overview.mdx +++ b/docs/v3/documentation/smart-contracts/tolk/overview.mdx @@ -133,7 +133,7 @@ Anyway, no matter what language you use, you should cover your contracts with te The first released version of Tolk is v0.6, emphasizing [missing](/v3/documentation/smart-contracts/tolk/changelog#how-tolk-was-born) FunC v0.5. Here are some (yet not all and not ordered in any way) points to be investigated: -- type system improvements: nullability, fixed-size integers, union types, dictionaries +- type system improvements - structures and generics - auto-pack structures to/from cells, probably integrated with message handlers - methods for structures, generalized to cover built-in types diff --git a/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-detail.mdx b/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-detail.mdx index 1ecfeee6c0..9221a4d52e 100644 --- a/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-detail.mdx +++ b/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-detail.mdx @@ -327,8 +327,10 @@ We have the following types: - typed tuple `[T1, T2, ...]` - tensor `(T1, T2, ...)` - callables `(TArgs) -> TResult` +- nullable types `T?`, compile-time null safety - `void` (more canonical to be named `unit`, but `void` is more reliable) - `self`, to make chainable methods, described below; actually it's not a type, it can only occur instead of return type of a function +- `never` (an always-throwing function returns `never`, for example; an "impossible type" is also `never`) The type system obeys the following rules: - variable types can be specified manually or are inferred from declarations, and never change after being declared @@ -424,7 +426,7 @@ The syntax `f(...)` is also supported: ```tolk t.tuplePush(1); // ok t.tuplePush(cs); // error, can not pass slice to int -t.tuplePush(null); // ok, null is "null of type int" +t.tuplePush(null); // ok, null is "null of type int" ``` User-defined functions may also be generic: @@ -440,7 +442,7 @@ Actually, they mostly remind "template" functions. At each unique invocation, fu There may be multiple generic parameters: ```tolk -fun replaceNulls(tensor: (T1, T2), v1IfNull: T1, v2IfNull: T2): (T1, T2) { +fun replaceNulls(tensor: (T1?, T2?), v1IfNull: T1, v2IfNull: T2): (T1, T2) { var (a, b) = tensor; return (a == null ? v1IfNull : a, b == null ? v2IfNull : b); } @@ -599,8 +601,6 @@ Creating null values and checking variables on null looks very pretty now. -Note, that it does NOT mean that Tolk language has nullability. No, you can still assign `null` to an integer variable — like in FunC, just syntactically pleasant. A true nullability will be available someday, after hard work on the type system. -

✅ `throw` and `assert` keywords @@ -1024,6 +1024,161 @@ globalTuple.1.2 += 10; // "GETGLOB" + ... + "SETGLOB" ``` +

+ ✅ Nullable types `T?`, null safety, smart casts, operator `!` +

+ +Tolk has nullable types: `int?`, `cell?`, and `T?` in general (even for tensors). +Non-nullable types, such as `int` and `cell`, can never hold null values. + +The compiler enforces **null safety**: you cannot use nullable types without first checking for null. +Fortunately, thanks to smart casts, these checks integrate smoothly and organically into the code. +Smart casts are purely a compile-time feature — they do not consume gas or extra stack space. + +```tolk +var value = x > 0 ? 1 : null; // int? + +value + 5; // error +s.storeInt(value); // error + +if (value != null) { + value + 5; // ok, smart cast + s.storeInt(value); // ok, smart cast +} +``` + +Remember, that when a variable's type is not specified, it's auto inferred from assignment and never changes: +```tolk +var i = 0; +i = null; // error, can't assign `null` to `int` +i = maybeInt; // error, can't assign `int?` to `int` +``` + +Such a code will not work, you must **explicitly declare the variable as nullable**:: +```tolk +// incorrect +var i = null; +if (...) { + i = 0; // error +} + +// correct +var i: int? = null; +// or +var i = null as int?; +``` + +Smart casts (similar to TypeScript and Kotlin) make it easier dealing with nullable types, allowing code like this: +```tolk +if (lastCell != null) { + // here lastCell is `cell`, not `cell?` +} +``` +```tolk +if (lastCell == null || prevCell == null) { + return; +} +// both lastCell and prevCell are `cell` +``` +```tolk +var x: int? = ...; +if (x == null) { + x = random(); +} +// here x is `int` +``` +```tolk +while (lastCell != null) { + lastCell = lastCell.beginParse().loadMaybeRef(); +} +// here lastCell is 100% null +``` +```tolk +// t: (int, int)? +t.0 // error +t!.0 // ok +if (t.0 != null) { + t.0 // ok +} +``` + +Note, that smart casts don't work for globals, only for local vars. + +Tolk has the `!` operator (non-null assertion, compile-time only), like `!` in TypeScript and `!!` in Kotlin. +If you are "absolutely certain" that a variable is not null, +this operator allows you to skip the compiler's check. +```tolk +fun doSmth(c: cell); + +fun analyzeStorage(nCells: int, lastCell: cell?) { + if (nCells) { // then lastCell 100% not null + doSmth(lastCell!); // use ! for this fact + } +} +``` + +In practice, you'll use this operator working with low-level dicts API. +In the future, Tolk will have a high-level `map`. +For now, working with dicts will require the `!` operator. +```tolk +// it returns either (slice, true) or (null, false) +@pure +fun iDictGet(self: cell?, keyLen: int, key: int): (slice?, bool) + asm(key self keyLen) "DICTIGET" "NULLSWAPIFNOT"; + +var (cs, exists) = dict.iDictGet(...); +// if exists is true, cs is not null +if (exists) { + cs!.loadInt(32); +} +``` + +You can also declare "always-throwing functions" that return `never`: +```tolk +fun alwaysThrows(): never { + throw 123; +} + +fun f(x: int) { + if (x > 0) { + return x; + } + alwaysThrows(); + // no `return` statement needed +} +``` + +The `never` type implicitly occurs when a condition can never happen: +```tolk +var v = 0; +// prints a warning +if (v == null) { + // v is `never` + v + 10; // error, can not apply `+` `never` and `int` +} +// v is `int` again +``` + +If you face `never` in compilation errors, most likely there is a warning in preceding code. + +Non-atomic nullables are also allowed: `(int, int)?`, `(int?, int?)?`, or even `()?`. Then, +**a special "value presence" stack slot** is implicitly added. +It holds 0 if value is null, and not 0 (currently, -1) if not null: +```tolk +// t: (int, int)? +t = (1, 2); // 1 2 -1 +t = (3, 4); // 3 4 -1 +t = null; // null null 0 + +// t: ()? +t = (); // -1 +t = null; // 0 +``` + +All in all, nullability is a major step forward for type safety and reliability. +Nullable types eliminate runtime errors, enforcing correct handling of optional values. + +

✅ No tilda `~` methods, `mutate` keyword instead

diff --git a/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-short.md b/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-short.md index d79a87a8e9..4180669133 100644 --- a/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-short.md +++ b/docs/v3/documentation/smart-contracts/tolk/tolk-vs-func/in-short.md @@ -47,6 +47,7 @@ get currentCounter(): int { ... } 10. Clear and readable error messages on type mismatch 11. `bool` type support 12. Indexed access `tensorVar.0` and `tupleVar.0` support +13. Nullable types `T?`, null safety, smart casts, operator `!` #### Tooling around - JetBrains plugin exists diff --git a/src/theme/prism/prism-tolk.js b/src/theme/prism/prism-tolk.js index 438973b727..28cfee225b 100644 --- a/src/theme/prism/prism-tolk.js +++ b/src/theme/prism/prism-tolk.js @@ -18,7 +18,7 @@ } ], - 'type-hint': /\b(type|enum|int|cell|void|bool|slice|tuple|builder|continuation)\b/, + 'type-hint': /\b(type|enum|int|cell|void|never|bool|slice|tuple|builder|continuation)\b/, 'boolean': /\b(false|true|null)\b/,