Skip to content

Commit

Permalink
feat: allow calling methods on null when self is of an optional t…
Browse files Browse the repository at this point in the history
…ype (#1567)
  • Loading branch information
Gusarich authored Jan 28, 2025
1 parent 442a0e5 commit 6fac963
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 0 deletions.
1 change: 1 addition & 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
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();
}
}
15 changes: 15 additions & 0 deletions src/types/stmts/expr-method-call-null.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
primitive Bool;
primitive Int;

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.foo();
}
}
24 changes: 24 additions & 0 deletions src/types/stmts/expr-method-call-null2.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
primitive Bool;
primitive Int;

trait BaseTrait { }

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

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

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

get fun test2(): Bool {
return null.bar();
}
}
Loading

0 comments on commit 6fac963

Please sign in to comment.