From 0b25fbc72b892b44ab80174ae46b5e2c251eb17e Mon Sep 17 00:00:00 2001 From: Gusarich Date: Wed, 15 May 2024 16:56:05 +0300 Subject: [PATCH 1/4] implement --- src/generator/writers/writeFunction.ts | 56 +++++++++------- .../contracts/underscore-variable.tact | 42 ++++++++++++ .../e2e-emulated/underscore-variable.spec.ts | 25 ++++++++ .../resolveStatements.spec.ts.snap | 62 ++++++++++++++++++ src/types/resolveStatements.ts | 64 +++++++++++++------ .../var-underscore-name-access.tact | 9 +++ .../var-underscore-name-access2.tact | 10 +++ .../stmts/var-underscore-name-in-foreach.tact | 8 +++ .../var-underscore-name-in-foreach2.tact | 10 +++ tact.config.json | 5 ++ 10 files changed, 247 insertions(+), 44 deletions(-) create mode 100644 src/test/e2e-emulated/contracts/underscore-variable.tact create mode 100644 src/test/e2e-emulated/underscore-variable.spec.ts create mode 100644 src/types/stmts-failed/var-underscore-name-access.tact create mode 100644 src/types/stmts-failed/var-underscore-name-access2.tact create mode 100644 src/types/stmts/var-underscore-name-in-foreach.tact create mode 100644 src/types/stmts/var-underscore-name-in-foreach2.tact diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index eda59a495..01112c250 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -195,7 +195,11 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } }); - ctx.append(`} catch (_, ${id(f.catchName)}) {`); + if (f.catchName == "_") { + ctx.append(`} catch (_) {`); + } else { + ctx.append(`} catch (_, ${id(f.catchName)}) {`); + } ctx.inIndent(() => { for (const s of f.catchStatements) { writeStatement(s, self, returns, ctx); @@ -215,6 +219,12 @@ export function writeStatement( } const flag = freshIdentifier("flag"); + const key = + f.keyName == "_" ? freshIdentifier("underscore") : id(f.keyName); + const value = + f.valueName == "_" + ? freshIdentifier("underscore") + : id(f.valueName); // Handle Int key if (t.key === "Int") { @@ -237,7 +247,7 @@ export function writeStatement( } ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_${vKind}`)}(${path}, ${bits}, ${vBits});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_${vKind}`)}(${path}, ${bits}, ${vBits});`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -245,13 +255,13 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_${vKind}`)}(${path}, ${bits}, ${id(f.keyName)}, ${vBits});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_${vKind}`)}(${path}, ${bits}, ${key}, ${vBits});`, ); }); ctx.append(`}`); } else if (t.value === "Bool") { ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_int`)}(${path}, ${bits}, 1);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_int`)}(${path}, ${bits}, 1);`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -259,13 +269,13 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_int`)}(${path}, ${bits}, ${id(f.keyName)}, 1);`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_int`)}(${path}, ${bits}, ${key}, 1);`, ); }); ctx.append(`}`); } else if (t.value === "Cell") { ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -273,13 +283,13 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${id(f.keyName)});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, ); }); ctx.append(`}`); } else if (t.value === "Address") { ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_slice`)}(${path}, ${bits});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_slice`)}(${path}, ${bits});`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -287,25 +297,25 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_slice`)}(${path}, ${bits}, ${id(f.keyName)});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_slice`)}(${path}, ${bits}, ${key});`, ); }); ctx.append(`}`); } else { // value is struct ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { ctx.append( - `var ${resolveFuncTypeUnpack(t.value, id(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${id(f.valueName)}));`, + `var ${resolveFuncTypeUnpack(t.value, id(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, ); for (const s of f.statements) { writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${id(f.keyName)});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, ); }); ctx.append(`}`); @@ -324,7 +334,7 @@ export function writeStatement( vKind = "uint"; } ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_${vKind}`)}(${path}, 267, ${vBits});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_${vKind}`)}(${path}, 267, ${vBits});`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -332,13 +342,13 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_${vKind}`)}(${path}, 267, ${id(f.keyName)}, ${vBits});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_${vKind}`)}(${path}, 267, ${key}, ${vBits});`, ); }); ctx.append(`}`); } else if (t.value === "Bool") { ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_int`)}(${path}, 267, 1);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_int`)}(${path}, 267, 1);`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -346,13 +356,13 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_int`)}(${path}, 267, ${id(f.keyName)}, 1);`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_int`)}(${path}, 267, ${key}, 1);`, ); }); ctx.append(`}`); } else if (t.value === "Cell") { ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -360,13 +370,13 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${id(f.keyName)});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, ); }); ctx.append(`}`); } else if (t.value === "Address") { ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_slice`)}(${path}, 267);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_slice`)}(${path}, 267);`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { @@ -374,25 +384,25 @@ export function writeStatement( writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_slice`)}(${path}, 267, ${id(f.keyName)});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_slice`)}(${path}, 267, ${key});`, ); }); ctx.append(`}`); } else { // value is struct ctx.append( - `var (${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, ); ctx.append(`while (${flag}) {`); ctx.inIndent(() => { ctx.append( - `var ${resolveFuncTypeUnpack(t.value, id(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${id(f.valueName)}));`, + `var ${resolveFuncTypeUnpack(t.value, id(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, ); for (const s of f.statements) { writeStatement(s, self, returns, ctx); } ctx.append( - `(${id(f.keyName)}, ${id(f.valueName)}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${id(f.keyName)});`, + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, ); }); ctx.append(`}`); diff --git a/src/test/e2e-emulated/contracts/underscore-variable.tact b/src/test/e2e-emulated/contracts/underscore-variable.tact new file mode 100644 index 000000000..485bdefbc --- /dev/null +++ b/src/test/e2e-emulated/contracts/underscore-variable.tact @@ -0,0 +1,42 @@ +contract UnderscoreVariableTestContract { + init() { + // Nothing to do + } + + receive() { + // Nothing to do + } + + get fun test1(): Int { + try { + nativeThrow(1); + } catch (_) { + return 0; + } + return 1; + } + + get fun test2(): Int { + let m: map = emptyMap(); + m.set(1, 2); + m.set(2, 4); + m.set(3, 6); + let x: Int = 0; + foreach (_, v in m) { + x += v; + } + return x; + } + + get fun test3(): Int { + let m: map = emptyMap(); + m.set(1, 2); + m.set(2, 4); + m.set(3, 6); + let x: Int = 0; + foreach (k, _ in m) { + x += k; + } + return x; + } +} \ No newline at end of file diff --git a/src/test/e2e-emulated/underscore-variable.spec.ts b/src/test/e2e-emulated/underscore-variable.spec.ts new file mode 100644 index 000000000..15ecf107b --- /dev/null +++ b/src/test/e2e-emulated/underscore-variable.spec.ts @@ -0,0 +1,25 @@ +import { toNano } from "@ton/core"; +import { ContractSystem } from "@tact-lang/emulator"; +import { __DANGER_resetNodeId } from "../../grammar/ast"; +import { UnderscoreVariableTestContract } from "./contracts/output/underscore-variable_UnderscoreVariableTestContract"; + +describe("underscore-variable", () => { + beforeEach(() => { + __DANGER_resetNodeId(); + }); + it("should implement underscore variables correctly", async () => { + // Init + const system = await ContractSystem.create(); + const treasure = system.treasure("treasure"); + const contract = system.open( + await UnderscoreVariableTestContract.fromInit(), + ); + await contract.send(treasure, { value: toNano("10") }, null); + await system.run(); + + // Check methods + expect(await contract.getTest1()).toEqual(0n); + expect(await contract.getTest2()).toEqual(12n); + expect(await contract.getTest3()).toEqual(6n); + }); +}); diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index fe3e1af24..1adb0dd56 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -580,6 +580,26 @@ Line 7, col 9: " `; +exports[`resolveStatements should fail statements for var-underscore-name-access 1`] = ` +":6:16: Unable to resolve id _ +Line 6, col 16: + 5 | foreach (_, _ in m) { +> 6 | return _; + ^ + 7 | } +" +`; + +exports[`resolveStatements should fail statements for var-underscore-name-access2 1`] = ` +":7:14: Unable to resolve id _ +Line 7, col 14: + 6 | foreach (_, v in m) { +> 7 | x += _; + ^ + 8 | } +" +`; + exports[`resolveStatements should resolve statements for contract-receiver-bounced 1`] = ` [ [ @@ -1602,3 +1622,45 @@ exports[`resolveStatements should resolve statements for var-scope-valueOf-fun 1 ], ] `; + +exports[`resolveStatements should resolve statements for var-underscore-name-in-foreach 1`] = ` +[ + [ + "emptyMap()", + "", + ], + [ + "m", + "map", + ], +] +`; + +exports[`resolveStatements should resolve statements for var-underscore-name-in-foreach2 1`] = ` +[ + [ + "emptyMap()", + "", + ], + [ + "0", + "Int", + ], + [ + "m", + "map", + ], + [ + "x", + "Int", + ], + [ + "v", + "Int", + ], + [ + "x", + "Int", + ], +] +`; diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index d5502fe1a..15aa59110 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -64,6 +64,9 @@ function addVariable( if (src.vars.has(name)) { throw Error("Variable already exists: " + name); // Should happen earlier } + if (name == "_") { + throw Error("Variable name cannot be '_'"); + } return { ...src, vars: new Map(src.vars).set(name, ref), @@ -388,15 +391,22 @@ function processStatements( const r = processStatements(s.statements, sctx, ctx); ctx = r.ctx; + let catchCtx = sctx; + // Process catchName variable for exit code - if (initialCtx.vars.has(s.catchName)) { - throwError(`Variable already exists: "${s.catchName}"`, s.ref); + if (s.catchName != "_") { + if (initialCtx.vars.has(s.catchName)) { + throwError( + `Variable already exists: "${s.catchName}"`, + s.ref, + ); + } + catchCtx = addVariable( + s.catchName, + { kind: "ref", name: "Int", optional: false }, + initialCtx, + ); } - let catchCtx = addVariable( - s.catchName, - { kind: "ref", name: "Int", optional: false }, - initialCtx, - ); // Process catch statements const rCatch = processStatements(s.catchStatements, catchCtx, ctx); @@ -432,23 +442,35 @@ function processStatements( ); } + let foreachCtx = sctx; + // Add key and value to statement context - if (initialCtx.vars.has(s.keyName)) { - throwError(`Variable already exists: "${s.keyName}"`, s.ref); + if (s.keyName != "_") { + if (initialCtx.vars.has(s.keyName)) { + throwError( + `Variable already exists: "${s.keyName}"`, + s.ref, + ); + } + foreachCtx = addVariable( + s.keyName, + { kind: "ref", name: mapType.key, optional: false }, + initialCtx, + ); } - let foreachCtx = addVariable( - s.keyName, - { kind: "ref", name: mapType.key, optional: false }, - initialCtx, - ); - if (foreachCtx.vars.has(s.valueName)) { - throwError(`Variable already exists: "${s.valueName}"`, s.ref); + if (s.valueName != "_") { + if (foreachCtx.vars.has(s.valueName)) { + throwError( + `Variable already exists: "${s.valueName}"`, + s.ref, + ); + } + foreachCtx = addVariable( + s.valueName, + { kind: "ref", name: mapType.value, optional: false }, + foreachCtx, + ); } - foreachCtx = addVariable( - s.valueName, - { kind: "ref", name: mapType.value, optional: false }, - foreachCtx, - ); // Process inner statements const r = processStatements(s.statements, foreachCtx, ctx); diff --git a/src/types/stmts-failed/var-underscore-name-access.tact b/src/types/stmts-failed/var-underscore-name-access.tact new file mode 100644 index 000000000..82fdae5bc --- /dev/null +++ b/src/types/stmts-failed/var-underscore-name-access.tact @@ -0,0 +1,9 @@ +primitive Int; + +fun test(): Int { + let m: map = emptyMap(); + foreach (_, _ in m) { + return _; + } + return 0; +} diff --git a/src/types/stmts-failed/var-underscore-name-access2.tact b/src/types/stmts-failed/var-underscore-name-access2.tact new file mode 100644 index 000000000..236352d4a --- /dev/null +++ b/src/types/stmts-failed/var-underscore-name-access2.tact @@ -0,0 +1,10 @@ +primitive Int; + +fun test(): Int { + let m: map = emptyMap(); + let x: Int = 0; + foreach (_, v in m) { + x += _; + } + return x; +} diff --git a/src/types/stmts/var-underscore-name-in-foreach.tact b/src/types/stmts/var-underscore-name-in-foreach.tact new file mode 100644 index 000000000..a8410783e --- /dev/null +++ b/src/types/stmts/var-underscore-name-in-foreach.tact @@ -0,0 +1,8 @@ +primitive Int; + +fun test() { + let m: map = emptyMap(); + foreach (_, _ in m) { + // something + } +} diff --git a/src/types/stmts/var-underscore-name-in-foreach2.tact b/src/types/stmts/var-underscore-name-in-foreach2.tact new file mode 100644 index 000000000..ea816f116 --- /dev/null +++ b/src/types/stmts/var-underscore-name-in-foreach2.tact @@ -0,0 +1,10 @@ +primitive Int; + +fun test(): Int { + let m: map = emptyMap(); + let x: Int = 0; + foreach (_, v in m) { + x += v; + } + return x; +} diff --git a/tact.config.json b/tact.config.json index 11753842d..0203b4139 100644 --- a/tact.config.json +++ b/tact.config.json @@ -94,6 +94,11 @@ "path": "./src/test/e2e-emulated/contracts/mutating-method-chaining.tact", "output": "./src/test/e2e-emulated/contracts/output" }, + { + "name": "underscore-variable", + "path": "./src/test/e2e-emulated/contracts/underscore-variable.tact", + "output": "./src/test/e2e-emulated/contracts/output" + }, { "name": "optionals", "path": "./src/test/e2e-emulated/contracts/optionals.tact", From fa220cd06a1103660fd620d3cd77069af871f971 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Wed, 15 May 2024 16:58:02 +0300 Subject: [PATCH 2/4] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5001a245d..3c692d0f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `let` statements can now be used without an explicit type declaration and determine the type automatically if it was not specified: PR [#198](https://github.com/tact-lang/tact/pull/198) - The outdated TextMate-style grammar files for text editors have been removed (the most recent grammar files can be found in the [tact-sublime](https://github.com/tact-lang/tact-sublime) repo): PR [#404](https://github.com/tact-lang/tact/pull/404) - The JSON schema for `tact.config.json` has been moved to the `json-schemas` project folder: PR [#404](https://github.com/tact-lang/tact/pull/404) +- Allow underscores as unused variable identifiers: PR [#338](https://github.com/tact-lang/tact/pull/338) ### Fixed From a9002a67d491018274ccd7ade99328b2893b1b99 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Tue, 11 Jun 2024 12:44:14 +0300 Subject: [PATCH 3/4] implement for let-statements --- src/generator/writers/writeFunction.ts | 6 +++++ .../contracts/underscore-variable.tact | 15 ++++++++++- .../e2e-emulated/underscore-variable.spec.ts | 1 + .../resolveStatements.spec.ts.snap | 27 +++++++++++++++++++ src/types/resolveStatements.ts | 8 ++++-- .../var-underscore-name-access3.tact | 10 +++++++ .../stmts/var-underscore-name-in-let.tact | 10 +++++++ 7 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/types/stmts-failed/var-underscore-name-access3.tact create mode 100644 src/types/stmts/var-underscore-name-in-let.tact diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index 01112c250..10cbb6e63 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -90,6 +90,12 @@ export function writeStatement( } return; } else if (f.kind === "statement_let") { + // Underscore name case + if (f.name === "_") { + ctx.append(`${writeExpression(f.expression, ctx)};`); + return; + } + // Contract/struct case const t = f.type === null diff --git a/src/test/e2e-emulated/contracts/underscore-variable.tact b/src/test/e2e-emulated/contracts/underscore-variable.tact index 485bdefbc..41c11e091 100644 --- a/src/test/e2e-emulated/contracts/underscore-variable.tact +++ b/src/test/e2e-emulated/contracts/underscore-variable.tact @@ -1,12 +1,19 @@ contract UnderscoreVariableTestContract { + something: Int; + init() { - // Nothing to do + self.something = 0; } receive() { // Nothing to do } + fun increaseSomething(): Int { + self.something += 1; + return 123; + } + get fun test1(): Int { try { nativeThrow(1); @@ -39,4 +46,10 @@ contract UnderscoreVariableTestContract { } return x; } + + get fun test4(): Int { + let _: Int = self.increaseSomething(); + let _: Int = self.increaseSomething(); + return self.something; + } } \ No newline at end of file diff --git a/src/test/e2e-emulated/underscore-variable.spec.ts b/src/test/e2e-emulated/underscore-variable.spec.ts index 15ecf107b..cb08eb185 100644 --- a/src/test/e2e-emulated/underscore-variable.spec.ts +++ b/src/test/e2e-emulated/underscore-variable.spec.ts @@ -21,5 +21,6 @@ describe("underscore-variable", () => { expect(await contract.getTest1()).toEqual(0n); expect(await contract.getTest2()).toEqual(12n); expect(await contract.getTest3()).toEqual(6n); + expect(await contract.getTest4()).toEqual(2n); }); }); diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 1adb0dd56..7314a2bec 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -600,6 +600,16 @@ Line 7, col 14: " `; +exports[`resolveStatements should fail statements for var-underscore-name-access3 1`] = ` +":9:12: Unable to resolve id _ +Line 9, col 12: + 8 | let _: Int = someImpureFunction(); +> 9 | return _; + ^ + 10 | } +" +`; + exports[`resolveStatements should resolve statements for contract-receiver-bounced 1`] = ` [ [ @@ -1664,3 +1674,20 @@ exports[`resolveStatements should resolve statements for var-underscore-name-in- ], ] `; + +exports[`resolveStatements should resolve statements for var-underscore-name-in-let 1`] = ` +[ + [ + "123", + "Int", + ], + [ + "someImpureFunction()", + "Int", + ], + [ + "123", + "Int", + ], +] +`; diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 15aa59110..1e1cbd574 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -188,12 +188,16 @@ function processStatements( s.ref, ); } - sctx = addVariable(s.name, variableType, sctx); + if (s.name !== "_") { + sctx = addVariable(s.name, variableType, sctx); + } } else { if (expressionType.kind === "null") { throwError(`Cannot infer type for "${s.name}"`, s.ref); } - sctx = addVariable(s.name, expressionType, sctx); + if (s.name !== "_") { + sctx = addVariable(s.name, expressionType, sctx); + } } } else if (s.kind === "statement_assign") { // Process lvalue diff --git a/src/types/stmts-failed/var-underscore-name-access3.tact b/src/types/stmts-failed/var-underscore-name-access3.tact new file mode 100644 index 000000000..b83d7e3f8 --- /dev/null +++ b/src/types/stmts-failed/var-underscore-name-access3.tact @@ -0,0 +1,10 @@ +primitive Int; + +fun someImpureFunction(): Int { + return 123; +} + +fun test(): Int { + let _: Int = someImpureFunction(); + return _; +} diff --git a/src/types/stmts/var-underscore-name-in-let.tact b/src/types/stmts/var-underscore-name-in-let.tact new file mode 100644 index 000000000..9c2ac2d9b --- /dev/null +++ b/src/types/stmts/var-underscore-name-in-let.tact @@ -0,0 +1,10 @@ +primitive Int; + +fun someImpureFunction(): Int { + return 123; +} + +fun test(): Int { + let _: Int = someImpureFunction(); + return 123; +} From 1c4143e40edaeb5360e8058acb1bd889aa5d0ccd Mon Sep 17 00:00:00 2001 From: Gusarich Date: Fri, 14 Jun 2024 12:38:36 +0300 Subject: [PATCH 4/4] resolve review comments --- .../contracts/underscore-variable.tact | 2 + .../e2e-emulated/underscore-variable.spec.ts | 2 +- .../resolveStatements.spec.ts.snap | 10 ++- src/types/resolveExpression.ts | 6 ++ src/types/resolveStatements.ts | 65 ++++++++----------- .../stmts/var-underscore-name-in-let.tact | 1 + 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/test/e2e-emulated/contracts/underscore-variable.tact b/src/test/e2e-emulated/contracts/underscore-variable.tact index 41c11e091..73fbaeef7 100644 --- a/src/test/e2e-emulated/contracts/underscore-variable.tact +++ b/src/test/e2e-emulated/contracts/underscore-variable.tact @@ -50,6 +50,8 @@ contract UnderscoreVariableTestContract { get fun test4(): Int { let _: Int = self.increaseSomething(); let _: Int = self.increaseSomething(); + let _ = self.increaseSomething(); + let _ = self.increaseSomething(); return self.something; } } \ No newline at end of file diff --git a/src/test/e2e-emulated/underscore-variable.spec.ts b/src/test/e2e-emulated/underscore-variable.spec.ts index cb08eb185..e447c2cda 100644 --- a/src/test/e2e-emulated/underscore-variable.spec.ts +++ b/src/test/e2e-emulated/underscore-variable.spec.ts @@ -21,6 +21,6 @@ describe("underscore-variable", () => { expect(await contract.getTest1()).toEqual(0n); expect(await contract.getTest2()).toEqual(12n); expect(await contract.getTest3()).toEqual(6n); - expect(await contract.getTest4()).toEqual(2n); + expect(await contract.getTest4()).toEqual(4n); }); }); diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 7314a2bec..c8d73e1c3 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -581,7 +581,7 @@ Line 7, col 9: `; exports[`resolveStatements should fail statements for var-underscore-name-access 1`] = ` -":6:16: Unable to resolve id _ +":6:16: Wildcard variable name '_' cannot be accessed Line 6, col 16: 5 | foreach (_, _ in m) { > 6 | return _; @@ -591,7 +591,7 @@ Line 6, col 16: `; exports[`resolveStatements should fail statements for var-underscore-name-access2 1`] = ` -":7:14: Unable to resolve id _ +":7:14: Wildcard variable name '_' cannot be accessed Line 7, col 14: 6 | foreach (_, v in m) { > 7 | x += _; @@ -601,7 +601,7 @@ Line 7, col 14: `; exports[`resolveStatements should fail statements for var-underscore-name-access3 1`] = ` -":9:12: Unable to resolve id _ +":9:12: Wildcard variable name '_' cannot be accessed Line 9, col 12: 8 | let _: Int = someImpureFunction(); > 9 | return _; @@ -1685,6 +1685,10 @@ exports[`resolveStatements should resolve statements for var-underscore-name-in- "someImpureFunction()", "Int", ], + [ + "someImpureFunction()", + "Int", + ], [ "123", "Int", diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 99d72ac27..33c920846 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -766,6 +766,12 @@ export function resolveExpression( const v = sctx.vars.get(exp.value); if (!v) { if (!hasStaticConstant(ctx, exp.value)) { + if (exp.value === "_") { + throwError( + "Wildcard variable name '_' cannot be accessed", + exp.ref, + ); + } throwError("Unable to resolve id " + exp.value, exp.ref); } else { const cc = getStaticConstant(ctx, exp.value); diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 1e1cbd574..3f39c9898 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -29,6 +29,20 @@ function emptyContext(root: ASTRef, returns: TypeRef): StatementContext { }; } +function checkVariableExists( + ctx: StatementContext, + name: string, + ref?: ASTRef, +): void { + if (ctx.vars.has(name)) { + if (ref) { + throwError(`Variable already exists: "${name}"`, ref); + } else { + throw Error(`Variable already exists: "${name}"`); + } + } +} + function addRequiredVariables( name: string, src: StatementContext, @@ -61,11 +75,9 @@ function addVariable( ref: TypeRef, src: StatementContext, ): StatementContext { - if (src.vars.has(name)) { - throw Error("Variable already exists: " + name); // Should happen earlier - } + checkVariableExists(src, name); // Should happen earlier if (name == "_") { - throw Error("Variable name cannot be '_'"); + return src; } return { ...src, @@ -174,9 +186,7 @@ function processStatements( ctx = resolveExpression(s.expression, sctx, ctx); // Check variable name - if (sctx.vars.has(s.name)) { - throwError(`Variable "${s.name}" already exists`, s.ref); - } + checkVariableExists(sctx, s.name, s.ref); // Check type const expressionType = getExpType(ctx, s.expression); @@ -188,16 +198,12 @@ function processStatements( s.ref, ); } - if (s.name !== "_") { - sctx = addVariable(s.name, variableType, sctx); - } + sctx = addVariable(s.name, variableType, sctx); } else { if (expressionType.kind === "null") { throwError(`Cannot infer type for "${s.name}"`, s.ref); } - if (s.name !== "_") { - sctx = addVariable(s.name, expressionType, sctx); - } + sctx = addVariable(s.name, expressionType, sctx); } } else if (s.kind === "statement_assign") { // Process lvalue @@ -398,19 +404,12 @@ function processStatements( let catchCtx = sctx; // Process catchName variable for exit code - if (s.catchName != "_") { - if (initialCtx.vars.has(s.catchName)) { - throwError( - `Variable already exists: "${s.catchName}"`, - s.ref, - ); - } - catchCtx = addVariable( - s.catchName, - { kind: "ref", name: "Int", optional: false }, - initialCtx, - ); - } + checkVariableExists(initialCtx, s.catchName, s.ref); + catchCtx = addVariable( + s.catchName, + { kind: "ref", name: "Int", optional: false }, + initialCtx, + ); // Process catch statements const rCatch = processStatements(s.catchStatements, catchCtx, ctx); @@ -450,12 +449,7 @@ function processStatements( // Add key and value to statement context if (s.keyName != "_") { - if (initialCtx.vars.has(s.keyName)) { - throwError( - `Variable already exists: "${s.keyName}"`, - s.ref, - ); - } + checkVariableExists(initialCtx, s.keyName, s.ref); foreachCtx = addVariable( s.keyName, { kind: "ref", name: mapType.key, optional: false }, @@ -463,12 +457,7 @@ function processStatements( ); } if (s.valueName != "_") { - if (foreachCtx.vars.has(s.valueName)) { - throwError( - `Variable already exists: "${s.valueName}"`, - s.ref, - ); - } + checkVariableExists(foreachCtx, s.valueName, s.ref); foreachCtx = addVariable( s.valueName, { kind: "ref", name: mapType.value, optional: false }, diff --git a/src/types/stmts/var-underscore-name-in-let.tact b/src/types/stmts/var-underscore-name-in-let.tact index 9c2ac2d9b..6422a59bc 100644 --- a/src/types/stmts/var-underscore-name-in-let.tact +++ b/src/types/stmts/var-underscore-name-in-let.tact @@ -6,5 +6,6 @@ fun someImpureFunction(): Int { fun test(): Int { let _: Int = someImpureFunction(); + let _ = someImpureFunction(); return 123; }