diff --git a/CHANGELOG.md b/CHANGELOG.md index 8712e6191..df5c4f98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +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) ### Changed diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index 980a57f9c..25cc63cf6 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -465,6 +465,26 @@ export function writeStatement( return; } + case "statement_destruct": { + const t = getExpType(ctx.ctx, f.expression); + if (t.kind !== "ref") { + throwInternalCompilerError( + `invalid destruct expression kind: ${t.kind}`, + f.expression.loc, + ); + } + const ty = getType(ctx.ctx, t.name); + const ids = ty.fields.map((field) => { + const id = f.identifiers.get(field.name); + return id === undefined || isWildcard(id[1]) + ? "_" + : funcIdOf(id[1]); + }); + ctx.append( + `var (${ids.join(", ")}) = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + return; + } } throw Error("Unknown statement kind"); diff --git a/src/grammar/__snapshots__/grammar.spec.ts.snap b/src/grammar/__snapshots__/grammar.spec.ts.snap index f4b766283..333d3dc1a 100644 --- a/src/grammar/__snapshots__/grammar.spec.ts.snap +++ b/src/grammar/__snapshots__/grammar.spec.ts.snap @@ -183,6 +183,16 @@ Line 6, col 1: " `; +exports[`grammar should fail destructuring-duplicate-source-id 1`] = ` +"Syntax error: :15:19: Duplicate destructuring field: 'a' +Line 15, col 19: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a: x, a: y } = s; + ^~~~ + 16 | return x + y; +" +`; + exports[`grammar should fail expr-fun-call-trailing-comma-no-args 1`] = ` "Syntax error: :6:14: Empty argument list should not have a dangling comma. Line 6, col 14: @@ -454,7 +464,7 @@ Line 1, col 9: `; exports[`grammar should fail ident-cannot-be-if-reserved-word 1`] = ` -":2:9: Parse error: expected not a reservedWord +":2:9: Parse error: expected "A".."Z" or not a reservedWord Line 2, col 9: 1 | fun hello(): Int { @@ -8063,6 +8073,835 @@ exports[`grammar should parse stmt-augmented-assign-logic 1`] = ` } `; +exports[`grammar should parse stmt-destructuring 1`] = ` +{ + "id": 140, + "imports": [], + "items": [ + { + "fields": [ + { + "as": null, + "id": 4, + "initializer": null, + "kind": "field_decl", + "loc": a: Int, + "name": { + "id": 2, + "kind": "id", + "loc": a, + "text": "a", + }, + "type": { + "id": 3, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "as": null, + "id": 7, + "initializer": null, + "kind": "field_decl", + "loc": b: Int, + "name": { + "id": 5, + "kind": "id", + "loc": b, + "text": "b", + }, + "type": { + "id": 6, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "as": null, + "id": 10, + "initializer": null, + "kind": "field_decl", + "loc": c: Int, + "name": { + "id": 8, + "kind": "id", + "loc": c, + "text": "c", + }, + "type": { + "id": 9, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + ], + "id": 11, + "kind": "struct_decl", + "loc": struct S { + a: Int; + b: Int; + c: Int; +}, + "name": { + "id": 1, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "fields": [ + { + "as": null, + "id": 15, + "initializer": null, + "kind": "field_decl", + "loc": a: Int, + "name": { + "id": 13, + "kind": "id", + "loc": a, + "text": "a", + }, + "type": { + "id": 14, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "as": null, + "id": 18, + "initializer": null, + "kind": "field_decl", + "loc": b: Int, + "name": { + "id": 16, + "kind": "id", + "loc": b, + "text": "b", + }, + "type": { + "id": 17, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + ], + "id": 19, + "kind": "message_decl", + "loc": message M { + a: Int; + b: Int; +}, + "name": { + "id": 12, + "kind": "type_id", + "loc": M, + "text": "M", + }, + "opcode": null, + }, + { + "attributes": [], + "id": 139, + "kind": "function_def", + "loc": fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c } = s; + let S { a: a1 } = s; + let S { b: b1 } = s; + let S { c: c1 } = s; + let S { a: a2, b: b2 } = s; + let S { a: a3, c: c3 } = s; + let S { b: b4, c: c4 } = s; + + let m = M{ a: 1, b: 2 }; + let M { a: a_m, b: b_m } = m; + + return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m; +}, + "name": { + "id": 20, + "kind": "id", + "loc": testFunc, + "text": "testFunc", + }, + "params": [], + "return": { + "id": 21, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + "statements": [ + { + "expression": { + "args": [ + { + "field": { + "id": 24, + "kind": "id", + "loc": a, + "text": "a", + }, + "id": 26, + "initializer": { + "base": 10, + "id": 25, + "kind": "number", + "loc": 1, + "value": 1n, + }, + "kind": "struct_field_initializer", + "loc": a: 1, + }, + { + "field": { + "id": 27, + "kind": "id", + "loc": b, + "text": "b", + }, + "id": 29, + "initializer": { + "base": 10, + "id": 28, + "kind": "number", + "loc": 2, + "value": 2n, + }, + "kind": "struct_field_initializer", + "loc": b: 2, + }, + { + "field": { + "id": 30, + "kind": "id", + "loc": c, + "text": "c", + }, + "id": 32, + "initializer": { + "base": 10, + "id": 31, + "kind": "number", + "loc": 3, + "value": 3n, + }, + "kind": "struct_field_initializer", + "loc": c: 3, + }, + ], + "id": 33, + "kind": "struct_instance", + "loc": S{ a: 1, b: 2, c: 3 }, + "type": { + "id": 23, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + "id": 34, + "kind": "statement_let", + "loc": let s = S{ a: 1, b: 2, c: 3 };, + "name": { + "id": 22, + "kind": "id", + "loc": s, + "text": "s", + }, + "type": null, + }, + { + "expression": { + "id": 45, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 46, + "identifiers": Map { + "a" => [ + { + "id": 36, + "kind": "id", + "loc": a, + "text": "a", + }, + { + "id": 37, + "kind": "id", + "loc": a, + "text": "a", + }, + ], + "b" => [ + { + "id": 39, + "kind": "id", + "loc": b, + "text": "b", + }, + { + "id": 40, + "kind": "id", + "loc": b, + "text": "b", + }, + ], + "c" => [ + { + "id": 42, + "kind": "id", + "loc": c, + "text": "c", + }, + { + "id": 43, + "kind": "id", + "loc": c, + "text": "c", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { a, b, c } = s;, + "type": { + "id": 35, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "id": 51, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 52, + "identifiers": Map { + "a" => [ + { + "id": 48, + "kind": "id", + "loc": a, + "text": "a", + }, + { + "id": 49, + "kind": "id", + "loc": a1, + "text": "a1", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { a: a1 } = s;, + "type": { + "id": 47, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "id": 57, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 58, + "identifiers": Map { + "b" => [ + { + "id": 54, + "kind": "id", + "loc": b, + "text": "b", + }, + { + "id": 55, + "kind": "id", + "loc": b1, + "text": "b1", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { b: b1 } = s;, + "type": { + "id": 53, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "id": 63, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 64, + "identifiers": Map { + "c" => [ + { + "id": 60, + "kind": "id", + "loc": c, + "text": "c", + }, + { + "id": 61, + "kind": "id", + "loc": c1, + "text": "c1", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { c: c1 } = s;, + "type": { + "id": 59, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "id": 72, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 73, + "identifiers": Map { + "a" => [ + { + "id": 66, + "kind": "id", + "loc": a, + "text": "a", + }, + { + "id": 67, + "kind": "id", + "loc": a2, + "text": "a2", + }, + ], + "b" => [ + { + "id": 69, + "kind": "id", + "loc": b, + "text": "b", + }, + { + "id": 70, + "kind": "id", + "loc": b2, + "text": "b2", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { a: a2, b: b2 } = s;, + "type": { + "id": 65, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "id": 81, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 82, + "identifiers": Map { + "a" => [ + { + "id": 75, + "kind": "id", + "loc": a, + "text": "a", + }, + { + "id": 76, + "kind": "id", + "loc": a3, + "text": "a3", + }, + ], + "c" => [ + { + "id": 78, + "kind": "id", + "loc": c, + "text": "c", + }, + { + "id": 79, + "kind": "id", + "loc": c3, + "text": "c3", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { a: a3, c: c3 } = s;, + "type": { + "id": 74, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "id": 90, + "kind": "id", + "loc": s, + "text": "s", + }, + "id": 91, + "identifiers": Map { + "b" => [ + { + "id": 84, + "kind": "id", + "loc": b, + "text": "b", + }, + { + "id": 85, + "kind": "id", + "loc": b4, + "text": "b4", + }, + ], + "c" => [ + { + "id": 87, + "kind": "id", + "loc": c, + "text": "c", + }, + { + "id": 88, + "kind": "id", + "loc": c4, + "text": "c4", + }, + ], + }, + "kind": "statement_destruct", + "loc": let S { b: b4, c: c4 } = s;, + "type": { + "id": 83, + "kind": "type_id", + "loc": S, + "text": "S", + }, + }, + { + "expression": { + "args": [ + { + "field": { + "id": 94, + "kind": "id", + "loc": a, + "text": "a", + }, + "id": 96, + "initializer": { + "base": 10, + "id": 95, + "kind": "number", + "loc": 1, + "value": 1n, + }, + "kind": "struct_field_initializer", + "loc": a: 1, + }, + { + "field": { + "id": 97, + "kind": "id", + "loc": b, + "text": "b", + }, + "id": 99, + "initializer": { + "base": 10, + "id": 98, + "kind": "number", + "loc": 2, + "value": 2n, + }, + "kind": "struct_field_initializer", + "loc": b: 2, + }, + ], + "id": 100, + "kind": "struct_instance", + "loc": M{ a: 1, b: 2 }, + "type": { + "id": 93, + "kind": "type_id", + "loc": M, + "text": "M", + }, + }, + "id": 101, + "kind": "statement_let", + "loc": let m = M{ a: 1, b: 2 };, + "name": { + "id": 92, + "kind": "id", + "loc": m, + "text": "m", + }, + "type": null, + }, + { + "expression": { + "id": 109, + "kind": "id", + "loc": m, + "text": "m", + }, + "id": 110, + "identifiers": Map { + "a" => [ + { + "id": 103, + "kind": "id", + "loc": a, + "text": "a", + }, + { + "id": 104, + "kind": "id", + "loc": a_m, + "text": "a_m", + }, + ], + "b" => [ + { + "id": 106, + "kind": "id", + "loc": b, + "text": "b", + }, + { + "id": 107, + "kind": "id", + "loc": b_m, + "text": "b_m", + }, + ], + }, + "kind": "statement_destruct", + "loc": let M { a: a_m, b: b_m } = m;, + "type": { + "id": 102, + "kind": "type_id", + "loc": M, + "text": "M", + }, + }, + { + "expression": { + "id": 137, + "kind": "op_binary", + "left": { + "id": 135, + "kind": "op_binary", + "left": { + "id": 133, + "kind": "op_binary", + "left": { + "id": 131, + "kind": "op_binary", + "left": { + "id": 129, + "kind": "op_binary", + "left": { + "id": 127, + "kind": "op_binary", + "left": { + "id": 125, + "kind": "op_binary", + "left": { + "id": 123, + "kind": "op_binary", + "left": { + "id": 121, + "kind": "op_binary", + "left": { + "id": 119, + "kind": "op_binary", + "left": { + "id": 117, + "kind": "op_binary", + "left": { + "id": 115, + "kind": "op_binary", + "left": { + "id": 113, + "kind": "op_binary", + "left": { + "id": 111, + "kind": "id", + "loc": a, + "text": "a", + }, + "loc": a + b, + "op": "+", + "right": { + "id": 112, + "kind": "id", + "loc": b, + "text": "b", + }, + }, + "loc": a + b + c, + "op": "+", + "right": { + "id": 114, + "kind": "id", + "loc": c, + "text": "c", + }, + }, + "loc": a + b + c + a1, + "op": "+", + "right": { + "id": 116, + "kind": "id", + "loc": a1, + "text": "a1", + }, + }, + "loc": a + b + c + a1 + b1, + "op": "+", + "right": { + "id": 118, + "kind": "id", + "loc": b1, + "text": "b1", + }, + }, + "loc": a + b + c + a1 + b1 + c1, + "op": "+", + "right": { + "id": 120, + "kind": "id", + "loc": c1, + "text": "c1", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2, + "op": "+", + "right": { + "id": 122, + "kind": "id", + "loc": a2, + "text": "a2", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2, + "op": "+", + "right": { + "id": 124, + "kind": "id", + "loc": b2, + "text": "b2", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3, + "op": "+", + "right": { + "id": 126, + "kind": "id", + "loc": a3, + "text": "a3", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3, + "op": "+", + "right": { + "id": 128, + "kind": "id", + "loc": c3, + "text": "c3", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4, + "op": "+", + "right": { + "id": 130, + "kind": "id", + "loc": b4, + "text": "b4", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4, + "op": "+", + "right": { + "id": 132, + "kind": "id", + "loc": c4, + "text": "c4", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m, + "op": "+", + "right": { + "id": 134, + "kind": "id", + "loc": a_m, + "text": "a_m", + }, + }, + "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m, + "op": "+", + "right": { + "id": 136, + "kind": "id", + "loc": b_m, + "text": "b_m", + }, + }, + "id": 138, + "kind": "statement_return", + "loc": return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m;, + }, + ], + }, + ], + "kind": "module", +} +`; + exports[`grammar should parse stmt-if 1`] = ` { "id": 16, diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index 6ab4cc9ad..cc75370b2 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -204,7 +204,8 @@ export type AstStatement = | AstStatementRepeat | AstStatementTry | AstStatementTryCatch - | AstStatementForEach; + | AstStatementForEach + | AstStatementDestruct; export type AstStatementLet = { kind: "statement_let"; @@ -320,6 +321,16 @@ export type AstStatementForEach = { loc: SrcInfo; }; +export type AstStatementDestruct = { + kind: "statement_destruct"; + type: AstTypeId; + /** field name -> [field id, local id] */ + identifiers: Map; + expression: AstExpression; + id: number; + loc: SrcInfo; +}; + // // Types // @@ -573,6 +584,14 @@ export const selfId: AstId = { loc: dummySrcInfo, }; +export type AstDestructMapping = { + kind: "destruct_mapping"; + field: AstId; + name: AstId; + id: number; + loc: SrcInfo; +}; + export type AstNumber = { kind: "number"; base: AstNumberBase; @@ -684,6 +703,7 @@ export type AstReceiverKind = export type AstNode = | AstFuncId + | AstDestructMapping | AstExpression | AstStatement | AstTypeDecl diff --git a/src/grammar/compare.ts b/src/grammar/compare.ts index eb5ab0251..d744caecc 100644 --- a/src/grammar/compare.ts +++ b/src/grammar/compare.ts @@ -52,6 +52,8 @@ import { AstFuncId, AstAsmFunctionDef, AstAsmInstruction, + AstDestructMapping, + AstStatementDestruct, } from "./ast"; import { AstRenamer } from "./rename"; import { throwInternalCompilerError } from "../errors"; @@ -551,6 +553,64 @@ export class AstComparator { ); } + case "destruct_mapping": { + const { + field: destructMappingField1, + name: destructMappingName1, + } = node1 as AstDestructMapping; + const { + field: destructMappingField2, + name: destructMappingName2, + } = node2 as AstDestructMapping; + return ( + this.compare( + destructMappingField1, + destructMappingField2, + ) && + this.compare(destructMappingName1, destructMappingName2) + ); + } + + case "statement_destruct": { + const { + type: destructType1, + identifiers: destructIdentifiers1, + expression: destructExpression1, + } = node1 as AstStatementDestruct; + const { + type: destructType2, + identifiers: destructIdentifiers2, + expression: destructExpression2, + } = node2 as AstStatementDestruct; + const sortedIdentifiers1 = Array.from( + destructIdentifiers1.values(), + ).sort(); + const sortedIdentifiers2 = Array.from( + destructIdentifiers2.values(), + ).sort(); + if (sortedIdentifiers1.length !== sortedIdentifiers2.length) { + return false; + } + for (let i = 0; i < sortedIdentifiers1.length; i++) { + if ( + !this.compare( + sortedIdentifiers1[i]![0], + sortedIdentifiers2[i]![0], + ) || + !this.compare( + sortedIdentifiers1[i]![1], + sortedIdentifiers2[i]![1], + ) + ) { + return false; + } + } + return ( + this.compare(destructType1, destructType2) && + this.compare(destructExpression1, destructExpression2) + ); + } + case "type_id": { const { text: typeIdText1 } = node1 as AstTypeId; const { text: typeIdText2 } = node2 as AstTypeId; diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index d4f38897e..09a5cf2fe 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -126,6 +126,7 @@ Tact { | StatementUntil | StatementTry | StatementForEach + | StatementDestruct StatementBlock = "{" Statement* "}" @@ -157,6 +158,11 @@ Tact { StatementForEach = foreach "(" id "," id "in" Expression ")" "{" Statement* "}" + StatementDestruct = let typeId "{" ListOf ","? "}" "=" Expression (";" | &"}") + + DestructItem = id ":" id --regular + | id --punned + Expression = ExpressionConditional ExpressionConditional = ExpressionOr "?" ExpressionOr ":" ExpressionConditional --ternary diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index ba5d8c7d3..840bc0eff 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -23,6 +23,7 @@ import { AstImport, AstConstantDef, AstNumberBase, + AstId, } from "./ast"; import { throwParseError, throwSyntaxError } from "../errors"; import { checkVariableName } from "./checkVariableName"; @@ -1012,6 +1013,40 @@ semantics.addOperation("astOfStatement", { loc: createRef(this), }); }, + StatementDestruct( + _letKwd, + typeId, + _lparen, + identifiers, + _optTrailingComma, + _rparen, + _equals, + expression, + _semicolon, + ) { + return createAstNode({ + kind: "statement_destruct", + type: typeId.astOfType(), + identifiers: identifiers + .asIteration() + .children.reduce((map, item) => { + const destructItem = item.astOfExpression(); + if (map.has(destructItem.field.text)) { + throwSyntaxError( + `Duplicate destructuring field: '${destructItem.field.text}'`, + destructItem.loc, + ); + } + map.set(destructItem.field.text, [ + destructItem.field, + destructItem.name, + ]); + return map; + }, new Map()), + expression: expression.astOfExpression(), + loc: createRef(this), + }); + }, }); semantics.addOperation("astOfType", { @@ -1140,6 +1175,22 @@ semantics.addOperation("astOfExpression", { loc: createRef(this), }); }, + DestructItem_punned(id) { + return createAstNode({ + kind: "destruct_mapping", + field: id.astOfExpression(), + name: id.astOfExpression(), + loc: createRef(this), + }); + }, + DestructItem_regular(idFrom, _colon, id) { + return createAstNode({ + kind: "destruct_mapping", + field: idFrom.astOfExpression(), + name: id.astOfExpression(), + loc: createRef(this), + }); + }, ExpressionAdd_add(left, _plus, right) { return createAstNode({ kind: "op_binary", diff --git a/src/grammar/hash.ts b/src/grammar/hash.ts index 81512e6b2..3a1582f13 100644 --- a/src/grammar/hash.ts +++ b/src/grammar/hash.ts @@ -129,6 +129,8 @@ export class AstHasher { return `${node.kind}|${this.hashStatements(node.statements)}|${this.hash(node.catchName)}|${this.hashStatements(node.catchStatements)}`; case "statement_foreach": return `${node.kind}|${this.hash(node.map)}|${this.hashStatements(node.statements)}`; + case "statement_destruct": + return `${node.kind}|${this.hash(node.type)}|${this.hashDestructIdentifiers(Array.from(node.identifiers.values()))}|${this.hash(node.expression)}`; // Expressions case "op_binary": return `${node.kind}|${node.op}|${this.hash(node.left)}|${this.hash(node.right)}`; @@ -193,6 +195,13 @@ export class AstHasher { } } + private hashDestructIdentifiers(identifiers: [AstId, AstId][]): string { + const identifiersHash = identifiers + .map(([key, value]) => `${this.hash(key)}|${this.hash(value)}`) + .join("|"); + return identifiersHash; + } + private hashStructDecl(node: AstStructDecl): string { const fieldsHash = this.hashFields(node.fields); return `struct|${fieldsHash}`; diff --git a/src/grammar/iterators.ts b/src/grammar/iterators.ts index 5ac36dc14..8db047100 100644 --- a/src/grammar/iterators.ts +++ b/src/grammar/iterators.ts @@ -134,6 +134,13 @@ export function traverse(node: AstNode, callback: (node: AstNode) => void) { if (node.type) traverse(node.type, callback); traverse(node.expression, callback); break; + case "statement_destruct": + node.identifiers.forEach(([field, name], _) => { + traverse(field, callback); + traverse(name, callback); + }); + traverse(node.expression, callback); + break; case "statement_return": if (node.expression) traverse(node.expression, callback); break; @@ -195,6 +202,10 @@ export function traverse(node: AstNode, callback: (node: AstNode) => void) { traverse(e, callback); }); break; + case "destruct_mapping": + traverse(node.field, callback); + traverse(node.name, callback); + break; case "type_id": break; case "optional_type": diff --git a/src/grammar/rename.ts b/src/grammar/rename.ts index 7ec7523d0..3ce5be75c 100644 --- a/src/grammar/rename.ts +++ b/src/grammar/rename.ts @@ -415,6 +415,11 @@ export class AstRenamer { map: this.renameExpression(stmt.map), statements: this.renameStatements(stmt.statements), }; + case "statement_destruct": + return { + ...stmt, + expression: this.renameExpression(stmt.expression), + }; default: return stmt; } diff --git a/src/grammar/test-failed/destructuring-duplicate-source-id.tact b/src/grammar/test-failed/destructuring-duplicate-source-id.tact new file mode 100644 index 000000000..d2c3cc227 --- /dev/null +++ b/src/grammar/test-failed/destructuring-duplicate-source-id.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a: x, a: y } = s; + return x + y; +} \ No newline at end of file diff --git a/src/grammar/test/stmt-destructuring.tact b/src/grammar/test/stmt-destructuring.tact new file mode 100644 index 000000000..a61c86668 --- /dev/null +++ b/src/grammar/test/stmt-destructuring.tact @@ -0,0 +1,26 @@ +struct S { + a: Int; + b: Int; + c: Int; +} + +message M { + a: Int; + b: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c } = s; + let S { a: a1 } = s; + let S { b: b1 } = s; + let S { c: c1 } = s; + let S { a: a2, b: b2 } = s; + let S { a: a3, c: c3 } = s; + let S { b: b4, c: c4 } = s; + + let m = M{ a: 1, b: 2 }; + let M { a: a_m, b: b_m } = m; + + return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m; +} \ No newline at end of file diff --git a/src/interpreter.ts b/src/interpreter.ts index f9441fd8d..66213c44b 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -34,6 +34,7 @@ import { AstStatement, AstStatementAssign, AstStatementAugmentedAssign, + AstStatementDestruct, AstStatementExpression, AstStatementForEach, AstStatementLet, @@ -1390,6 +1391,9 @@ export class Interpreter { case "statement_let": this.interpretLetStatement(ast); break; + case "statement_destruct": + this.interpretDestructStatement(ast); + break; case "statement_assign": this.interpretAssignStatement(ast); break; @@ -1438,6 +1442,56 @@ export class Interpreter { this.envStack.setNewBinding(idText(ast.name), val); } + public interpretDestructStatement(ast: AstStatementDestruct) { + for (const [_, name] of ast.identifiers.values()) { + if (hasStaticConstant(this.context, idText(name))) { + // Attempt of shadowing a constant in a destructuring declaration + throwInternalCompilerError( + `declaration of ${idText(name)} shadows a constant with the same name`, + ast.loc, + ); + } + } + const val = this.interpretExpression(ast.expression); + if ( + val === null || + typeof val !== "object" || + !("$tactStruct" in val) + ) { + throwErrorConstEval( + `destructuring assignment expected a struct, but got ${showValue( + val, + )}`, + ast.expression.loc, + ); + } + if (ast.identifiers.size !== Object.keys(val).length - 1) { + throwErrorConstEval( + `destructuring assignment expected ${Object.keys(val).length - 1} fields, but got ${ + ast.identifiers.size + }`, + ast.loc, + ); + } + + for (const [field, name] of ast.identifiers.values()) { + if (name.text === "_") { + continue; + } + const v = val[idText(field)]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (v === undefined) { + throwErrorConstEval( + `destructuring assignment expected field ${idTextErr( + field, + )}`, + ast.loc, + ); + } + this.envStack.setNewBinding(idText(name), v); + } + } + public interpretAssignStatement(ast: AstStatementAssign) { if (ast.path.kind === "id") { const val = this.interpretExpression(ast.expression); diff --git a/src/prettyPrinter.ts b/src/prettyPrinter.ts index 0ac67d4d9..4c7685802 100644 --- a/src/prettyPrinter.ts +++ b/src/prettyPrinter.ts @@ -47,6 +47,7 @@ import { AstAsmInstruction, AstAsmShuffle, astNumToString, + AstStatementDestruct, } from "./grammar/ast"; import { throwInternalCompilerError } from "./errors"; import JSONbig from "json-bigint"; @@ -577,6 +578,10 @@ export class PrettyPrinter { return this.ppAstStatementTryCatch( stmt as AstStatementTryCatch, ); + case "statement_destruct": + return this.ppAstStatementDestruct( + stmt as AstStatementDestruct, + ); } } @@ -680,6 +685,20 @@ export class PrettyPrinter { const catchBody = this.ppStatementBlock(statement.catchStatements); return `${this.indent()}try ${tryBody} catch (${this.ppAstId(statement.catchName)}) ${catchBody}`; } + + ppAstStatementDestruct(statement: AstStatementDestruct): string { + const ids = statement.identifiers + .values() + .reduce((acc: string[], [field, name]) => { + const id = + field.text === name.text + ? this.ppAstId(name) + : `${this.ppAstId(field)}: ${this.ppAstId(name)}`; + acc.push(id); + return acc; + }, []); + return `${this.indent()}let ${this.ppAstTypeId(statement.type)} {${ids.join(", ")}} = ${this.ppAstExpression(statement.expression)};`; + } } /** diff --git a/src/test/contracts/case-destructuring.tact b/src/test/contracts/case-destructuring.tact new file mode 100644 index 000000000..8a4c89170 --- /dev/null +++ b/src/test/contracts/case-destructuring.tact @@ -0,0 +1,24 @@ +struct S { + a: Int; + b: Int; + c: Int; +} + +message M { + a: Int; + b: Int; +} + +fun testFunc(): Int { + let s = S{a: 1, b: 2, c: 3}; + let S {a, b, c} = s; + let S {a: a1} = s; + let S {b: b1} = s; + let S {c: c1} = s; + let S {a: a2, b: b2} = s; + let S {a: a3, c: c3} = s; + let S {b: b4, c: c4} = s; + let m = M{a: 1, b: 2}; + let M {a: a_m, b: b_m} = m; + return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m; +} \ No newline at end of file diff --git a/src/test/contracts/renamer-expected/case-destructuring.tact b/src/test/contracts/renamer-expected/case-destructuring.tact new file mode 100644 index 000000000..c11334837 --- /dev/null +++ b/src/test/contracts/renamer-expected/case-destructuring.tact @@ -0,0 +1,24 @@ +struct struct_decl_0 { + a: Int; + b: Int; + c: Int; +} + +message message_decl_1 { + a: Int; + b: Int; +} + +fun function_def_2(): Int { + let s = S{a: 1, b: 2, c: 3}; + let S {a, b, c} = s; + let S {a: a1} = s; + let S {b: b1} = s; + let S {c: c1} = s; + let S {a: a2, b: b2} = s; + let S {a: a3, c: c3} = s; + let S {b: b4, c: c4} = s; + let m = M{a: 1, b: 2}; + let M {a: a_m, b: b_m} = m; + return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m; +} \ No newline at end of file diff --git a/src/test/e2e-emulated/contracts/structs.tact b/src/test/e2e-emulated/contracts/structs.tact index 9eb01567a..4ab416e3a 100644 --- a/src/test/e2e-emulated/contracts/structs.tact +++ b/src/test/e2e-emulated/contracts/structs.tact @@ -245,6 +245,79 @@ struct OptionalFields { avatar: String?; } +struct S1 { + a: Int; + b: Int; + c: Int; +} + +fun destructuringTest1(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {a, b} = s; + + return (a ? 1 : 0) + b; +} + +fun destructuringTest2(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {a: _, b} = s; + + return b; +} + +fun destructuringTest3(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b, a} = s; + + return (a ? 1 : 0) + b; +} + +fun destructuringTest4(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b: b, a: a} = s; + + return (a ? 1 : 0) + b; +} + +fun destructuringTest5(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b: a, a: b} = s; + + return (b ? 1 : 0) + a; +} + +fun destructuringTest6(): Int { + let s = S1 { a: 1, b: 2, c: 3 }; + let S1 { c: e, b, a } = s; + return a + b + e; +} + +fun destructuringTest7(): S1 { + let s = S1 { a: 1, b: 2, c: 3 }; + let S1 { a, c: e, b: b } = s; + return S1 {a: e, b: b, c: a}; +} + contract StructsTester { s1: S = S {a: false, b: 21 + 21}; s2: S; @@ -933,4 +1006,99 @@ contract StructsTester { avatar: "non-null string", } } + + get fun destructuringTest1(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {a, b} = s; + + return (a ? 1 : 0) + b; + } + + get fun destructuringTest1Const(): Int { + return destructuringTest1(); + } + + get fun destructuringTest2(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {a: _, b} = s; + + return b; + } + + get fun destructuringTest2Const(): Int { + return destructuringTest2(); + } + + get fun destructuringTest3(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b, a} = s; + + return (a ? 1 : 0) + b; + } + + get fun destructuringTest3Const(): Int { + return destructuringTest3(); + } + + get fun destructuringTest4(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b: b, a: a} = s; + + return (a ? 1 : 0) + b; + } + + get fun destructuringTest4Const(): Int { + return destructuringTest4(); + } + + get fun destructuringTest5(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b: a, a: b} = s; + + return (b ? 1 : 0) + a; + } + + get fun destructuringTest5Const(): Int { + return destructuringTest5(); + } + + get fun destructuringTest6(): Int { + let s = S1 { a: 1, b: 2, c: 3 }; + let S1 { c: e, b, a } = s; + return a + b + e; + } + + get fun destructuringTest6Const(): Int { + return destructuringTest6(); + } + + get fun destructuringTest7(): S1 { + let s = S1 { a: 1, b: 2, c: 3 }; + let S1 { a, c: e, b: b } = s; + return S1 {a: e, b: b, c: a}; + } + + get fun destructuringTest7Const(): S1 { + return destructuringTest7(); + } } diff --git a/src/test/e2e-emulated/structs.spec.ts b/src/test/e2e-emulated/structs.spec.ts index 614ad54b3..f7f20f5db 100644 --- a/src/test/e2e-emulated/structs.spec.ts +++ b/src/test/e2e-emulated/structs.spec.ts @@ -7,6 +7,7 @@ import { MyStruct2, MyStruct3, OptionalFields, + S1, StructsTester, UintFields, loadMyMessage1, @@ -412,5 +413,31 @@ describe("structs", () => { expect( await contract.getOptionalFields(), ).toMatchObject(optionalFields); + + // Struct destructuring + expect(await contract.getDestructuringTest1()).toBe(43n); + expect(await contract.getDestructuringTest1Const()).toBe(43n); + expect(await contract.getDestructuringTest2()).toBe(42n); + expect(await contract.getDestructuringTest2Const()).toBe(42n); + expect(await contract.getDestructuringTest3()).toBe(43n); + expect(await contract.getDestructuringTest3Const()).toBe(43n); + expect(await contract.getDestructuringTest4()).toBe(43n); + expect(await contract.getDestructuringTest4Const()).toBe(43n); + expect(await contract.getDestructuringTest5()).toBe(43n); + expect(await contract.getDestructuringTest5Const()).toBe(43n); + expect(await contract.getDestructuringTest6()).toBe(6n); + expect(await contract.getDestructuringTest6Const()).toBe(6n); + expect(await contract.getDestructuringTest7()).toMatchObject({ + $$type: "S1", + a: 3n, + b: 2n, + c: 1n, + }); + expect(await contract.getDestructuringTest7Const()).toMatchObject({ + $$type: "S1", + a: 3n, + b: 2n, + c: 1n, + }); }); }); diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index f83f3b84b..b3bc08044 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -898,6 +898,116 @@ Line 5, col 5: " `; +exports[`resolveStatements should fail statements for stmt-destructuring-fields-duplicate-id 1`] = ` +":15:22: Variable already exists: "x" +Line 15, col 22: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a: x, b: x, c: y } = s; + ^ + 16 | return x + y; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-destructable 1`] = ` +":16:25: Type 'map' cannot be destructured +Line 16, col 25: + 15 | let m: map = emptyMap(); +> 16 | let S { a, b, c } = m; + ^ + 17 | return a + b + c; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-destructable-opt 1`] = ` +":15:25: Type 'S?' is optional and cannot be destructured +Line 15, col 25: + 14 | let s: S? = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a, b, c } = s; + ^ + 16 | return a + b + c; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-destructable2 1`] = ` +":16:29: Type 'Test' cannot be destructured +Line 16, col 29: + 15 | let s = S{ a: 1, b: 2, c: 3 }; +> 16 | let S { a, b, c } = self; + ^~~~ + 17 | return a + b + c; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-existing 1`] = ` +":15:22: Field '"d"' not found in type 'S' +Line 15, col 22: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a, b, c, d: e } = s; + ^ + 16 | return a + b + c + e; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-existing-punned 1`] = ` +":15:16: Field '"bb"' not found in type 'S' +Line 15, col 16: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a, bb, c } = s; + ^~ + 16 | return a + bb + c; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-existing-punned2 1`] = ` +":15:22: Field '"d"' not found in type 'S' +Line 15, col 22: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a, b, c, d } = s; + ^ + 16 | return a + b + c + d; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-non-existing-underscore 1`] = ` +":15:12: Field '"_"' not found in type 'S' +Line 15, col 12: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S {_, b} = s; + ^ + 16 | return b; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-shadowed-const-id 1`] = ` +":17:16: Variable "x" is trying to shadow an existing constant with the same name +Line 17, col 16: + 16 | let s = S{ a: 1, b: 2, c: 3 }; +> 17 | let S { a: x, b: b, c: c } = s; + ^ + 18 | return x + b + c; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-shadowed-id 1`] = ` +":16:16: Variable already exists: "x" +Line 16, col 16: + 15 | let x = 0; +> 16 | let S { a: x, b: b, c: c } = s; + ^ + 17 | return x + b + c; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-wrong-type 1`] = ` +":18:21: Type mismatch: "S2" is not assignable to "S1" +Line 18, col 21: + 17 | fun testFunc(): Int { +> 18 | let S1 {x, y} = S2 {x: 42, y: 43}; + ^~~~~~~~~~~~~~~~~ + 19 | return x + y; +" +`; + exports[`resolveStatements should fail statements for stmt-do-int 1`] = ` ":9:5: Type mismatch: "Int" is not assignable to "Bool" Line 9, col 5: @@ -2940,6 +3050,179 @@ exports[`resolveStatements should resolve statements for stmt-augmented-assgn-nu ] `; +exports[`resolveStatements should resolve statements for stmt-destructuring 1`] = ` +[ + [ + "1", + "Int", + ], + [ + "2", + "Int", + ], + [ + "3", + "Int", + ], + [ + "S{ a: 1, b: 2, c: 3 }", + "S", + ], + [ + "s", + "S", + ], + [ + "s", + "S", + ], + [ + "s", + "S", + ], + [ + "s", + "S", + ], + [ + "s", + "S", + ], + [ + "s", + "S", + ], + [ + "s", + "S", + ], + [ + "1", + "Int", + ], + [ + "2", + "Int", + ], + [ + "M{ a: 1, b: 2 }", + "M", + ], + [ + "m", + "M", + ], + [ + "a", + "Int", + ], + [ + "b", + "Int", + ], + [ + "a + b", + "Int", + ], + [ + "c", + "Int", + ], + [ + "a + b + c", + "Int", + ], + [ + "a1", + "Int", + ], + [ + "a + b + c + a1", + "Int", + ], + [ + "b1", + "Int", + ], + [ + "a + b + c + a1 + b1", + "Int", + ], + [ + "c1", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1", + "Int", + ], + [ + "a2", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2", + "Int", + ], + [ + "b2", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2", + "Int", + ], + [ + "a3", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2 + a3", + "Int", + ], + [ + "c3", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3", + "Int", + ], + [ + "b4", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4", + "Int", + ], + [ + "c4", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4", + "Int", + ], + [ + "a_m", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m", + "Int", + ], + [ + "b_m", + "Int", + ], + [ + "a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m", + "Int", + ], +] +`; + exports[`resolveStatements should resolve statements for stmt-let-assgn 1`] = ` [ [ diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 81ce982e9..9ea42ceea 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -684,6 +684,69 @@ function processStatements( } sctx = initialSctx; // Re-assign the modified initial context back to sctx after merging + break; + } + case "statement_destruct": { + // Process expression + ctx = resolveExpression(s.expression, sctx, ctx); + + // Check variable names + for (const [_, name] of s.identifiers.values()) { + checkVariableExists(ctx, sctx, name); + } + + // Check type + const expressionType = getExpType(ctx, s.expression); + if (expressionType.kind !== "ref") { + throwCompilationError( + `Type '${printTypeRef(expressionType)}' cannot be destructured`, + s.expression.loc, + ); + } + if (expressionType.optional) { + throwCompilationError( + `Type '${printTypeRef(expressionType)}' is optional and cannot be destructured`, + s.expression.loc, + ); + } + const ty = getType(ctx, expressionType.name); + if (ty.kind !== "struct") { + throwCompilationError( + `Type '${printTypeRef(expressionType)}' cannot be destructured`, + s.expression.loc, + ); + } + + // Compare type with the specified one + const typeRef = resolveTypeRef(ctx, s.type); + if (typeRef.kind !== "ref") { + throwInternalCompilerError( + `Unexpected type kind: '${typeRef.kind}'`, + s.type.loc, + ); + } + if (expressionType.name !== typeRef.name) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(typeRef)}"`, + s.expression.loc, + ); + } + + // Add variables + s.identifiers.forEach(([field, name], _) => { + const f = ty.fields.find((f) => eqNames(f.name, field)); + if (!f) { + throwCompilationError( + `Field '${idTextErr(field)}' not found in type '${expressionType.name}'`, + field.loc, + ); + } + if (name.text !== "_") { + sctx = addVariable(name, f.type, ctx, sctx); + } + }); + + break; } } } diff --git a/src/types/stmts-failed/stmt-destructuring-fields-duplicate-id.tact b/src/types/stmts-failed/stmt-destructuring-fields-duplicate-id.tact new file mode 100644 index 000000000..a070ec2d2 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-duplicate-id.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a: x, b: x, c: y } = s; + return x + y; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-destructable-opt.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-destructable-opt.tact new file mode 100644 index 000000000..57b9a8e0d --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-destructable-opt.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s: S? = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c } = s; + return a + b + c; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-destructable.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-destructable.tact new file mode 100644 index 000000000..61075ed9a --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-destructable.tact @@ -0,0 +1,18 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let m: map = emptyMap(); + let S { a, b, c } = m; + return a + b + c; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-destructable2.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-destructable2.tact new file mode 100644 index 000000000..ae592944b --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-destructable2.tact @@ -0,0 +1,19 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +contract Test { + fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c } = self; + return a + b + c; + } +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned.tact new file mode 100644 index 000000000..ae58f858c --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, bb, c } = s; + return a + bb + c; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned2.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned2.tact new file mode 100644 index 000000000..cf02bcad2 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned2.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c, d } = s; + return a + b + c + d; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-existing-underscore.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-underscore.tact new file mode 100644 index 000000000..9bf8c31a5 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-underscore.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S {_, b} = s; + return b; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-non-existing.tact b/src/types/stmts-failed/stmt-destructuring-fields-non-existing.tact new file mode 100644 index 000000000..8dcf1283e --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing.tact @@ -0,0 +1,17 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c, d: e } = s; + return a + b + c + e; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-shadowed-const-id.tact b/src/types/stmts-failed/stmt-destructuring-fields-shadowed-const-id.tact new file mode 100644 index 000000000..6613e9d15 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-shadowed-const-id.tact @@ -0,0 +1,19 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +const x: Int = 5; + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a: x, b: b, c: c } = s; + return x + b + c; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-shadowed-id.tact b/src/types/stmts-failed/stmt-destructuring-fields-shadowed-id.tact new file mode 100644 index 000000000..56332106d --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-shadowed-id.tact @@ -0,0 +1,18 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let x = 0; + let S { a: x, b: b, c: c } = s; + return x + b + c; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-wrong-type.tact b/src/types/stmts-failed/stmt-destructuring-fields-wrong-type.tact new file mode 100644 index 000000000..cc7e33ef6 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-wrong-type.tact @@ -0,0 +1,20 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S1 { + x: Int; + y: Int; +} + +struct S2 { + x: Int; + y: Int; +} + +fun testFunc(): Int { + let S1 {x, y} = S2 {x: 42, y: 43}; + return x + y; +} \ No newline at end of file diff --git a/src/types/stmts/stmt-destructuring.tact b/src/types/stmts/stmt-destructuring.tact new file mode 100644 index 000000000..c559d99c1 --- /dev/null +++ b/src/types/stmts/stmt-destructuring.tact @@ -0,0 +1,32 @@ +primitive Int; + +trait BaseTrait { + +} + +struct S { + a: Int; + b: Int; + c: Int; +} + +message M { + a: Int; + b: Int; +} + +fun testFunc(): Int { + let s = S{ a: 1, b: 2, c: 3 }; + let S { a, b, c } = s; + let S { a: a1 } = s; + let S { b: b1 } = s; + let S { c: c1 } = s; + let S { a: a2, b: b2 } = s; + let S { a: a3, c: c3 } = s; + let S { b: b4, c: c4 } = s; + + let m = M{ a: 1, b: 2 }; + let M { a: a_m, b: b_m } = m; + + return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m; +} \ No newline at end of file