1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[Tolk] Smart casts and control flow graph

With the introduction of nullable types, we want the
compiler to be smart in cases like
> if (x == null) return;
> // x is int now
or
> if (x == null) x = 0;
> // x is int now

These are called smart casts: when the type of variable
at particular usage might differ from its declaration.

Implementing smart casts is very challenging. They are based
on building control-flow graph and handling every AST vertex
with care. Actually, I represent cfg not a as a "graph with
edges". Instead, it's a "structured DFS" for the AST:
1) at every point of inferring, we have "current flow facts"
2) when we see an `if (...)`, we create two derived contexts
3) after `if`, finalize them at the end and unify
4) if we detect unreachable code, we mark that context
In other words, we get the effect of a CFG but in a more direct
approach. That's enough for AST-level data-flow.

Smart casts work for local variables and tensor/tuple indices.
Compilation errors have been reworked and now are more friendly.
There are also compilation warnings for always true/false
conditions inside if, assert, etc.
This commit is contained in:
tolk-vm 2025-02-24 20:14:16 +03:00
parent f3e620f48c
commit 7bcb8b895f
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
47 changed files with 3057 additions and 833 deletions

View file

@ -73,7 +73,7 @@ fun test104() {
var t1_1: (int, int)? = (1, 2);
var t1_2: (int, int)? = t1_1;
var t1_3: (int, int)? = t1_1!;
var t2_1: (int, int)? = null;
var t2_1: (int, int)? = getNullableTensor(null);
var t2_2 = t2_1;
return (t1_3, t2_2);
}
@ -101,9 +101,12 @@ fun test108(x1: (int, int)) {
incrementTensorComponents(mutate x1);
x1.incrementTensorComponents();
var x2: (int, int)? = x1;
__expect_type(x2, "(int, int)");
x2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x2);
__expect_type(x2, "(int, int)?");
var x3: (int, int)? = null;
__expect_type(x3, "null");
x3.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x3);
return (x1, x2, x3);
@ -148,7 +151,7 @@ fun test111() {
var x = (1, 2);
assignFirstComponent(mutate x, 50);
var x2: (int, int)? = null;
var x3 = x2;
var x3 = x2 as (int, int)?;
assignFirstComponentNullable(mutate x2, 30);
assignFirstComponentNullable(mutate x3, 70);
g110_1 = (1, 2);
@ -361,23 +364,36 @@ fun test132() {
return (result, 777, aln1, aln2, doubleNulls.1 == null, doubleNulls);
}
@method_id(133)
fun test133() {
var x: (int, int)? = (10, 20);
return sumOfTensor(x) + x.0 + x.1; // smart casted
}
@method_id(134)
fun test134(): (int, int)? {
var x: (int, int)? = (10, 20);
incrementTensorComponents(mutate x); // smart casted
return x;
}
fun getNormalNullableTensorWidth1(vLess100: int?): ([int?], ())? {
if (vLess100 != null && vLess100! >= 100) {
if (vLess100 != null && vLess100 >= 100) {
return null;
}
return ([vLess100], ()); // such a nullable tensor can store NULL in the same slot
}
fun getTrickyNullableTensorWidth1(vLess100: int?): (int?, ())? {
if (vLess100 != null && vLess100! >= 100) {
if (vLess100 != null && vLess100 >= 100) {
return null;
}
return (vLess100, ()); // such a nullable tensor requires an extra stack slot for null presence
}
fun getEvenTrickierNullableWidth1(vLess100: int?): ((), (int?, ()), ())? {
if (vLess100 != null && vLess100! >= 100) {
if (vLess100 != null && vLess100 >= 100) {
return null;
}
return ((), (vLess100, ()), ());
@ -406,35 +422,35 @@ fun main(){}
/**
@testcase | 101 | | 1 2 -1
@testcase | 102 | | 1 2 -1 (null) (null) 0
@testcase | 103 | 1 2 | 3 3 0 1 2 -1
@testcase | 104 | | 1 2 -1 (null) (null) 0
@testcase | 103 | 1 2 | 3 3 0 1 2
@testcase | 104 | | 1 2 (null) (null) 0
@testcase | 105 | | (null) (null) (null) 0 1 2 3 -1
@testcase | 106 | | 1 2 -1
@testcase | 106 | | 1 2
@testcase | 107 | | 0 0 -1 0 0 -1
@testcase | 108 | 5 6 | 7 8 10 11 -1 (null) (null) 0
@testcase | 109 | | 0 0 -1 0 -1 0 0 -1 -1
@testcase | 110 | | 3 4 (null) (null) 0 6 7 -1
@testcase | 111 | | 50 30 70 90 100
@testcase | 112 | | 12 22 -1
@testcase | 112 | | 12 22
@testcase | 113 | | -1
@testcase | 114 | | (null) (null) (null) 0 (null) (null) (null) 0
@testcase | 115 | | 2 3 7 (null) (null) 0 5 0 -1 0
@testcase | 116 | -1 | (null) (null) 0 (null) (null) 0
@testcase | 116 | 0 | 1 2 -1 1 2 -1
@testcase | 117 | | (null) (null) 0 1 3
@testcase | 117 | | (null) 1 3
@testcase | 118 | 5 | 5 10 -1
@testcase | 118 | null | (null) (null) 0
@testcase | 119 | | (null) (null) 0 (null) (null) 0 1 2 -1 100
@testcase | 119 | | (null) (null) 1 2 -1 100
@testcase | 120 | -1 | (null) (null) 0
@testcase | 120 | 0 | 1 2 -1
@testcase | 121 | | [ 1 [ 3 4 ] ]
@testcase | 122 | 0 | [ 1 [ 3 4 ] 4 (null) ]
@testcase | 122 | -1 | [ 1 (null) 4 (null) ]
@testcase | 123 | | 1 3 4 -1
@testcase | 124 | 0 | 1 3 4 -1 4 (null) (null) 0 -1
@testcase | 124 | -1 | 1 (null) (null) 0 4 (null) (null) 0 -1
@testcase | 124 | 0 | 1 3 4 -1 4 (null) (null) 0
@testcase | 124 | -1 | 1 (null) (null) 0 4 (null) (null) 0
@testcase | 125 | | 3
@testcase | 126 | | 1 (null) (null) 0 2
@testcase | 126 | | 1 (null) 2
@testcase | 127 | 1 | 1 (null) (null) 0 2
@testcase | 127 | 2 | 1 2 3 -1 4
@testcase | 127 | 3 | 1 (null) (null) 0 5
@ -447,6 +463,8 @@ fun main(){}
@testcase | 130 | -1 | 1 (null) (null) 0
@testcase | 131 | | -1 777 0 777 777 777 0 0 -1 -1 777 -1 -1 -1 777
@testcase | 132 | | -1 0 -1 0 777 (null) (null) -1 0 0
@testcase | 133 | | 60
@testcase | 134 | | 11 21 -1
@testcase | 135 | | [ 10 ] [ (null) ] (null) 777 10 -1 (null) -1 (null) 0 777 10 -1 (null) -1 (null) 0 777 0 0 -1 0 0 -1 0 0 -1 777 0 -1 0 0 -1 0
@fif_codegen