Open anton-trunov opened 5 months ago
Couple of ideas:
get
attribute to be its own function type to make it clear that they're special:contract Example {
field: Int;
// Like this (removed `fun` keyword here):
get field(): Int {
return self.field;
}
// Or maybe even like this, to make it clear that they're off-chain
// (and would therefore be called "offchain functions" and not "getter functions"):
off getField(): Int {
return self.field;
}
offchain getField(): Int {
return self.field;
}
}
- Commas (
,
) should separate fields in in struct/message declarations to make it consistent with struct/message definitions;
Not sure here, as this will also influence fields (persistent state variables) in contracts and traits — they use semicolons in their declarations as of now
Not sure here, as this will also influence fields (persistent state variables) in contracts and traits — they use semicolons in their declarations as of now
It's fine, though. Syntactically it's included between a pair of curly braces, so there won't be any grammar conflicts. And there is a nice principle saying that declarations and definitions should resemble each other. And it makes it resemble TS more, which is what we target in terms of syntax
Added a couple more bullet points:
storage_var <- expression
;self
, for example, storage.var
(we might even want to hint at it syntactically with storage { var : Type ... }
declaration inside a contract);{1: "foo", 2: "bar"}
), map access (map[key]
), map updates (map[key] = val
and map[key] += inc
, etc.)send(SendParameters{value: amount, to: self.owner, mode: mode})
we can have something like send {value: amount, to: self.owner, mode: mode}
;emptyMap
by default, in other words m: map<Int, Int>;
without the corresponding entry in init
should produce a compilation error;
- Tool to help migrate Tact v1 projects to Tact v2
We can use ast-grep, which uses Tree-sitter to perform AST-based search and replace. Additionally, it can lint code with the visually same error reports like Rust does, provided that we specify linting rules — this can be a starting point for our linters.
Apart from CLI, it also has a Node.js binding @ast-grep/napi
with jQuery-like utility methods to traverse and manipulate syntax tree nodes
@novusnota I was thinking we can have a tool that combines Tact v1 parser and Tact v2 code formatter: this way we can migrate contracts if we only have local syntactic changes. Plus we can do some more massaging of the original contract, like explicit map initializations.
@anton-trunov Oh, so you want for code formatter to not only format, but also perform lint-like fixes? I thought of formatter purely in terms of whitespace fixing, and leaving the actual code replacements to the tact lint --fix
or similar
struct
, message
, contract
, init
, bounced
, external
, primitive
, map
, and receive
, get
(or whatever we change those to) as identifiers and make those keywords, e.g. let struct: Int = 0
will become illegal;Oh, so you want for code formatter to not only format, but also perform lint-like fixes?
@novusnota Not at all, I was talking about creating a tool like tact-migrate-v1-to-v2
which would combine the parser for Tact v1 and the future code formatter for Tact v2 + plus some more automatic migrations that are possibly not purely syntactical
Disallow initializing maps with emptyMap by default, in other words m: map<Int, Int>; without the corresponding entry in init should produce a compilation error;
Why? I have multiple use cases where I need to declare an empty map without adding any entries in init (eg. RBAC).
Why? I have multiple use cases where I need to declare an empty map without adding any entries in init (eg. RBAC).
Semantically nothing will be changed here, the user will have to just explicitly state the initial value for map. This part should be migrated automatically
Any plans to revive this? https://github.com/tact-lang/docs-obsolete/blob/main/tact-design.md#interfaces
I'd love to see impl MyTrait for MyStruct { }
Yep, we definitely need adhoc polymorphism in Tact
Not sure here, as this will also influence fields (persistent state variables) in contracts and traits — they use semicolons in their declarations as of now
It's fine, though. Syntactically it's included between a pair of curly braces, so there won't be any grammar conflicts. And there is a nice principle saying that declarations and definitions should resemble each other. And it makes it resemble TS more, which is what we target in terms of syntax
Well, as struct
and message
are essentially declaring a new type and we're trying to target TS in terms of syntax, we should also consider that in TS type declarations use semicolons (like we do) and not commas, see:
Albeit it's just a suggestion — do we need a bounced<>
type wrapper going forward? Maybe, having the bounced()
receiver is clear enough?
... as
struct
andmessage
are essentially declaring a new type ...
Regarning struct
vs. message
— users of Tact may have a confusion between message
as type definitions and messages as means of contract interaction on the blockchain.
I'm not sure if that's a valid issue (need to gather community feedback here), but it may be beneficial to merge struct
and message
definitions under the same name, say, struct
or even tuple
, to later make it closer to an advanced successor of TVMs tuples.
That way we could clarify things and make it simpler to read and write Tact code. And that would also alleviate the need to capitalize Messages/Structs in the docs to refer to the types and not to the communication itself :)
I think the current definition of struct
and message
is clear enough and they offer clear separation of usage scenarios.
If we were to merge the two then what would become message(0x1234) M {}
? Does struct(0x1234) M {}
even make sense?
Equality comparisons via hashing should be explicit. We compare cells and slices via hashing but use == / != which hides this fact. Switching to something like ==# / !=# makes it more obvious;
Maybe better to make like in Kotlin/JS/TS/PHP using ===
and !===
operator? Its has also ligatures in most of monospace fonts
Get rid of multi-line /**/
comments. Instead, use variations of single-line comments //
:
//!
as the top-level comments describing the current file (may be omitted, I guess)///
(or regular //
, for super-simplification!) as documentation comments for the lines that follow, as it's done in Zig, Rust, Solidity, and, to some extent, Dart.Current multiline doc-comments are, in fact, written as if they're multiple single-line comments, because every line is prefixed by *
. It's very redundant fact of JSDoc and JavaDoc, which removes the whole point (IMO) of having them over just a bunch of consecutive single-line ones.
It's much easier for users to type and maintain single-line //
comments and their series, especially without the advanced help from the editor. More comments, more documentation, more clarity!
It's much easier for us to NOT have multi-line comments, as they're the only token in Tact which can span multiple lines and completely change the list of tokens and parsed AST of the rest of the file as soon as the beginning /*
appears. Not having any multi-line tokens would significantly ease the lexing phase from the perspective of incremental updates (we can now lex and re-lex by lines, essentially making way for lexing in-parallel, like Zig does), but this would also improve the performance of lexing and parsing phases! Blazing-fast AST generation for compiler and for tooling.
Great suggestion! Looks like it's about time to move this issue to its own repo and continue discussions in separate issues there. Wdyt?
Well, yeah, this can be moved to tact-lang/roadmap or to some more RFC-specific repo and then referenced from this issue
We may try to add direct TVM assembly function wrappers, similar to how it can be done in FunC: RETALT example. This will remove the need to write them in FunC, and then import and wrap in a native function in Tact — we would just write assembly ourselves.
Perhaps, it's best to add another attribute to native functions in Tact, so that we have @asm("...")
in addition to the existing @name(func_name_here)
:
@asm("RETALT")
native returnAlt();
// or without a string literal, similar to @name(...)
@asm(RETALT)
native returnAlt();
// and, perhaps, also allow replacing parentheses () with braces {}
// to write multi-line assembly right between them
@asm{
<{
}>CONT // c
0 SETNUMARGS // c'
2 PUSHINT // c' 2
SWAP // 2 c'
1 -1 SETCONTARGS
} native stackOverflow();
Syntax aspects are up to discussion though, not 100% sure how this should be arranged :)
This is a very incomplete draft proposal for the next major version of Tact. Comments are most welcome.
Grammar
...
(or something else) -- useful for parsing incomplete examples in docs, where the triple dots mean "some code skipped";struct
,message
,contract
,init
,bounced
,external
,primitive
,map
, andreceive
,get
(or whatever we change those to) as identifiers and make those keywords, e.g.let struct: Int = 0
will become illegal;if
statement always have parentheses around its condition, or drop parens forwhile
,repeat
,do-until
;;
a statement separator, not a statement terminator, but allow;
for the last statement in a block;,
) should separate fields in in struct/message declarations to make it consistent with struct/message definitions;Int
type ascriptions for contract for storage variables: e.g.x : int32
instead ofx : Int as int32
;storage_var <- expression
;self
, for example,storage.var
(we might even want to hint at it syntactically withstorage { var : Type ... }
declaration inside a contract);{1: "foo", 2: "bar"}
), map access (map[key]
), map updates (map[key] = val
andmap[key] += inc
, etc.);map
type identifier:Map<K, V>
;==
/!=
which hides this fact. Switching to something like==#
/!=#
makes it more obvious;send(SendParameters{value: amount, to: self.owner, mode: mode})
we can have something likesend {value: amount, to: self.owner, mode: mode}
;send
can actually deploy a contract, it should be evident in the call, something likedeploy {...}
(e.g.send
wouldn't be able to deploy, onlydeploy
could);sender()
in contract initializers (init
) should be calleddeployer()
;/**/
comments in favor of semantic single-line comments, see: https://github.com/tact-lang/tact/issues/249#issuecomment-2102765900Semantical changes
null
to mean "empty map" (actually we might want to rethink our design of optionals and removenull
completely);==
/!=
only accept expressions of the same type: no more implicit type conversions allowing comparingInt
andInt?
;emptyMap
by default, in other wordsm: map<Int, Int>;
without the corresponding entry ininit
should produce a compilation error;==
and!=
: it should be evident that these are not trivial operations in terms of gas consumption;Int
;SendPayGasSeparately
naming, see #149;send
should support sendingStateInit
directly without destructuring it into thecode
anddata
parts (this can, for instance, simplify theforward
method from theBase
trait);Time
type and possiblyTimeInterval
type too, plus some primitives for working with absolute time and time intervals;now()
builtin should return values of the typeTime
;FunC, Asm, support and interop
asm
blocks support (see here)Standard library, base trait, built-in functions
BaseTrait
's traitsforward
,notify
andreply
are not very descriptive and should be renamed;BaseTrait
should be renamed and explicitly used;Tooling