Skip to content

Commit

Permalink
fix: typechecking of conditional expressions (tact-lang#394)
Browse files Browse the repository at this point in the history
in the presense of subtyping
  • Loading branch information
anton-trunov authored Jun 10, 2024
1 parent 0f65cfa commit a59bf32
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Return type of `skipBits` now matches FunC and does not lead to compilation errors: PR [#388](https://github.com/tact-lang/tact/pull/388)
- Typechecking of conditional expressions when one branch's type is a subtype of another, i.e. for optionals and maps/`null`: PR [#394](https://github.com/tact-lang/tact/pull/394)

## [1.3.1] - 2024-06-08

Expand Down
129 changes: 129 additions & 0 deletions src/types/__snapshots__/resolveStatements.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,16 @@ Line 5, col 12:
"
`;
exports[`resolveStatements should fail statements for case-54 1`] = `
"<unknown>:6:5: Type mismatch: "Int?" is not assignable to "Int"
Line 6, col 5:
5 | let cond: Bool = true;
> 6 | return cond ? 42 : x;
^~~~~~~~~~~~~~~~~~~~~
7 | }
"
`;
exports[`resolveStatements should resolve statements for case-0 1`] = `
[
[
Expand Down Expand Up @@ -1417,3 +1427,122 @@ exports[`resolveStatements should resolve statements for case-21 1`] = `
],
]
`;
exports[`resolveStatements should resolve statements for case-22 1`] = `
[
[
"true",
"Bool",
],
[
"cond",
"Bool",
],
[
"42",
"Int",
],
[
"x",
"Int?",
],
[
"cond ? 42 : x",
"Int?",
],
[
"true",
"Bool",
],
[
"cond",
"Bool",
],
[
"x",
"Int?",
],
[
"42",
"Int",
],
[
"cond ? x : 42",
"Int?",
],
[
"true",
"Bool",
],
[
"cond",
"Bool",
],
[
"null",
"<null>",
],
[
"m",
"map<Int, Int>",
],
[
"cond ? null : m",
"map<Int, Int>",
],
[
"true",
"Bool",
],
[
"cond",
"Bool",
],
[
"m",
"map<Int, Int>",
],
[
"null",
"<null>",
],
[
"cond ? m : null",
"map<Int, Int>",
],
[
"true",
"Bool",
],
[
"cond",
"Bool",
],
[
"42",
"Int",
],
[
"Baz { b: 42 }",
"Baz",
],
[
"Baz { b: 42 }.toCell()",
"Cell",
],
[
"x",
"Cell?",
],
[
"cond ? Baz { b: 42 }.toCell() : x",
"Cell?",
],
[
"Bar {
a: cond ? Baz { b: 42 }.toCell() : x
}",
"Bar",
],
]
`;
2 changes: 1 addition & 1 deletion src/types/isAssignable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TypeRef } from "./types";
export function isAssignable(src: TypeRef, to: TypeRef): boolean {
// If both are refs
if (src.kind === "ref" && to.kind === "ref") {
// Can assign optional to non-optional
// Cannot assign optional to non-optional
if (!to.optional && src.optional) {
return false;
}
Expand Down
20 changes: 13 additions & 7 deletions src/types/resolveExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,15 +663,21 @@ export function resolveConditional(
ctx = resolveExpression(ast.elseBranch, sctx, ctx);
const thenType = getExpType(ctx, ast.thenBranch);
const elseType = getExpType(ctx, ast.elseBranch);
if (!typeRefEquals(thenType, elseType)) {
throwError(
`Non-matching types "${printTypeRef(thenType)}" and "${printTypeRef(elseType)}" for ternary branches`,
ast.elseBranch.ref,
);

// This takes care of sub-typing for optionals and maps/null
if (isAssignable(thenType, elseType)) {
// the type of the else branch is the more general one
return registerExpType(ctx, ast, elseType);
}
if (isAssignable(elseType, thenType)) {
// the type of the then branch is the more general one
return registerExpType(ctx, ast, thenType);
}

// Register result
return registerExpType(ctx, ast, thenType);
throwError(
`Non-matching types "${printTypeRef(thenType)}" and "${printTypeRef(elseType)}" for ternary branches`,
ast.elseBranch.ref,
);
}

export function resolveLValueRef(
Expand Down
12 changes: 12 additions & 0 deletions src/types/stmts-failed/case-54.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
primitive Int;
primitive Bool;

fun foo1(x: Int?): Int {
let cond: Bool = true;
return cond ? 42 : x;
}

fun foo2(x: Int?): Int {
let cond: Bool = true;
return cond ? x : 42;
}
36 changes: 36 additions & 0 deletions src/types/stmts/case-22.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
primitive Int;
primitive Bool;
primitive Cell;

fun foo1(x: Int?): Int? {
let cond: Bool = true;
return cond ? 42 : x;
}

fun foo2(x: Int?): Int? {
let cond: Bool = true;
return cond ? x : 42;
}

fun bar1(m: map<Int, Int>): map<Int, Int> {
let cond: Bool = true;
return cond ? null : m;
}

fun bar2(m: map<Int, Int>): map<Int, Int> {
let cond: Bool = true;
return cond ? m : null;
}

struct Bar {
a: Cell?;
}

struct Baz { b: Int; }

fun baz(x: Cell?): Bar {
let cond: Bool = true;
return Bar {
a: cond ? Baz { b: 42 }.toCell() : x
};
}

0 comments on commit a59bf32

Please sign in to comment.