Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow underscores as unused variable identifiers #338

Merged
merged 5 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
anton-trunov marked this conversation as resolved.
Show resolved Hide resolved
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
Loading