Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into docs/cookbook-dedust
Browse files Browse the repository at this point in the history
  • Loading branch information
novusnota committed Nov 10, 2024
2 parents c7884d5 + b8c07e1 commit 1a626a9
Show file tree
Hide file tree
Showing 41 changed files with 3,089 additions and 453 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/tact.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js 18 for backwards compat tests
uses: actions/setup-node@v3
with:
node-version: 18
# without caching

- name: Backwards compat tests
run: |
# Temporarily ignore engines
yarn config set ignore-engines true
# Install dependencies, gen and build the compiler
yarn install
yarn clean
yarn gen
yarn build
# Test some specific things for backwards compatibility.
# It's important to restrain from using too much of Node.js 22+ features
# until it goes into maintenance LTS state and majority of users catches up
yarn jest -t 'isSubsetOf'
# Clean-up
yarn cleanall
yarn config delete ignore-engines
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921)
- Docs: Added NFTs cookbook: PR [#958](https://github.com/tact-lang/tact/pull/958)
- 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), PR [#969](https://github.com/tact-lang/tact/pull/969)
- Docs: automatic links to Web IDE from all code blocks: PR [#994](https://github.com/tact-lang/tact/pull/994)
- The `SendDefaultMode` send mode constant to the standard library: PR [#1010](https://github.com/tact-lang/tact/pull/1010)
- Docs: initial semi-automated Chinese translation of the documentation: PR [#942](https://github.com/tact-lang/tact/pull/942)
Expand All @@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Prevent inline code snippets from changing their background color: PR [#935](https://github.com/tact-lang/tact/pull/935)
- Docs: correctly handle next and previous page links at the bottom of the pages when there's a separator item in the sidebar: PR [#949](https://github.com/tact-lang/tact/pull/949)
- Docs: compilation of examples in `data-structures.mdx` and across Cookbook: PR [#917](https://github.com/tact-lang/tact/pull/917)
- `as coins` map value serialization type is now handled correctly: PR [#987](https://github.com/tact-lang/tact/pull/987)
- Type checking for `foreach` loops in trait methods: PR [#1017](https://github.com/tact-lang/tact/pull/1017)

### Release contributors

Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"uintptr",
"uninit",
"unixfs",
"varuint",
"workchain",
"xffff",
"привет"
Expand Down
155 changes: 155 additions & 0 deletions docs/src/content/docs/book/statements.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -96,6 +98,157 @@ value += 5; // augmented assignment (one of the many, see below)

:::

## Destructuring assignment

<Badge text="Available since Tact 1.6" variant="tip" size="medium"/><p/>

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 }
// 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 } = get42();
// ------- ------ -------
// ↑ ↑ ↑
// | | gives the Example Struct
// | definition of "number" variable, derived
// | from the field "number" in Example Struct
// target structure type "Example"
// to destructure fields from
// 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;
}
```

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 get42(): Example { return Example { number: 42 } }
fun basic() {
let Example { number } += get42();
// ^ 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" in 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 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 discards the considered field's value. 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"
}
```

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:

```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.
Expand Down Expand Up @@ -415,3 +568,5 @@ foreach (_, _ in quartiles) {
:::

[int]: /book/integers
[s]: /book/structs-and-messages#structs
[m]: /book/structs-and-messages#messages
4 changes: 3 additions & 1 deletion scripts/copy-files.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as glob from "glob";

const cp = async (fromGlob: string, toPath: string) => {
for await (const file of fs.glob(fromGlob)) {
const files = glob.sync(fromGlob);
for (const file of files) {
await fs.copyFile(file, path.join(toPath, path.basename(file)));
}
};
Expand Down
16 changes: 16 additions & 0 deletions src/abi/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ export const MapFunctions: Map<string, AbiFunction> = new Map([
} else if (self.valueAs?.startsWith("uint")) {
vBits = parseInt(self.valueAs.slice(4), 10);
vKind = "uint";
} else if (self.valueAs?.startsWith("coins")) {
vKind = "coins";
ctx.used(`__tact_dict_set_${kind}_${vKind}`);
return `${resolved[0]}~__tact_dict_set_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]})`;
}
ctx.used(`__tact_dict_set_${kind}_${vKind}`);
return `${resolved[0]}~__tact_dict_set_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]}, ${vBits})`;
Expand Down Expand Up @@ -226,6 +230,10 @@ export const MapFunctions: Map<string, AbiFunction> = new Map([
} else if (self.valueAs?.startsWith("uint")) {
vBits = parseInt(self.valueAs.slice(4), 10);
vKind = "uint";
} else if (self.valueAs?.startsWith("coins")) {
vKind = "coins";
ctx.used(`__tact_dict_get_${kind}_${vKind}`);
return `__tact_dict_get_${kind}_${vKind}(${resolved[0]}, ${bits}, ${resolved[1]})`;
}
ctx.used(`__tact_dict_get_${kind}_${vKind}`);
return `__tact_dict_get_${kind}_${vKind}(${resolved[0]}, ${bits}, ${resolved[1]}, ${vBits})`;
Expand Down Expand Up @@ -571,6 +579,10 @@ export const MapFunctions: Map<string, AbiFunction> = new Map([
} else if (self.valueAs?.startsWith("uint")) {
vBits = parseInt(self.valueAs.slice(4), 10);
vKind = "uint";
} else if (self.valueAs?.startsWith("coins")) {
vKind = "coins";
ctx.used(`__tact_dict_replace_${kind}_${vKind}`);
return `${resolved[0]}~__tact_dict_replace_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]})`;
}
ctx.used(`__tact_dict_replace_${kind}_${vKind}`);
return `${resolved[0]}~__tact_dict_replace_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]}, ${vBits})`;
Expand Down Expand Up @@ -649,6 +661,10 @@ export const MapFunctions: Map<string, AbiFunction> = new Map([
} else if (self.valueAs?.startsWith("uint")) {
vBits = parseInt(self.valueAs.slice(4), 10);
vKind = "uint";
} else if (self.valueAs?.startsWith("coins")) {
vKind = "coins";
ctx.used(`__tact_dict_replaceget_${kind}_${vKind}`);
return `${resolved[0]}~__tact_dict_replaceget_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]})`;
}
ctx.used(`__tact_dict_replaceget_${kind}_${vKind}`);
return `${resolved[0]}~__tact_dict_replaceget_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]}, ${vBits})`;
Expand Down
10 changes: 9 additions & 1 deletion src/bindings/typescript/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@ type MapSerializerDescrKey =
| { kind: "address" };
type MapSerializerDescrValue =
| { kind: "int" | "uint"; bits: number }
| { kind: "varuint"; length: number }
| { kind: "boolean" }
| { kind: "address" }
| { kind: "cell" }
Expand Down Expand Up @@ -665,6 +666,9 @@ function getValueParser(src: MapSerializerDescrValue) {
return `Dictionary.Values.BigUint(${src.bits})`;
}
}
case "varuint": {
return `Dictionary.Values.BigVarUint(${src.length})`;
}
case "address": {
return "Dictionary.Values.Address()";
}
Expand Down Expand Up @@ -739,7 +743,7 @@ const map: Serializer<MapSerializerDescr> = {
) {
value = { kind: "uint", bits: 256 };
} else if (src.valueFormat === "coins") {
value = { kind: "uint", bits: 124 };
value = { kind: "varuint", length: 4 };
}
}
if (src.value === "address") {
Expand Down Expand Up @@ -809,6 +813,10 @@ const map: Serializer<MapSerializerDescr> = {
}
}
break;
case "varuint": {
valueT = `bigint`;
break;
}
case "boolean":
{
valueT = `boolean`;
Expand Down
Loading

0 comments on commit 1a626a9

Please sign in to comment.