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

fix: support T and T? methods with the same name #1758

Closed
wants to merge 18 commits into from
1 change: 1 addition & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Forbid the `override` modifier for constants without the corresponding super-constant: PR [#1591](https://github.com/tact-lang/tact/pull/1591)
- Check map types for `deepEquals` method: PR [#1718](https://github.com/tact-lang/tact/pull/1718)
- Remove "remainder" from error messages: PR [#1699](https://github.com/tact-lang/tact/pull/1699)
- Support `T` and `T?` methods with the same name: PR [#1758](https://github.com/tact-lang/tact/pull/1758)
- Check map types for `deepEquals` method: PR [#1718](https://github.com/tact-lang/tact/pull/1718)

### Docs
Expand Down
2 changes: 1 addition & 1 deletion src/abi/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([
} else if (arg0.kind === "ref") {
if (arg0.name === "Int") {
const exp = writeExpression(resolved[0]!, ctx);
return `${ctx.used(`__tact_debug_str`)}(${ctx.used(ops.extension("Int", "toString"))}(${exp}), ${debugPrint2}, "${debugPrint1}")`;
return `${ctx.used(`__tact_debug_str`)}(${ctx.used(ops.extension("Int", false, "toString"))}(${exp}), ${debugPrint2}, "${debugPrint1}")`;
} else if (arg0.name === "Bool") {
const exp = writeExpression(resolved[0]!, ctx);
return `${ctx.used(`__tact_debug_bool`)}(${exp}, ${debugPrint2}, "${debugPrint1}")`;
Expand Down
2 changes: 1 addition & 1 deletion src/abi/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export const MapFunctions: ReadonlyMap<string, AbiFunction> = new Map([
const [self] = args;
checkMapType(self, ref);

return { kind: "ref", name: "Cell", optional: true };
return { kind: "ref", name: "Cell", optional: false };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, this wouldn't fail at runtime?

contract Foo {
    get fun foo(): Slice {
        let x: map<Int, Int> = emptyMap();
        return x.asCell().asSlice();
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, now we just generate the qualifier expression as is, so mp.asCell() becomes mp in the FunC code:

return writeExpression(exprs[0]!, ctx);

Thus, asCell simply returns non optional Cell type

},
generate(
ctx: WriterContext,
Expand Down
4 changes: 4 additions & 0 deletions src/generator/createABI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI {
// Structs
const types: ABIType[] = [];
for (const t of allTypes) {
if (t.optional) {
continue;
}

if (t.kind === "struct") {
types.push({
name: t.name,
Expand Down
15 changes: 15 additions & 0 deletions src/generator/writeProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ export async function writeProgram(
const emittedTypes: string[] = [];
const types = getSortedTypes(ctx);
for (const t of types) {
if (t.optional) {
// no need to generate anything for T?
continue;
}

const ffs: WrittenFunction[] = [];
if (t.kind === "struct" || t.kind === "contract" || t.kind == "trait") {
const typeFunctions = tryExtractModule(
Expand Down Expand Up @@ -312,6 +317,11 @@ function writeAll(
// Serializers
const sortedTypes = getSortedTypes(ctx);
for (const t of sortedTypes) {
if (t.optional) {
// no need to generate serialization for T?
continue;
}

if (t.kind === "contract" || t.kind === "struct") {
const allocation = getAllocation(ctx, t.name);
const allocationBounced = getAllocation(ctx, toBounced(t.name));
Expand Down Expand Up @@ -352,6 +362,11 @@ function writeAll(

// Accessors
for (const t of allTypes) {
if (t.optional) {
// no need to generate accessors for T?
continue;
}

if (t.kind === "contract" || t.kind === "struct") {
writeAccessors(t, t.origin, wCtx);
}
Expand Down
3 changes: 2 additions & 1 deletion src/generator/writers/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ export const ops = {
receiveBounceAny: (type: string) => `$${type}$_receive_bounce_fallback`,

// Functions
extension: (type: string, name: string) => `$${type}$_fun_${name}`,
extension: (type: string, option: boolean, name: string) =>
`$${type}${option ? "_optional" : ""}$_fun_${name}`,
global: (name: string) => `$global_${name}`,
nonModifying: (name: string) => `${name}$not_mut`,

Expand Down
12 changes: 10 additions & 2 deletions src/generator/writers/writeExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,11 @@ export function writeExpression(
// Reference type
if (selfTyRef.kind === "ref") {
// Render function call
const selfTy = getType(wCtx.ctx, selfTyRef.name);
const selfTy = getType(
wCtx.ctx,
selfTyRef.name,
selfTyRef.optional,
);

// Check struct ABI
if (selfTy.kind === "struct") {
Expand All @@ -593,7 +597,11 @@ export function writeExpression(

// Resolve function
const methodDescr = selfTy.functions.get(idText(f.method))!;
let name = ops.extension(selfTyRef.name, idText(f.method));
let name = ops.extension(
selfTyRef.name,
selfTyRef.optional,
idText(f.method),
);
if (
methodDescr.ast.kind === "function_def" ||
methodDescr.ast.kind === "function_decl" ||
Expand Down
8 changes: 4 additions & 4 deletions src/generator/writers/writeFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ function writeCondition(
export function writeFunction(f: FunctionDescription, ctx: WriterContext) {
const [self, isSelfOpt] =
f.self?.kind === "ref"
? [getType(ctx.ctx, f.self.name), f.self.optional]
? [getType(ctx.ctx, f.self.name, f.self.optional), f.self.optional]
: [null, false];

// Write function header
Expand Down Expand Up @@ -582,7 +582,7 @@ export function writeFunction(f: FunctionDescription, ctx: WriterContext) {

case "asm_function_def": {
const name = self
? ops.extension(self.name, f.name)
? ops.extension(self.name, self.optional, f.name)
: ops.global(f.name);
ctx.fun(name, () => {
const { functionParams, shuffle } = getAsmFunctionSignature(
Expand Down Expand Up @@ -616,7 +616,7 @@ export function writeFunction(f: FunctionDescription, ctx: WriterContext) {

case "function_def": {
const name = self
? ops.extension(self.name, f.name)
? ops.extension(self.name, self.optional, f.name)
: ops.global(f.name);

ctx.fun(name, () => {
Expand Down Expand Up @@ -790,7 +790,7 @@ export function writeGetter(f: FunctionDescription, wCtx: WriterContext) {

// Execute get method
wCtx.append(
`var res = self~${wCtx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`,
`var res = self~${wCtx.used(ops.extension(self.name, self.optional, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`,
);

// Pack if needed
Expand Down
7 changes: 7 additions & 0 deletions src/storage/__snapshots__/resolveAllocation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "struct",
"name": "Point",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down Expand Up @@ -365,6 +366,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "struct",
"name": "Point2",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down Expand Up @@ -613,6 +615,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "struct",
"name": "Point3",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down Expand Up @@ -1646,6 +1649,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "struct",
"name": "Deep",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down Expand Up @@ -1996,6 +2000,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "struct",
"name": "Deep2",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down Expand Up @@ -2321,6 +2326,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "contract",
"name": "Sample",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down Expand Up @@ -2351,6 +2357,7 @@ exports[`resolveAllocation should write program 1`] = `
"interfaces": [],
"kind": "trait",
"name": "BaseTrait",
"optional": false,
"origin": "user",
"partialFieldCount": 0,
"receivers": [],
Expand Down
4 changes: 4 additions & 0 deletions src/test/e2e-emulated/contracts/non-mutating-methods.tact
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ extends fun equal(self: SomeStruct?, other: SomeStruct?): Bool {
return self!!.i == other!!.i && self!!.b == other!!.b;
}

extends fun equal(self: SomeStruct, other: SomeStruct): Bool {
return self.i == other.i && self.b == other.b;
}

contract Tester {
receive() { }

Expand Down
34 changes: 34 additions & 0 deletions src/test/e2e-emulated/contracts/optionals-3.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
struct SomeStruct { i: Int; b: Bool }

extends fun equal(self: SomeStruct?, other: SomeStruct?): Bool {
if (self == null && other == null) { return true }
if (self == null || other == null) { return false }
return self!!.i == other!!.i && self!!.b == other!!.b;
}

extends fun equal(self: SomeStruct, other: SomeStruct): Bool {
return self.i == other.i && self.b == other.b;
}

contract Opt5 {
receive() { }

get fun test1(): Bool {
let s1 = SomeStruct {i: 42, b: true};
let s2 = SomeStruct {i: 42, b: false};
return s1.equal(s1)
}

get fun test2(): Bool {
let s1 = SomeStruct {i: 42, b: true};
let s2 = SomeStruct {i: 42, b: false};
let s3: SomeStruct? = null;
return s1.equal(s1) && !s3.equal(s2);
}

get fun test3(): Bool {
let s1: SomeStruct? = null;
let s2: SomeStruct? = null;
return !s1.equal(s2);
}
}
38 changes: 38 additions & 0 deletions src/test/e2e-emulated/optionals-3.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { toNano } from "@ton/core";
import type { SandboxContract, TreasuryContract } from "@ton/sandbox";
import { Blockchain } from "@ton/sandbox";
import { Opt5 } from "./contracts/output/optionals-3_Opt5";
import "@ton/test-utils";

describe("optionals", () => {
let blockchain: Blockchain;
let treasure: SandboxContract<TreasuryContract>;
let contract: SandboxContract<Opt5>;

beforeEach(async () => {
blockchain = await Blockchain.create();
blockchain.verbosity.print = false;
treasure = await blockchain.treasury("treasure");

contract = blockchain.openContract(await Opt5.fromInit());

const deployResult = await contract.send(
treasure.getSender(),
{ value: toNano("10") },
null,
);

expect(deployResult.transactions).toHaveTransaction({
from: treasure.address,
to: contract.address,
success: true,
deploy: true,
});
});

it("should calculate correct result", async () => {
expect(await contract.getTest1()).toEqual(true);
expect(await contract.getTest2()).toEqual(true);
expect(await contract.getTest3()).toEqual(false);
});
});
Loading
Loading