diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e1f45c7f..3fe5c5fa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 [#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) diff --git a/src/grammar/__snapshots__/grammar.spec.ts.snap b/src/grammar/__snapshots__/grammar.spec.ts.snap index 333d3dc1a..060ae17e9 100644 --- a/src/grammar/__snapshots__/grammar.spec.ts.snap +++ b/src/grammar/__snapshots__/grammar.spec.ts.snap @@ -8075,7 +8075,7 @@ exports[`grammar should parse stmt-augmented-assign-logic 1`] = ` exports[`grammar should parse stmt-destructuring 1`] = ` { - "id": 140, + "id": 148, "imports": [], "items": [ { @@ -8209,17 +8209,17 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "attributes": [], - "id": 139, + "id": 147, "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 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; @@ -8321,12 +8321,12 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 45, + "id": 46, "kind": "id", "loc": s, "text": "s", }, - "id": 46, + "id": 47, "identifiers": Map { "a" => [ { @@ -8371,6 +8371,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, ], }, + "ignoreUnspecifiedFields": false, "kind": "statement_destruct", "loc": let S { a, b, c } = s;, "type": { @@ -8382,32 +8383,33 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 51, + "id": 53, "kind": "id", "loc": s, "text": "s", }, - "id": 52, + "id": 54, "identifiers": Map { "a" => [ { - "id": 48, + "id": 49, "kind": "id", "loc": a, "text": "a", }, { - "id": 49, + "id": 50, "kind": "id", "loc": a1, "text": "a1", }, ], }, + "ignoreUnspecifiedFields": true, "kind": "statement_destruct", - "loc": let S { a: a1 } = s;, + "loc": let S { a: a1, .. } = s;, "type": { - "id": 47, + "id": 48, "kind": "type_id", "loc": S, "text": "S", @@ -8415,32 +8417,33 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 57, + "id": 60, "kind": "id", "loc": s, "text": "s", }, - "id": 58, + "id": 61, "identifiers": Map { "b" => [ { - "id": 54, + "id": 56, "kind": "id", "loc": b, "text": "b", }, { - "id": 55, + "id": 57, "kind": "id", "loc": b1, "text": "b1", }, ], }, + "ignoreUnspecifiedFields": true, "kind": "statement_destruct", - "loc": let S { b: b1 } = s;, + "loc": let S { b: b1, .. } = s;, "type": { - "id": 53, + "id": 55, "kind": "type_id", "loc": S, "text": "S", @@ -8448,32 +8451,33 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 63, + "id": 67, "kind": "id", "loc": s, "text": "s", }, - "id": 64, + "id": 68, "identifiers": Map { "c" => [ { - "id": 60, + "id": 63, "kind": "id", "loc": c, "text": "c", }, { - "id": 61, + "id": 64, "kind": "id", "loc": c1, "text": "c1", }, ], }, + "ignoreUnspecifiedFields": true, "kind": "statement_destruct", - "loc": let S { c: c1 } = s;, + "loc": let S { c: c1, .. } = s;, "type": { - "id": 59, + "id": 62, "kind": "type_id", "loc": S, "text": "S", @@ -8481,22 +8485,22 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 72, + "id": 77, "kind": "id", "loc": s, "text": "s", }, - "id": 73, + "id": 78, "identifiers": Map { "a" => [ { - "id": 66, + "id": 70, "kind": "id", "loc": a, "text": "a", }, { - "id": 67, + "id": 71, "kind": "id", "loc": a2, "text": "a2", @@ -8504,23 +8508,24 @@ exports[`grammar should parse stmt-destructuring 1`] = ` ], "b" => [ { - "id": 69, + "id": 73, "kind": "id", "loc": b, "text": "b", }, { - "id": 70, + "id": 74, "kind": "id", "loc": b2, "text": "b2", }, ], }, + "ignoreUnspecifiedFields": true, "kind": "statement_destruct", - "loc": let S { a: a2, b: b2 } = s;, + "loc": let S { a: a2, b: b2, .. } = s;, "type": { - "id": 65, + "id": 69, "kind": "type_id", "loc": S, "text": "S", @@ -8528,22 +8533,22 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 81, + "id": 87, "kind": "id", "loc": s, "text": "s", }, - "id": 82, + "id": 88, "identifiers": Map { "a" => [ { - "id": 75, + "id": 80, "kind": "id", "loc": a, "text": "a", }, { - "id": 76, + "id": 81, "kind": "id", "loc": a3, "text": "a3", @@ -8551,23 +8556,24 @@ exports[`grammar should parse stmt-destructuring 1`] = ` ], "c" => [ { - "id": 78, + "id": 83, "kind": "id", "loc": c, "text": "c", }, { - "id": 79, + "id": 84, "kind": "id", "loc": c3, "text": "c3", }, ], }, + "ignoreUnspecifiedFields": true, "kind": "statement_destruct", - "loc": let S { a: a3, c: c3 } = s;, + "loc": let S { a: a3, c: c3, .. } = s;, "type": { - "id": 74, + "id": 79, "kind": "type_id", "loc": S, "text": "S", @@ -8575,22 +8581,22 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 90, + "id": 97, "kind": "id", "loc": s, "text": "s", }, - "id": 91, + "id": 98, "identifiers": Map { "b" => [ { - "id": 84, + "id": 90, "kind": "id", "loc": b, "text": "b", }, { - "id": 85, + "id": 91, "kind": "id", "loc": b4, "text": "b4", @@ -8598,23 +8604,24 @@ exports[`grammar should parse stmt-destructuring 1`] = ` ], "c" => [ { - "id": 87, + "id": 93, "kind": "id", "loc": c, "text": "c", }, { - "id": 88, + "id": 94, "kind": "id", "loc": c4, "text": "c4", }, ], }, + "ignoreUnspecifiedFields": true, "kind": "statement_destruct", - "loc": let S { b: b4, c: c4 } = s;, + "loc": let S { b: b4, c: c4, .. } = s;, "type": { - "id": 83, + "id": 89, "kind": "type_id", "loc": S, "text": "S", @@ -8625,15 +8632,15 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "args": [ { "field": { - "id": 94, + "id": 101, "kind": "id", "loc": a, "text": "a", }, - "id": 96, + "id": 103, "initializer": { "base": 10, - "id": 95, + "id": 102, "kind": "number", "loc": 1, "value": 1n, @@ -8643,15 +8650,15 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "field": { - "id": 97, + "id": 104, "kind": "id", "loc": b, "text": "b", }, - "id": 99, + "id": 106, "initializer": { "base": 10, - "id": 98, + "id": 105, "kind": "number", "loc": 2, "value": 2n, @@ -8660,21 +8667,21 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": b: 2, }, ], - "id": 100, + "id": 107, "kind": "struct_instance", "loc": M{ a: 1, b: 2 }, "type": { - "id": 93, + "id": 100, "kind": "type_id", "loc": M, "text": "M", }, }, - "id": 101, + "id": 108, "kind": "statement_let", "loc": let m = M{ a: 1, b: 2 };, "name": { - "id": 92, + "id": 99, "kind": "id", "loc": m, "text": "m", @@ -8683,22 +8690,22 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 109, + "id": 117, "kind": "id", "loc": m, "text": "m", }, - "id": 110, + "id": 118, "identifiers": Map { "a" => [ { - "id": 103, + "id": 110, "kind": "id", "loc": a, "text": "a", }, { - "id": 104, + "id": 111, "kind": "id", "loc": a_m, "text": "a_m", @@ -8706,23 +8713,24 @@ exports[`grammar should parse stmt-destructuring 1`] = ` ], "b" => [ { - "id": 106, + "id": 113, "kind": "id", "loc": b, "text": "b", }, { - "id": 107, + "id": 114, "kind": "id", "loc": b_m, "text": "b_m", }, ], }, + "ignoreUnspecifiedFields": false, "kind": "statement_destruct", "loc": let M { a: a_m, b: b_m } = m;, "type": { - "id": 102, + "id": 109, "kind": "type_id", "loc": M, "text": "M", @@ -8730,46 +8738,46 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, { "expression": { - "id": 137, + "id": 145, "kind": "op_binary", "left": { - "id": 135, + "id": 143, "kind": "op_binary", "left": { - "id": 133, + "id": 141, "kind": "op_binary", "left": { - "id": 131, + "id": 139, "kind": "op_binary", "left": { - "id": 129, + "id": 137, "kind": "op_binary", "left": { - "id": 127, + "id": 135, "kind": "op_binary", "left": { - "id": 125, + "id": 133, "kind": "op_binary", "left": { - "id": 123, + "id": 131, "kind": "op_binary", "left": { - "id": 121, + "id": 129, "kind": "op_binary", "left": { - "id": 119, + "id": 127, "kind": "op_binary", "left": { - "id": 117, + "id": 125, "kind": "op_binary", "left": { - "id": 115, + "id": 123, "kind": "op_binary", "left": { - "id": 113, + "id": 121, "kind": "op_binary", "left": { - "id": 111, + "id": 119, "kind": "id", "loc": a, "text": "a", @@ -8777,7 +8785,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b, "op": "+", "right": { - "id": 112, + "id": 120, "kind": "id", "loc": b, "text": "b", @@ -8786,7 +8794,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c, "op": "+", "right": { - "id": 114, + "id": 122, "kind": "id", "loc": c, "text": "c", @@ -8795,7 +8803,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1, "op": "+", "right": { - "id": 116, + "id": 124, "kind": "id", "loc": a1, "text": "a1", @@ -8804,7 +8812,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1, "op": "+", "right": { - "id": 118, + "id": 126, "kind": "id", "loc": b1, "text": "b1", @@ -8813,7 +8821,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1, "op": "+", "right": { - "id": 120, + "id": 128, "kind": "id", "loc": c1, "text": "c1", @@ -8822,7 +8830,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2, "op": "+", "right": { - "id": 122, + "id": 130, "kind": "id", "loc": a2, "text": "a2", @@ -8831,7 +8839,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2, "op": "+", "right": { - "id": 124, + "id": 132, "kind": "id", "loc": b2, "text": "b2", @@ -8840,7 +8848,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3, "op": "+", "right": { - "id": 126, + "id": 134, "kind": "id", "loc": a3, "text": "a3", @@ -8849,7 +8857,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3, "op": "+", "right": { - "id": 128, + "id": 136, "kind": "id", "loc": c3, "text": "c3", @@ -8858,7 +8866,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4, "op": "+", "right": { - "id": 130, + "id": 138, "kind": "id", "loc": b4, "text": "b4", @@ -8867,7 +8875,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4, "op": "+", "right": { - "id": 132, + "id": 140, "kind": "id", "loc": c4, "text": "c4", @@ -8876,7 +8884,7 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m, "op": "+", "right": { - "id": 134, + "id": 142, "kind": "id", "loc": a_m, "text": "a_m", @@ -8885,13 +8893,13 @@ exports[`grammar should parse stmt-destructuring 1`] = ` "loc": a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m, "op": "+", "right": { - "id": 136, + "id": 144, "kind": "id", "loc": b_m, "text": "b_m", }, }, - "id": 138, + "id": 146, "kind": "statement_return", "loc": return a + b + c + a1 + b1 + c1 + a2 + b2 + a3 + c3 + b4 + c4 + a_m + b_m;, }, diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index cc75370b2..200d190e8 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -326,6 +326,7 @@ export type AstStatementDestruct = { type: AstTypeId; /** field name -> [field id, local id] */ identifiers: Map; + ignoreUnspecifiedFields: boolean; expression: AstExpression; id: number; loc: SrcInfo; @@ -592,6 +593,13 @@ export type AstDestructMapping = { loc: SrcInfo; }; +export type AstDestructEnd = { + kind: "destruct_end"; + ignoreUnspecifiedFields: boolean; + id: number; + loc: SrcInfo; +}; + export type AstNumber = { kind: "number"; base: AstNumberBase; @@ -704,6 +712,7 @@ export type AstReceiverKind = export type AstNode = | AstFuncId | AstDestructMapping + | AstDestructEnd | AstExpression | AstStatement | AstTypeDecl diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index 09a5cf2fe..3c3b4ad7f 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -158,7 +158,9 @@ Tact { StatementForEach = foreach "(" id "," id "in" Expression ")" "{" Statement* "}" - StatementDestruct = let typeId "{" ListOf ","? "}" "=" Expression (";" | &"}") + StatementDestruct = let typeId "{" ListOf EndOfIdentifiers "}" "=" Expression (";" | &"}") + EndOfIdentifiers = "," ".." --ignoreUnspecifiedFields + | ","? --regular DestructItem = id ":" id --regular | id --punned diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index d33aa28ca..2048e5139 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -1018,7 +1018,7 @@ semantics.addOperation("astOfStatement", { typeId, _lparen, identifiers, - _optTrailingComma, + endOfIdentifiers, _rparen, _equals, expression, @@ -1043,6 +1043,8 @@ semantics.addOperation("astOfStatement", { ]); return map; }, new Map()), + ignoreUnspecifiedFields: + endOfIdentifiers.astOfExpression().ignoreUnspecifiedFields, expression: expression.astOfExpression(), loc: createRef(this), }); @@ -1191,6 +1193,20 @@ semantics.addOperation("astOfExpression", { loc: createRef(this), }); }, + EndOfIdentifiers_regular(_comma) { + return createAstNode({ + kind: "destruct_end", + ignoreUnspecifiedFields: false, + loc: createRef(this), + }); + }, + EndOfIdentifiers_ignoreUnspecifiedFields(_comma, _dotDot) { + return createAstNode({ + kind: "destruct_end", + ignoreUnspecifiedFields: true, + loc: createRef(this), + }); + }, ExpressionAdd_add(left, _plus, right) { return createAstNode({ kind: "op_binary", diff --git a/src/grammar/iterators.ts b/src/grammar/iterators.ts index 8db047100..bc473a918 100644 --- a/src/grammar/iterators.ts +++ b/src/grammar/iterators.ts @@ -141,6 +141,8 @@ export function traverse(node: AstNode, callback: (node: AstNode) => void) { }); traverse(node.expression, callback); break; + case "destruct_end": + break; case "statement_return": if (node.expression) traverse(node.expression, callback); break; diff --git a/src/grammar/test/stmt-destructuring.tact b/src/grammar/test/stmt-destructuring.tact index a61c86668..7c7316826 100644 --- a/src/grammar/test/stmt-destructuring.tact +++ b/src/grammar/test/stmt-destructuring.tact @@ -12,12 +12,12 @@ message M { 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 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; diff --git a/src/interpreter.ts b/src/interpreter.ts index 66213c44b..65a223e2e 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -1465,14 +1465,6 @@ export class Interpreter { 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 === "_") { diff --git a/src/prettyPrinter.ts b/src/prettyPrinter.ts index 4c7685802..2e1ccf630 100644 --- a/src/prettyPrinter.ts +++ b/src/prettyPrinter.ts @@ -697,7 +697,8 @@ export class PrettyPrinter { acc.push(id); return acc; }, []); - return `${this.indent()}let ${this.ppAstTypeId(statement.type)} {${ids.join(", ")}} = ${this.ppAstExpression(statement.expression)};`; + const restPattern = statement.ignoreUnspecifiedFields ? ", .." : ""; + return `${this.indent()}let ${this.ppAstTypeId(statement.type)} {${ids.join(", ")}${restPattern}} = ${this.ppAstExpression(statement.expression)};`; } } diff --git a/src/test/contracts/case-destructuring.tact b/src/test/contracts/case-destructuring.tact index 8a4c89170..e1ace4b5b 100644 --- a/src/test/contracts/case-destructuring.tact +++ b/src/test/contracts/case-destructuring.tact @@ -12,12 +12,12 @@ message M { 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 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; diff --git a/src/test/contracts/renamer-expected/case-destructuring.tact b/src/test/contracts/renamer-expected/case-destructuring.tact index c11334837..16b94fc9b 100644 --- a/src/test/contracts/renamer-expected/case-destructuring.tact +++ b/src/test/contracts/renamer-expected/case-destructuring.tact @@ -12,12 +12,12 @@ message message_decl_1 { 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 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; diff --git a/src/test/e2e-emulated/contracts/structs.tact b/src/test/e2e-emulated/contracts/structs.tact index 4ab416e3a..cea1d5f8e 100644 --- a/src/test/e2e-emulated/contracts/structs.tact +++ b/src/test/e2e-emulated/contracts/structs.tact @@ -318,6 +318,17 @@ fun destructuringTest7(): S1 { return S1 {a: e, b: b, c: a}; } +fun destructuringTest8(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b, ..} = s; + + return b; +} + contract StructsTester { s1: S = S {a: false, b: 21 + 21}; s2: S; @@ -1101,4 +1112,19 @@ contract StructsTester { get fun destructuringTest7Const(): S1 { return destructuringTest7(); } + + get fun destructuringTest8(): Int { + let s = S { + a: true, + b: 42 + }; + + let S {b, ..} = s; + + return b; + } + + get fun destructuringTest8Const(): Int { + return destructuringTest8(); + } } diff --git a/src/test/e2e-emulated/structs.spec.ts b/src/test/e2e-emulated/structs.spec.ts index f7f20f5db..2ffd4fdc0 100644 --- a/src/test/e2e-emulated/structs.spec.ts +++ b/src/test/e2e-emulated/structs.spec.ts @@ -439,5 +439,7 @@ describe("structs", () => { b: 2n, c: 1n, }); + expect(await contract.getDestructuringTest8()).toBe(42n); + expect(await contract.getDestructuringTest8Const()).toBe(42n); }); }); diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index b3bc08044..0a89ef360 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -939,12 +939,12 @@ Line 16, col 29: `; 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: +":15:19: Field '"d"' not found in type 'S' +Line 15, col 19: 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; +> 15 | let S { a, b, d: e } = s; + ^ + 16 | return a + b + e; " `; @@ -959,12 +959,12 @@ Line 15, col 16: `; 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: +":15:19: Field '"d"' not found in type 'S' +Line 15, col 19: 14 | let s = S{ a: 1, b: 2, c: 3 }; -> 15 | let S { a, b, c, d } = s; - ^ - 16 | return a + b + c + d; +> 15 | let S { a, b, d } = s; + ^ + 16 | return a + b + d; " `; @@ -972,9 +972,9 @@ exports[`resolveStatements should fail statements for stmt-destructuring-fields- ":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; +> 15 | let S {_, b, c} = s; ^ - 16 | return b; + 16 | return b + c; " `; @@ -998,6 +998,26 @@ Line 16, col 16: " `; +exports[`resolveStatements should fail statements for stmt-destructuring-fields-wrong-count 1`] = ` +":15:5: Expected 3 fields, but got 2 +Line 15, col 5: + 14 | let s = S{ a: 1, b: 2, c: 3 }; +> 15 | let S { a, b } = s; + ^~~~~~~~~~~~~~~~~~~ + 16 | return a + b; +" +`; + +exports[`resolveStatements should fail statements for stmt-destructuring-fields-wrong-count2 1`] = ` +":15:5: Expected 3 fields, but got 4 +Line 15, col 5: + 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-wrong-type 1`] = ` ":18:21: Type mismatch: "S2" is not assignable to "S1" Line 18, col 21: diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 9ea42ceea..1e6e2afe4 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -717,6 +717,17 @@ function processStatements( ); } + // Check variables count + if ( + !s.ignoreUnspecifiedFields && + s.identifiers.size !== ty.fields.length + ) { + throwCompilationError( + `Expected ${ty.fields.length} fields, but got ${s.identifiers.size}`, + s.loc, + ); + } + // Compare type with the specified one const typeRef = resolveTypeRef(ctx, s.type); if (typeRef.kind !== "ref") { 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 index cf02bcad2..09952ac7d 100644 --- a/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned2.tact +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-punned2.tact @@ -12,6 +12,6 @@ struct S { fun testFunc(): Int { let s = S{ a: 1, b: 2, c: 3 }; - let S { a, b, c, d } = s; - return a + b + c + d; + let S { a, b, d } = s; + return a + b + 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 index 9bf8c31a5..783bcad18 100644 --- a/src/types/stmts-failed/stmt-destructuring-fields-non-existing-underscore.tact +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing-underscore.tact @@ -12,6 +12,6 @@ struct S { fun testFunc(): Int { let s = S{ a: 1, b: 2, c: 3 }; - let S {_, b} = s; - return b; + let S {_, b, c} = s; + return b + c; } \ 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 index 8dcf1283e..1ed48343a 100644 --- a/src/types/stmts-failed/stmt-destructuring-fields-non-existing.tact +++ b/src/types/stmts-failed/stmt-destructuring-fields-non-existing.tact @@ -12,6 +12,6 @@ struct S { 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; + let S { a, b, d: e } = s; + return a + b + e; } \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-wrong-count.tact b/src/types/stmts-failed/stmt-destructuring-fields-wrong-count.tact new file mode 100644 index 000000000..d711b2cf8 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-wrong-count.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 } = s; + return a + b; +} \ No newline at end of file diff --git a/src/types/stmts-failed/stmt-destructuring-fields-wrong-count2.tact b/src/types/stmts-failed/stmt-destructuring-fields-wrong-count2.tact new file mode 100644 index 000000000..cf02bcad2 --- /dev/null +++ b/src/types/stmts-failed/stmt-destructuring-fields-wrong-count2.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/stmt-destructuring.tact b/src/types/stmts/stmt-destructuring.tact index c559d99c1..e0a4ae779 100644 --- a/src/types/stmts/stmt-destructuring.tact +++ b/src/types/stmts/stmt-destructuring.tact @@ -18,12 +18,12 @@ message M { 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 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;