From 0b25fbc72b892b44ab80174ae46b5e2c251eb17e Mon Sep 17 00:00:00 2001 From: Gusarich Date: Wed, 15 May 2024 16:56:05 +0300 Subject: [PATCH] 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",