Skip to content

Commit

Permalink
Merge branch 'main' into stack-deepness
Browse files Browse the repository at this point in the history
  • Loading branch information
jeshecdom committed Jan 28, 2025
2 parents 4b1bb31 + 6fac963 commit a6bd7a5
Show file tree
Hide file tree
Showing 11 changed files with 392 additions and 3 deletions.
2 changes: 2 additions & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allow serialization specifiers for trait fields PR: [#1303](https://github.com/tact-lang/tact/pull/1303)
- Remove unused typechecker wrapper with the file `check.ts` it is contained in: PR [#1313](https://github.com/tact-lang/tact/pull/1313)
- Unified `StatementTry` and `StatementTryCatch` AST nodes: PR [#1418](https://github.com/tact-lang/tact/pull/1418)
- Calling methods on `null` when `self` is of an optional type is now allowed: PR [#1567](https://github.com/tact-lang/tact/pull/1567)
- Make `msg_bounced` last parameter of `*_contract_router_internal` for better code generation: PR [#1585](https://github.com/tact-lang/tact/pull/1585)
- Inline `*_contract_init` function: PR [#1589](https://github.com/tact-lang/tact/pull/1589)
- Better error message for `unresolved name` error: PR [#1595](https://github.com/tact-lang/tact/pull/1595)
Expand Down Expand Up @@ -69,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Throwing from functions with non-trivial branching in the `try` statement: PR [#1501](https://github.com/tact-lang/tact/pull/1501)
- Forbid read and write to self in contract init function: PR [#1482](https://github.com/tact-lang/tact/pull/1482)
- Support for using a constant within another constant and for the default value of a struct field before constant declaration: PR [#1478](https://github.com/tact-lang/tact/pull/1478)
- Incorrect call generation to a mutation function: PR [#1608](https://github.com/tact-lang/tact/pull/1608)

### Docs

Expand Down
8 changes: 6 additions & 2 deletions src/generator/writers/writeFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,9 +702,13 @@ function writeNonMutatingFunction(
ctx.context("stdlib");
}
ctx.body(() => {
const params = f.ast.params;
const withoutSelfParams =
params.length > 0 && params.at(0)?.name.text === "self"
? params.slice(1)
: params;
ctx.append(
`return ${funcIdOf("self")}~${markUsedName ? ctx.used(name) : name}(${f.ast.params
.slice(1)
`return ${funcIdOf("self")}~${markUsedName ? ctx.used(name) : name}(${withoutSelfParams
.map((arg) => funcIdOf(arg.name))
.join(", ")});`,
);
Expand Down
20 changes: 19 additions & 1 deletion src/test/e2e-emulated/contracts/mutating-methods.tact
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ struct Foo {
s: Slice;
}

struct MyStruct {
age: Int;
}

fun returnMyStruct(): MyStruct {
return MyStruct{ age: 10 };
}

extends mutates fun setAge(self: MyStruct, age: Int): Int {
let old = self.age;
self.age = age;
return old + self.age;
}

contract Tester {
s: Slice;

Expand Down Expand Up @@ -84,4 +98,8 @@ contract Tester {
x.multiplyExtends(2).multiplyExtends(3);
return x.multiplyExtends(2).multiplyExtends(3);
}
}

get fun test12(): Int {
return returnMyStruct().setAge(20);
}
}
1 change: 1 addition & 0 deletions src/test/e2e-emulated/mutating-methods.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,6 @@ describe("bugs", () => {

expect(await contract.getTest11(1n)).toBe(6n);
expect(await contract.getTest11(2n)).toBe(12n);
expect(await contract.getTest12()).toBe(30n);
});
});
229 changes: 229 additions & 0 deletions src/types/__snapshots__/resolveStatements.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,24 @@ exports[`resolveStatements should fail statements for expr-map-exists-method-on-
"
`;

exports[`resolveStatements should fail statements for expr-method-call-null-ambiguous 1`] = `
"<unknown>:19:16: Ambiguous method call "foo"
18 | get fun test(): Bool {
> 19 | return null.foo();
^~~~~~~~~~
20 | }
"
`;

exports[`resolveStatements should fail statements for expr-method-call-null-not-existing 1`] = `
"<unknown>:14:16: Invalid type "<null>" for function call
13 | get fun test(): Bool {
> 14 | return null.bar();
^~~~~~~~~~
15 | }
"
`;

exports[`resolveStatements should fail statements for expr-method-does-not-exist-but-field-does 1`] = `
"<unknown>:13:5: Type "S" does not have a function named "x()", did you mean field "x" instead?
12 | let s: S = S{ x: 1 };
Expand Down Expand Up @@ -2530,6 +2548,217 @@ exports[`resolveStatements should resolve statements for expr-maps-exists-method
]
`;

exports[`resolveStatements should resolve statements for expr-method-call-null 1`] = `
[
[
"self",
"Int?",
],
[
"null",
"<null>",
],
[
"self == null",
"Bool",
],
[
"false",
"Bool",
],
[
"self",
"Int?",
],
[
"self!!",
"Int",
],
[
"42",
"Int",
],
[
"self!! == 42",
"Bool",
],
[
"null",
"<null>",
],
[
"null.foo()",
"Bool",
],
]
`;

exports[`resolveStatements should resolve statements for expr-method-call-null2 1`] = `
[
[
"self",
"Int?",
],
[
"null",
"<null>",
],
[
"self == null",
"Bool",
],
[
"false",
"Bool",
],
[
"self",
"Int?",
],
[
"self!!",
"Int",
],
[
"42",
"Int",
],
[
"self!! == 42",
"Bool",
],
[
"self",
"Int?",
],
[
"null",
"<null>",
],
[
"self == null",
"Bool",
],
[
"false",
"Bool",
],
[
"self",
"Int?",
],
[
"self!!",
"Int",
],
[
"69",
"Int",
],
[
"self!! == 69",
"Bool",
],
[
"null",
"<null>",
],
[
"null.foo()",
"Bool",
],
[
"null",
"<null>",
],
[
"null.bar()",
"Bool",
],
]
`;

exports[`resolveStatements should resolve statements for expr-method-call-null3 1`] = `
[
[
"self",
"Int?",
],
[
"null",
"<null>",
],
[
"self == null",
"Bool",
],
[
"false",
"Bool",
],
[
"self",
"Int?",
],
[
"self!!",
"Int",
],
[
"42",
"Int",
],
[
"self!! == 42",
"Bool",
],
[
"self",
"String?",
],
[
"null",
"<null>",
],
[
"self == null",
"Bool",
],
[
"false",
"Bool",
],
[
"self",
"String?",
],
[
"self!!",
"String",
],
[
""hello"",
"String",
],
[
"self!! == "hello"",
"Bool",
],
[
"null",
"<null>",
],
[
"x",
"Int?",
],
[
"x.foo()",
"Bool",
],
]
`;

exports[`resolveStatements should resolve statements for expr-struct-construction 1`] = `
[
[
Expand Down
37 changes: 37 additions & 0 deletions src/types/resolveExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,43 @@ function resolveCall(
throwCompilationError(`Cannot call function on bounced value`, exp.loc);
}

if (src.kind === "null") {
// e.g. null.foo()
// we need to try to find a method foo that accepts nullable type as self

const types = getAllTypes(ctx);
const candidates = [];
for (const t of types) {
const f = t.functions.get(idText(exp.method));
if (f) {
if (f.self?.kind === "ref" && f.self.optional) {
candidates.push({ type: t, f });
}
}
}

const candidate = candidates[0];

// No candidates found
if (typeof candidate === "undefined") {
throwCompilationError(
`Invalid type "${printTypeRef(src)}" for function call`,
exp.loc,
);
}

// Too many candidates found
if (candidates.length > 1) {
throwCompilationError(
`Ambiguous method call ${idTextErr(exp.method)}`,
exp.loc,
);
}

// Return the only candidate
return registerExpType(ctx, exp, candidate.f.returns);
}

throwCompilationError(
`Invalid type "${printTypeRef(src)}" for function call`,
exp.loc,
Expand Down
21 changes: 21 additions & 0 deletions src/types/stmts-failed/expr-method-call-null-ambiguous.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
primitive Bool;
primitive Int;
primitive String;

trait BaseTrait { }

extends fun foo(self: Int?): Bool {
if (self == null) { return false }
else { return self!! == 42 }
}

extends fun foo(self: String?): Bool {
if (self == null) { return false }
else { return self!! == "hello" }
}

contract Test {
get fun test(): Bool {
return null.foo();
}
}
16 changes: 16 additions & 0 deletions src/types/stmts-failed/expr-method-call-null-not-existing.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
primitive Bool;
primitive Int;
primitive String;

trait BaseTrait { }

extends fun foo(self: Int?): Bool {
if (self == null) { return false }
else { return self!! == 42 }
}

contract Test {
get fun test(): Bool {
return null.bar();
}
}
Loading

0 comments on commit a6bd7a5

Please sign in to comment.