Skip to content

Commit

Permalink
feat: allow underscores as unused variable identifiers (#338)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gusarich authored Jun 14, 2024
1 parent a437543 commit c4ffbf0
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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)
- The default compilation mode does decompile BoC files anymore, to additionally perform decompilation at the end of the pipeline, set the `fullWithDecompilation` mode in the `mode` project properties of `tact.config.json`: PR [#417](https://github.com/tact-lang/tact/pull/417)

### Fixed
Expand Down
62 changes: 39 additions & 23 deletions src/generator/writers/writeFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -195,7 +201,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);
Expand All @@ -215,6 +225,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") {
Expand All @@ -237,75 +253,75 @@ 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(() => {
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}_${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(() => {
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}_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(() => {
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(`}`);
} 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(() => {
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}_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(`}`);
Expand All @@ -324,75 +340,75 @@ 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(() => {
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_${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(() => {
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_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(() => {
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(`}`);
} 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(() => {
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_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(`}`);
Expand Down
57 changes: 57 additions & 0 deletions src/test/e2e-emulated/contracts/underscore-variable.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
contract UnderscoreVariableTestContract {
something: Int;

init() {
self.something = 0;
}

receive() {
// Nothing to do
}

fun increaseSomething(): Int {
self.something += 1;
return 123;
}

get fun test1(): Int {
try {
nativeThrow(1);
} catch (_) {
return 0;
}
return 1;
}

get fun test2(): Int {
let m: map<Int, Int> = 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<Int, Int> = 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;
}

get fun test4(): Int {
let _: Int = self.increaseSomething();
let _: Int = self.increaseSomething();
let _ = self.increaseSomething();
let _ = self.increaseSomething();
return self.something;
}
}
26 changes: 26 additions & 0 deletions src/test/e2e-emulated/underscore-variable.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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);
expect(await contract.getTest4()).toEqual(4n);
});
});
Loading

0 comments on commit c4ffbf0

Please sign in to comment.