Skip to content

Commit

Permalink
fix: better type error messages for void type (#442)
Browse files Browse the repository at this point in the history
  • Loading branch information
anton-trunov authored Jun 20, 2024
1 parent 9ed9193 commit 6f75cda
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Int as coins` as a value type of a map in persistent storage does not throw compilation error anymore: PR [#413](https://github.com/tact-lang/tact/pull/413)
- The semantics of the Tact arithmetic operations in the constant evaluator to perform rounding towards negative infinity: PR [#432](https://github.com/tact-lang/tact/pull/432)
- Inferring `void` type in let statements is now forbidden: PR [#438](https://github.com/tact-lang/tact/pull/438)
- Better error messages for the `void` type: PR [#442](https://github.com/tact-lang/tact/pull/442)

## [1.3.1] - 2024-06-08

Expand Down
80 changes: 80 additions & 0 deletions src/types/__snapshots__/resolveStatements.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ Line 5, col 12:
"
`;
exports[`resolveStatements should fail statements for expr-cmp-void1 1`] = `
"<unknown>:7:12: Expressions of "<void>" type cannot be used for (non)equality operator "=="
Line 7, col 12:
6 | fun baz(): Bool {
> 7 | return foo() == bar()
^~~~~~~~~~~~~~
8 | }
"
`;
exports[`resolveStatements should fail statements for expr-cmp-void2 1`] = `
"<unknown>:7:12: Expressions of "<void>" type cannot be used for (non)equality operator "!="
Line 7, col 12:
6 | fun baz(): Bool {
> 7 | return foo() != bar()
^~~~~~~~~~~~~~
8 | }
"
`;
exports[`resolveStatements should fail statements for expr-conditional-branch-mismatch 1`] = `
"<unknown>:5:43: Non-matching types "Bool" and "Int" for ternary branches
Line 5, col 43:
Expand Down Expand Up @@ -110,6 +130,16 @@ Line 5, col 5:
"
`;
exports[`resolveStatements should fail statements for expr-conditional-void-branches copy 1`] = `
"<unknown>:6:19: Expressions of "<void>" type cannot be used for conditional expression
Line 6, col 19:
5 | fun bar(): Int {
> 6 | let voidVar = true ? foo() : foo();
^~~~~~~~~~~~~~~~~~~~
7 | return 42;
"
`;
exports[`resolveStatements should fail statements for expr-internal-fun-call-bool-param.tact 1`] = `
"<unknown>:20:27: Invalid type "Bool" for argument "b"
Line 20, col 27:
Expand Down Expand Up @@ -430,6 +460,16 @@ Line 5, col 5:
"
`;
exports[`resolveStatements should fail statements for stmt-conditional-expr-stmt-void1 1`] = `
"<unknown>:5:5: Expressions of "<void>" type cannot be used for conditional expression
Line 5, col 5:
4 | fun baz() {
> 5 | true ? foo() : bar();
^~~~~~~~~~~~~~~~~~~~
6 | }
"
`;
exports[`resolveStatements should fail statements for stmt-do-int 1`] = `
"<unknown>:9:5: Type mismatch: "Int" is not assignable to "Bool"
Line 9, col 5:
Expand Down Expand Up @@ -510,6 +550,46 @@ Line 8, col 9:
"
`;
exports[`resolveStatements should fail statements for stmt-return-void1 1`] = `
"<unknown>:4:5: 'return' statement can only be used with non-void types
Line 4, col 5:
3 | fun bar() {
> 4 | return foo()
^~~~~~~~~~~~
5 | }
"
`;
exports[`resolveStatements should fail statements for stmt-return-void2 1`] = `
"<unknown>:6:5: Type mismatch: "Int" is not assignable to "<void>"
Line 6, col 5:
5 | fun bar() {
> 6 | return foo()
^~~~~~~~~~~~
7 | }
"
`;
exports[`resolveStatements should fail statements for stmt-return-void3 1`] = `
"<unknown>:3:18: The function fails to return a result of type "Int"
Line 3, col 18:
2 |
> 3 | fun bar(): Int { return }
^~~~~~
4 |
"
`;
exports[`resolveStatements should fail statements for stmt-unboxing-expr-stmt-void 1`] = `
"<unknown>:4:5: Type "<void>" is not optional
Line 4, col 5:
3 | fun bar() {
> 4 | foo()!!
^~~~~~~
5 | }
"
`;
exports[`resolveStatements should fail statements for stmt-while-int 1`] = `
"<unknown>:9:5: Type mismatch: "Int" is not assignable to "Bool"
Line 9, col 5:
Expand Down
12 changes: 12 additions & 0 deletions src/types/resolveExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,12 @@ function resolveBinaryOp(
exp.ref,
);
}
if (l.kind == "void" || r.kind == "void") {
throwSyntaxError(
`Expressions of "<void>" type cannot be used for (non)equality operator "${exp.op}"`,
exp.ref,
);
}
if (l.kind !== "ref" || r.kind !== "ref") {
throwSyntaxError(
`Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`,
Expand Down Expand Up @@ -670,6 +676,12 @@ export function resolveConditional(

const resultType = moreGeneralType(thenType, elseType);
if (resultType) {
if (resultType.kind == "void") {
throwSyntaxError(
`Expressions of "<void>" type cannot be used for conditional expression`,
ast.ref,
);
}
return registerExpType(ctx, ast, resultType);
}

Expand Down
13 changes: 12 additions & 1 deletion src/types/resolveStatements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,17 @@ function processStatements(

// Check type
const expressionType = getExpType(ctx, s.expression);

// Actually, we might relax the following restriction in the future
// Because `return foo()` means `foo(); return` for a void-returning function
// And `return foo()` looks nicer when the user needs early exit from a function
// right after executing `foo()`
if (expressionType.kind == "void") {
throwSyntaxError(
`'return' statement can only be used with non-void types`,
s.ref,
);
}
if (!isAssignable(expressionType, sctx.returns)) {
throwSyntaxError(
`Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(sctx.returns)}"`,
Expand All @@ -309,7 +320,7 @@ function processStatements(
} else {
if (sctx.returns.kind !== "void") {
throwSyntaxError(
`Type mismatch: "void" is not assignable to "${printTypeRef(sctx.returns)}"`,
`The function fails to return a result of type "${printTypeRef(sctx.returns)}"`,
s.ref,
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/types/stmts-failed/expr-cmp-void1.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
primitive Bool;

fun foo() { return }
fun bar() { return }

fun baz(): Bool {
return foo() == bar()
}
8 changes: 8 additions & 0 deletions src/types/stmts-failed/expr-cmp-void2.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
primitive Bool;

fun foo() { return }
fun bar() { return }

fun baz(): Bool {
return foo() != bar()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
primitive Int;

fun foo() { return }

fun bar(): Int {
let voidVar = true ? foo() : foo();
return 42;
}
6 changes: 6 additions & 0 deletions src/types/stmts-failed/stmt-conditional-expr-stmt-void1.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fun foo() { return }
fun bar() { return }

fun baz() {
true ? foo() : bar();
}
5 changes: 5 additions & 0 deletions src/types/stmts-failed/stmt-return-void1.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fun foo() { return }

fun bar() {
return foo()
}
7 changes: 7 additions & 0 deletions src/types/stmts-failed/stmt-return-void2.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
primitive Int;

fun foo(): Int { return 42 }

fun bar() {
return foo()
}
3 changes: 3 additions & 0 deletions src/types/stmts-failed/stmt-return-void3.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
primitive Int;

fun bar(): Int { return }
5 changes: 5 additions & 0 deletions src/types/stmts-failed/stmt-unboxing-expr-stmt-void.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fun foo() { return }

fun bar() {
foo()!!
}
5 changes: 2 additions & 3 deletions src/types/subtyping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ export function isAssignable(src: TypeRef, to: TypeRef): boolean {
return true;
}

// If either is void
if (src.kind === "void" || to.kind === "void") {
return false; // Void is not assignable
if (src.kind === "void" && to.kind === "void") {
return true;
}

// Check null
Expand Down

0 comments on commit 6f75cda

Please sign in to comment.