From 89933f0aff42b70ed2951230e7004121b8de3f16 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Wed, 16 Oct 2024 05:23:41 +0200 Subject: [PATCH 1/5] feat(docs): destructuring statement --- docs/src/content/docs/book/statements.mdx | 129 ++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/docs/src/content/docs/book/statements.mdx b/docs/src/content/docs/book/statements.mdx index 4db01b93c..5efe64e47 100644 --- a/docs/src/content/docs/book/statements.mdx +++ b/docs/src/content/docs/book/statements.mdx @@ -3,6 +3,8 @@ title: Statements description: "This page lists all the statements in Tact, which can appear anywhere in the function bodies" --- +import { Badge } from '@astrojs/starlight/components'; + The following statements can appear anywhere in the [function](/book/functions) body. ## `let` statement {#let} @@ -96,6 +98,131 @@ value += 5; // augmented assignment (one of the many, see below) ::: +## Destructuring assignment + +

+ +The destructuring assignment is a concise way to unpack [Structs][s] and [Messages][m] into distinct variables. It mirrors the [instantiation syntax](/book/expressions#instantiation), but instead of creating a new [Struct][s] or [Message][m] it binds every field or some of the fields to their respective variables. + +The syntax is derived from the [`let` statement](#let), and instead of specifying the variable name directly it involves specifying the structure type on the left side of the [assignment operator `={:tact}`](/book/operators#assignment), which corresponds to the structure type of the value on the right side. + +```tact {6} +// Definition of Example +struct Example { number: Int } + +fun basic() { + // Basic syntax of destructuring assignment (to the left of "="): + let Example { number } = Example { number: 42 }; + // ------- ------ ---------------------- + // ↑ ↑ ↑ + // | | instantiation of Example Struct + // | definition of "number" variable, derived + // | from the field "number" on Example Struct + // target structure type "Example" + // to destructure fields from + + // The syntax above is equivalent to the following series of statements: + let example = Example { number: 42 }; + let number = example.number; +} +``` + +Just like in [instantiation](/book/expressions#instantiation), the trailing comma is allowed. + +```tact +struct Example { number: Int } + +fun trailblazing() { + let Example { + number, // trailing comma inside variable list + } = Example { + number: 42, // trailing comma inside field list + }; +} +``` + +:::note + + [Augmented assignment operators](/book/operators#augmented-assignment) do not make sense for such assignments and will therefore be reported as parsing errors: + + ```tact + struct Example { number: Int } + + fun basic() { + let Example { number } += Example { number: 42 }; + // ^ this will result in the parse error: + // expected "=" + } + ``` + +::: + +To create a binding under a different variable name, specify it after the semicolon `:{:tact}`. + +```tact +// Similar definition, but this time field is called "field", not "number" +struct Example { field: Int } + +fun naming(s: Example) { + let Example { field: varFromField } = s; + // ------------ ↑ + // ↑ | + // | instance of Example Struct, received + // | as a parameter of the function "naming" + // definition of "varFromField" variable, derived + // from the field "field" on Example Struct +} +``` + +Note, that the order of bindings doesn't matter — all the fields retain their values and types under their names no matter the order in which they stand in their definition in the respective [Struct][s] or [Message][m]. + +```tact +// "first" goes first, then goes "second" +struct Two { first: Int; second: String } + +fun order(s: Two) { + let Two { second, first } = s; + // ------ ----- + // ↑ ↑ + // | this variable will be of type Int, + // | same as the "first" field on Struct Two + // this variable will be of type String, + // same as the "second" field on Struct Two +} +``` + +Destructuring assignment is exhaustive and requires specifying all the fields as variables. To deliberately ignore some of the fields, use an underscore `_{:tact}`, which would make fields value considered unused and discarded. Note, that such wildcard variable name `_{:tact}` cannot be accessed: + +```tact +// "first" goes first, then goes "second" +struct Two { first: Int; second: String } + +fun discard(s: Two) { + let Two { second: _, first } = s; + // --- + // ↑ + // discards the "second" field, only taking the "first" +} +``` + +:::caution + + At the moment, destructuring of nested [Structs][s] or [Messages][m] isn't allowed. That is, the following won't work: + + ```tact + struct First { nested: Second } + struct Second { field: Int } + + fun example() { + let prep = First { nested: Second { field: 42 } }; + let First { nested: { field: thing } } = prep; + // ^ this will result in the parse error: + // expected "_", "A".."Z", or "a".."z" + } + ``` + +::: + ## Branches Control the flow of the code. @@ -415,3 +542,5 @@ foreach (_, _ in quartiles) { ::: [int]: /book/integers +[s]: /book/structs-and-messages#structs +[m]: /book/structs-and-messages#messages From 3da7a0aac9232c41ac42ff5af9c019f53333ecda Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Wed, 16 Oct 2024 05:24:56 +0200 Subject: [PATCH 2/5] chore: CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df5c4f98b..36419e715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Docs: the `description` property to the frontmatter of the each page for better SEO: PR [#916](https://github.com/tact-lang/tact/pull/916) - Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921) - Ability to specify a compile-time method ID expression for getters: PR [#922](https://github.com/tact-lang/tact/pull/922) and PR [#932](https://github.com/tact-lang/tact/pull/932) -- Destructuring of structs and messages: PR [#856](https://github.com/tact-lang/tact/pull/856) +- Destructuring of structs and messages: PR [#856](https://github.com/tact-lang/tact/pull/856), PR [#964](https://github.com/tact-lang/tact/pull/964) ### Changed From 9889fb14e723316c0c4fd16d1cc047e07f103b14 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:51:00 +0200 Subject: [PATCH 3/5] chore: vary the examples --- docs/src/content/docs/book/statements.mdx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/src/content/docs/book/statements.mdx b/docs/src/content/docs/book/statements.mdx index 5efe64e47..87234f812 100644 --- a/docs/src/content/docs/book/statements.mdx +++ b/docs/src/content/docs/book/statements.mdx @@ -110,18 +110,29 @@ The syntax is derived from the [`let` statement](#let), and instead of specifyin // Definition of Example struct Example { number: Int } +// An arbitrary helper function +fun get42(): Example { return Example { number: 42 } } + fun basic() { // Basic syntax of destructuring assignment (to the left of "="): - let Example { number } = Example { number: 42 }; - // ------- ------ ---------------------- + let Example { number } = get42(); + // ------- ------ ------- // ↑ ↑ ↑ - // | | instantiation of Example Struct + // | | gives the Example Struct // | definition of "number" variable, derived // | from the field "number" on Example Struct // target structure type "Example" // to destructure fields from - // The syntax above is equivalent to the following series of statements: + // Same as above, but with an instantiation + // to showcase how destructuring syntax mirrors it: + let Example { number } = Example { number: 42 }; + // ---------------------- + // ↑ + // instantiation of Example Struct + + // Above examples of syntax are roughly equivalent + // to the following series of statements: let example = Example { number: 42 }; let number = example.number; } @@ -147,9 +158,10 @@ fun trailblazing() { ```tact struct Example { number: Int } + fun get42(): Example { return Example { number: 42 } } fun basic() { - let Example { number } += Example { number: 42 }; + let Example { number } += get42(); // ^ this will result in the parse error: // expected "=" } From ea429a1badd543d80dc8f1512233902bbe341c4d Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:02:36 +0200 Subject: [PATCH 4/5] feat: add the `..` syntax --- docs/src/content/docs/book/statements.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/src/content/docs/book/statements.mdx b/docs/src/content/docs/book/statements.mdx index 87234f812..45ce9d0eb 100644 --- a/docs/src/content/docs/book/statements.mdx +++ b/docs/src/content/docs/book/statements.mdx @@ -217,6 +217,20 @@ fun discard(s: Two) { } ``` +To completely ignore the rest of the fields, use `..` at the end of the list: + +```tact +struct Many { one: Int; two: Int; three: Int; fans: Int } + +fun ignore(s: Many) { + let Many { fans, .. } = s; + // -- + // ↑ + // ignores all the unspecified fields, + // defining only "fans" +} +``` + :::caution At the moment, destructuring of nested [Structs][s] or [Messages][m] isn't allowed. That is, the following won't work: From b45048c4fcdb108da94b7d927af1725017878ade Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:55:10 +0100 Subject: [PATCH 5/5] fix: suggestions from code review --- docs/src/content/docs/book/statements.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/content/docs/book/statements.mdx b/docs/src/content/docs/book/statements.mdx index 45ce9d0eb..ce363f5a3 100644 --- a/docs/src/content/docs/book/statements.mdx +++ b/docs/src/content/docs/book/statements.mdx @@ -120,7 +120,7 @@ fun basic() { // ↑ ↑ ↑ // | | gives the Example Struct // | definition of "number" variable, derived - // | from the field "number" on Example Struct + // | from the field "number" in Example Struct // target structure type "Example" // to destructure fields from @@ -182,7 +182,7 @@ fun naming(s: Example) { // | instance of Example Struct, received // | as a parameter of the function "naming" // definition of "varFromField" variable, derived - // from the field "field" on Example Struct + // from the field "field" in Example Struct } ``` @@ -199,11 +199,11 @@ fun order(s: Two) { // | this variable will be of type Int, // | same as the "first" field on Struct Two // this variable will be of type String, - // same as the "second" field on Struct Two + // same as the "second" field in Struct Two } ``` -Destructuring assignment is exhaustive and requires specifying all the fields as variables. To deliberately ignore some of the fields, use an underscore `_{:tact}`, which would make fields value considered unused and discarded. Note, that such wildcard variable name `_{:tact}` cannot be accessed: +Destructuring assignment is exhaustive and requires specifying all the fields as variables. To deliberately ignore some of the fields, use an underscore `_{:tact}`, which discards the considered field's value. Note, that such wildcard variable name `_{:tact}` cannot be accessed: ```tact // "first" goes first, then goes "second"