diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index d8fc5ad56..25cc63cf6 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -475,10 +475,10 @@ export function writeStatement( } const ty = getType(ctx.ctx, t.name); const ids = ty.fields.map((field) => { - const id = Array.from(f.identifiers.entries()).find( - ([key, val]) => val.text !== "_" && key.text === field.name, - ); - return id ? funcIdOf(id[1]) : "_"; + 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)};`, diff --git a/src/grammar/__snapshots__/grammar.spec.ts.snap b/src/grammar/__snapshots__/grammar.spec.ts.snap index 651a97c83..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: @@ -8318,39 +8328,48 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 46, "identifiers": Map { - { - "id": 36, - "kind": "id", - "loc": a, - "text": "a", - } => { - "id": 37, - "kind": "id", - "loc": a, - "text": "a", - }, - { - "id": 39, - "kind": "id", - "loc": b, - "text": "b", - } => { - "id": 40, - "kind": "id", - "loc": b, - "text": "b", - }, - { - "id": 42, - "kind": "id", - "loc": c, - "text": "c", - } => { - "id": 43, - "kind": "id", - "loc": c, - "text": "c", - }, + "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;, @@ -8370,17 +8389,20 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 52, "identifiers": Map { - { - "id": 48, - "kind": "id", - "loc": a, - "text": "a", - } => { - "id": 49, - "kind": "id", - "loc": a1, - "text": "a1", - }, + "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;, @@ -8400,17 +8422,20 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 58, "identifiers": Map { - { - "id": 54, - "kind": "id", - "loc": b, - "text": "b", - } => { - "id": 55, - "kind": "id", - "loc": b1, - "text": "b1", - }, + "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;, @@ -8430,17 +8455,20 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 64, "identifiers": Map { - { - "id": 60, - "kind": "id", - "loc": c, - "text": "c", - } => { - "id": 61, - "kind": "id", - "loc": c1, - "text": "c1", - }, + "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;, @@ -8460,28 +8488,34 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 73, "identifiers": Map { - { - "id": 66, - "kind": "id", - "loc": a, - "text": "a", - } => { - "id": 67, - "kind": "id", - "loc": a2, - "text": "a2", - }, - { - "id": 69, - "kind": "id", - "loc": b, - "text": "b", - } => { - "id": 70, - "kind": "id", - "loc": b2, - "text": "b2", - }, + "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;, @@ -8501,28 +8535,34 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 82, "identifiers": Map { - { - "id": 75, - "kind": "id", - "loc": a, - "text": "a", - } => { - "id": 76, - "kind": "id", - "loc": a3, - "text": "a3", - }, - { - "id": 78, - "kind": "id", - "loc": c, - "text": "c", - } => { - "id": 79, - "kind": "id", - "loc": c3, - "text": "c3", - }, + "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;, @@ -8542,28 +8582,34 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 91, "identifiers": Map { - { - "id": 84, - "kind": "id", - "loc": b, - "text": "b", - } => { - "id": 85, - "kind": "id", - "loc": b4, - "text": "b4", - }, - { - "id": 87, - "kind": "id", - "loc": c, - "text": "c", - } => { - "id": 88, - "kind": "id", - "loc": c4, - "text": "c4", - }, + "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;, @@ -8644,28 +8690,34 @@ exports[`grammar should parse stmt-destructuring 1`] = ` }, "id": 110, "identifiers": Map { - { - "id": 103, - "kind": "id", - "loc": a, - "text": "a", - } => { - "id": 104, - "kind": "id", - "loc": a_m, - "text": "a_m", - }, - { - "id": 106, - "kind": "id", - "loc": b, - "text": "b", - } => { - "id": 107, - "kind": "id", - "loc": b_m, - "text": "b_m", - }, + "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;, diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index 0a954aedd..332389073 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -324,7 +324,7 @@ export type AstStatementForEach = { export type AstStatementDestruct = { kind: "statement_destruct"; type: AstTypeId; - identifiers: Map; + identifiers: Map; // field name -> [field id, local id] expression: AstExpression; id: number; loc: SrcInfo; diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index e795ce8a7..840bc0eff 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -1031,9 +1031,18 @@ semantics.addOperation("astOfStatement", { .asIteration() .children.reduce((map, item) => { const destructItem = item.astOfExpression(); - map.set(destructItem.field, destructItem.name); + 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()), + }, new Map()), expression: expression.astOfExpression(), loc: createRef(this), }); diff --git a/src/grammar/iterators.ts b/src/grammar/iterators.ts index 24ce4c3da..8db047100 100644 --- a/src/grammar/iterators.ts +++ b/src/grammar/iterators.ts @@ -135,7 +135,7 @@ export function traverse(node: AstNode, callback: (node: AstNode) => void) { traverse(node.expression, callback); break; case "statement_destruct": - node.identifiers.forEach((name, field) => { + node.identifiers.forEach(([field, name], _) => { traverse(field, callback); traverse(name, callback); }); 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/interpreter.ts b/src/interpreter.ts index 55f74f733..66213c44b 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -1443,7 +1443,7 @@ export class Interpreter { } public interpretDestructStatement(ast: AstStatementDestruct) { - for (const [_, name] of ast.identifiers) { + for (const [_, name] of ast.identifiers.values()) { if (hasStaticConstant(this.context, idText(name))) { // Attempt of shadowing a constant in a destructuring declaration throwInternalCompilerError( @@ -1474,7 +1474,7 @@ export class Interpreter { ); } - for (const [field, name] of ast.identifiers) { + for (const [field, name] of ast.identifiers.values()) { if (name.text === "_") { continue; } diff --git a/src/prettyPrinter.ts b/src/prettyPrinter.ts index 975ce1cee..4c7685802 100644 --- a/src/prettyPrinter.ts +++ b/src/prettyPrinter.ts @@ -687,15 +687,16 @@ export class PrettyPrinter { } ppAstStatementDestruct(statement: AstStatementDestruct): string { - const ids = Array.from(statement.identifiers.entries()).map( - ([field, name]) => { - if (field.text === name.text) { - return this.ppAstId(name); - } else { - return `${this.ppAstId(field)}: ${this.ppAstId(name)}`; - } - }, - ); + 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/types/resolveStatements.ts b/src/types/resolveStatements.ts index dc7fe81e9..9ea42ceea 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -691,7 +691,7 @@ function processStatements( ctx = resolveExpression(s.expression, sctx, ctx); // Check variable names - for (const name of s.identifiers.values()) { + for (const [_, name] of s.identifiers.values()) { checkVariableExists(ctx, sctx, name); } @@ -733,7 +733,7 @@ function processStatements( } // Add variables - s.identifiers.forEach((name, field) => { + s.identifiers.forEach(([field, name], _) => { const f = ty.fields.find((f) => eqNames(f.name, field)); if (!f) { throwCompilationError(