Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tolk v0.7: overhaul compiler internals and the type system; bool type #1477

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[Tolk] Rewrite the type system from Hindley-Milner to static typing
FunC's (and Tolk's before this PR) type system is based on Hindley-Milner.
This is a common approach for functional languages, where
types are inferred from usage through unification.
As a result, type declarations are not necessary:
() f(a,b) { return a+b; } // a and b now int, since `+` (int, int)

While this approach works for now, problems arise with the introduction
of new types like bool, where `!x` must handle both int and bool.
It will also become incompatible with int32 and other strict integers.
This will clash with structure methods, struggle with proper generics,
and become entirely impractical for union types.

This PR completely rewrites the type system targeting the future.
1) type of any expression is inferred and never changed
2) this is available because dependent expressions already inferred
3) forall completely removed, generic functions introduced
   (they work like template functions actually, instantiated while inferring)
4) instantiation `<...>` syntax, example: `t.tupleAt<int>(0)`
5) `as` keyword, for example `t.tupleAt(0) as int`
6) methods binding is done along with type inferring, not before
   ("before", as worked previously, was always a wrong approach)
tolk-vm committed Jan 15, 2025
commit 799e2d12655536295b21c04a77ff5465a81aaca3
12 changes: 6 additions & 6 deletions crypto/smartcont/tolk-stdlib/common.tolk
Original file line number Diff line number Diff line change
@@ -17,17 +17,17 @@ fun createEmptyTuple(): tuple
/// Appends a value to tuple, resulting in `Tuple t' = (x1, ..., xn, value)`.
/// If its size exceeds 255, throws a type check exception.
@pure
fun tuplePush<X>(mutate self: tuple, value: X): void
fun tuplePush<T>(mutate self: tuple, value: T): void
asm "TPUSH";

/// Returns the first element of a non-empty tuple.
@pure
fun tupleFirst<X>(t: tuple): X
fun tupleFirst<T>(t: tuple): T
asm "FIRST";

/// Returns the [`index`]-th element of a tuple.
@pure
fun tupleAt<X>(t: tuple, index: int): X
fun tupleAt<T>(t: tuple, index: int): T
builtin;

/// Returns the size of a tuple (elements count in it).
@@ -37,7 +37,7 @@ fun tupleSize(t: tuple): int

/// Returns the last element of a non-empty tuple.
@pure
fun tupleLast(t: tuple): int
fun tupleLast<T>(t: tuple): T
asm "LAST";


@@ -306,11 +306,11 @@ fun getBuilderDepth(b: builder): int
*/

/// Dump a variable [x] to the debug log.
fun debugPrint<X>(x: X): void
fun debugPrint<T>(x: T): void
builtin;

/// Dump a string [x] to the debug log.
fun debugPrintString<X>(x: X): void
fun debugPrintString<T>(x: T): void
builtin;

/// Dumps the stack (at most the top 255 values) and shows the total stack depth.
6 changes: 6 additions & 0 deletions crypto/smartcont/tolk-stdlib/gas-payments.tolk
Original file line number Diff line number Diff line change
@@ -61,3 +61,9 @@ fun calculateOriginalMessageFee(workchain: int, incomingFwdFee: int): int
/// If it has no debt, `0` is returned.
fun getMyStorageDuePayment(): int
asm "DUEPAYMENT";

/// Returns the amount of nanotoncoins charged for storage.
/// (during storage phase preceeding to current computation phase)
@pure
fun getMyStoragePaidPayment(): int
asm "STORAGEFEES";
2 changes: 1 addition & 1 deletion tolk-tester/tests/a10.tolk
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ fun test88(x: int) {
}

@method_id(89)
fun test89(last: int) {
fun test89(last: int): (int, int, int, int) {
var t: tuple = createEmptyTuple();
t.tuplePush(1);
t.tuplePush(2);
3 changes: 2 additions & 1 deletion tolk-tester/tests/a6.tolk
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ fun calc_phi(): int {
repeat (70) { n*=10; };
var p= 1;
var `q`=1;
_=`q`;
do {
(p,q)=(q,p+q);
} while (q <= n); //;;
@@ -27,7 +28,7 @@ fun calc_sqrt2(): int {
return mulDivRound(p, n, q);
}

fun calc_root(m: auto): auto {
fun calc_root(m: int) {
var base: int=1;
repeat(70) { base *= 10; }
var (a, b, c) = (1,0,-m);
2 changes: 1 addition & 1 deletion tolk-tester/tests/a6_5.tolk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@deprecated
fun twice(f: auto, x: auto): auto {
fun twice(f: int -> int, x: int) {
return f (f (x));
}

2 changes: 1 addition & 1 deletion tolk-tester/tests/allow_post_modification.tolk
Original file line number Diff line number Diff line change
@@ -138,5 +138,5 @@ fun main() {
inc CALLDICT // self newY
}>
"""
@code_hash 33262590582878205026101577472505372101182291690814957175155528952950621243206
@code_hash 7627024945492125068389905298530400936797031708759561372406088054030801992712
*/
28 changes: 28 additions & 0 deletions tolk-tester/tests/assignment-tests.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
fun extractFromTypedTuple(params: [int]) {
var [payload: int] = params;
return payload + 10;
}

@method_id(101)
fun test101(x: int) {
var params = [x];
return extractFromTypedTuple(params);
}

fun autoInferIntNull(x: int) {
if (x > 10) { return null; }
return x;
}

fun main(value: int) {
var (x: int, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
if (x == null && y == null) { return null; }
return x == null || y == null ? -1 : x + y;
}

/**
@testcase | 0 | 3 | 9
@testcase | 0 | 6 | -1
@testcase | 0 | 11 | (null)
@testcase | 101 | 78 | 88
*/
4 changes: 2 additions & 2 deletions tolk-tester/tests/c2.tolk
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ fun check_assoc(a: int, b: int, c: int): int {
return op(op(a, b), c) == op(a, op(b, c));
}

fun unnamed_args(_: int, _: slice, _: auto): auto {
fun unnamed_args(_: int, _: slice, _: int) {
return true;
}

@@ -14,7 +14,7 @@ fun main(x: int, y: int, z: int): int {
}

@method_id(101)
fun test101(x: int, z: int): auto {
fun test101(x: int, z: int) {
return unnamed_args(x, "asdf", z);
}

2 changes: 1 addition & 1 deletion tolk-tester/tests/c2_1.tolk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fun check_assoc(op: auto, a: int, b: int, c: int) {
fun check_assoc(op: (int, int) -> int, a: int, b: int, c: int) {
return op(op(a, b), c) == op(a, op(b, c));
}

150 changes: 150 additions & 0 deletions tolk-tester/tests/generics-1.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
fun eq1<X>(value: X): X { return value; }
fun eq2<X>(value: X) { return value; }
fun eq3<X>(value: X): X { var cp: [X] = [eq1(value)]; var ((([v: X]))) = cp; return v; }
fun eq4<X>(value: X) { return eq1<X>(value); }

@method_id(101)
fun test101(x: int) {
var (a, b, c) = (x, (x,x), [x,x]);
return (eq1(a), eq1(b), eq1(c), eq2(a), eq2(b), eq2(c), eq3(a), eq4(b), eq3(createEmptyTuple()));
}

fun getTwo<X>(): X { return 2 as X; }

fun takeInt(a: int) { return a; }

@method_id(102)
fun test102(): (int, int, int, [(int, int)]) {
var a: int = getTwo();
var _: int = getTwo();
var b = getTwo() as int;
var c: int = 1 ? getTwo() : getTwo();
var c redef = getTwo();
return (eq1<int>(a), eq2<int>(b), takeInt(getTwo()), [(getTwo(), getTwo())]);
}

@method_id(103)
fun test103(first: int): (int, int, int) {
var t = createEmptyTuple();
var cs = beginCell().storeInt(100, 32).endCell().beginParse();
t.tuplePush(first);
t.tuplePush(2);
t.tuplePush(cs);
cs = t.tupleAt(2);
cs = t.tupleAt(2) as slice;
return (t.tupleAt(0), cs.loadInt(32), t.tupleAt<slice>(2).loadInt(32));
}

fun manyEq<T1, T2, T3>(a: T1, b: T2, c: T3): [T1, T2, T3] {
return [a, b, c];
}

@method_id(104)
fun test104(f: int) {
return (
manyEq(1 ? 1 : 1, f ? 0 : null, !f ? getTwo() as int : null),
manyEq((f ? null as int : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool()), 0, eq4(f))
);
}

fun calcSum<X>(x: X, y: X) { return x + y; }

@method_id(105)
fun test105() {
if (0) { calcSum(((0)), null); }
return (calcSum(1, 2));
}

fun calcYPlus1<Y>(value: Y) { return value + 1; }
fun calcLoad32(cs: slice) { return cs.loadInt(32); }
fun calcTensorPlus1(tens: (int, int)) { var (f, s) = tens; return (f + 1, s + 1); }
fun calcTensorMul2(tens: (int, int)) { var (f, s) = tens; return (f * 2, s * 2); }
fun cellToSlice(c: cell) { return c.beginParse(); }
fun abstractTransform<X, Y, R>(xToY: (X) -> Y, yToR: (((Y))) -> R, initialX: X): R {
var y = xToY(initialX);
return yToR(y);
}

@method_id(106)
fun test106() {
var c = beginCell().storeInt(106, 32).endCell();
return [
abstractTransform(cellToSlice, calcLoad32, c),
abstractTransform(calcYPlus1<int>, calcYPlus1<int>, 0),
abstractTransform(calcTensorPlus1, calcTensorMul2, (2, 2))
];
}

fun callTupleFirst<X, Y>(t: X): Y { return t.tupleFirst(); }
fun callTuplePush<T, V>(mutate self: T, v1: V, v2: V): self { self.tuplePush(v1); tuplePush(mutate self, v2); return self; }
fun getTupleLastInt(t: tuple) { return t.tupleLast<int>(); }
fun getTupleSize(t: tuple) { return t.tupleSize(); }
fun callAnyFn<TObj, TResult>(f: (TObj) -> TResult, arg: TObj) { return f(arg); }
fun callAnyFn2<TCallback>(f: TCallback, arg: tuple) { return f(arg); }

global t107: tuple;

@method_id(107)
fun test107() {
t107 = createEmptyTuple();
callTuplePush(mutate t107, 1, 2);
t107.callTuplePush(3, 4).callTuplePush(5, 6);
var first: int = t107.callTupleFirst();
return (
callAnyFn<tuple, int>(getTupleSize, t107),
callAnyFn2(getTupleSize, t107),
first,
callTupleFirst(t107) as int,
callAnyFn(getTupleLastInt, t107),
callAnyFn2(getTupleLastInt, t107)
);
}

global g108: int;

fun inc108(by: int) { g108 += by; }
fun getInc108() { return inc108; }
fun returnResult<RetT>(f: () -> RetT): RetT { return f(); }
fun applyAndReturn<ArgT, RetT>(f: () -> (ArgT) -> RetT, arg: ArgT): () -> ArgT -> RetT {
f()(arg);
return f;
}

@method_id(108)
fun test108() {
g108 = 0;
getInc108()(1);
returnResult<(int) -> void>(getInc108)(2);
applyAndReturn<int, void>(getInc108, 10)()(10);
returnResult(getInc108)(2);
applyAndReturn(getInc108, 10)()(10);
return g108;
}

fun main(x: int): (int, [[int, int]]) {
try { if(x) { throw (1, x); } }
catch (excNo, arg) { return (arg as int, [[eq2(arg as int), getTwo()]]); }
return (0, [[x, 1]]);
}

/**
@testcase | 0 | 1 | 1 [ [ 1 2 ] ]
@testcase | 101 | 0 | 0 0 0 [ 0 0 ] 0 0 0 [ 0 0 ] 0 0 0 []
@testcase | 102 | | 2 2 2 [ 2 2 ]
@testcase | 103 | 0 | 0 100 100
@testcase | 104 | 0 | [ 1 (null) 2 ] [ 2 -1 0 0 ]
@testcase | 105 | | 3
@testcase | 106 | | [ 106 2 6 6 ]
@testcase | 107 | | 6 6 1 1 6 6
@testcase | 108 | | 45

@fif_codegen DECLPROC eq1<int>
@fif_codegen DECLPROC eq1<tuple>
@fif_codegen DECLPROC eq1<(int,int)>
@fif_codegen DECLPROC eq1<[int,int]>
@fif_codegen DECLPROC getTwo<int>

@fif_codegen_avoid DECLPROC eq1
@fif_codegen_avoid DECLPROC eq2
@fif_codegen_avoid DECLPROC eq3
*/
9 changes: 5 additions & 4 deletions tolk-tester/tests/invalid-call-1.tolk
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
fun main() {
return true();
const asdf = 1;

fun main(x: int) {
return x.asdf();
}

/**
@compilation_should_fail
The message is weird now, but later I'll rework error messages anyway.
@stderr cannot apply expression of type int to an expression of type (): cannot unify type () -> ??2 with int
@stderr calling a non-function
*/
10 changes: 10 additions & 0 deletions tolk-tester/tests/invalid-call-9.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fun getOne() { return 1; }

fun main() {
return getOne<int>();
}

/**
@compilation_should_fail
@stderr calling a not generic function with generic T
*/
13 changes: 13 additions & 0 deletions tolk-tester/tests/invalid-declaration-11.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// this function is declared incorrectly,
// since it should return 2 values onto a stack (1 for returned slice, 1 for mutated int)
// but contains not 2 numbers in asm ret_order
fun loadAddress2(mutate self: int): slice
asm( -> 1 0 2) "LDMSGADDR";

fun main(){}

/**
@compilation_should_fail
@stderr ret_order (after ->) expected to contain 2 numbers
@stderr asm( -> 1 0 2)
*/
16 changes: 16 additions & 0 deletions tolk-tester/tests/invalid-declaration-12.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fun proxy(x: int) {
return factorial(x);
}

fun factorial(x: int) {
if (x <= 0) {
return 1;
}
return x * proxy(x-1);
}

/**
@compilation_should_fail
@stderr could not infer return type of `factorial`, because it appears in a recursive call chain
@stderr fun factorial
*/
7 changes: 7 additions & 0 deletions tolk-tester/tests/invalid-declaration-13.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const c: slice = 123 + 456;

/**
@compilation_should_fail
@stderr expression type does not match declared type
@stderr const c
*/
10 changes: 10 additions & 0 deletions tolk-tester/tests/invalid-generics-1.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fun f<X>(v: int, x: X) {}

fun failCantDeduceWithoutArgument() {
return f(1);
}

/**
@compilation_should_fail
@stderr can not deduce X for generic function `f<X>`
*/
9 changes: 9 additions & 0 deletions tolk-tester/tests/invalid-generics-10.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun invalidReferencingGenericMethodWithoutGeneric() {
var t = createEmptyTuple();
var cb = t.tupleLast;
}

/**
@compilation_should_fail
@stderr can not use a generic function `tupleLast<T>` as non-call
*/
11 changes: 11 additions & 0 deletions tolk-tester/tests/invalid-generics-11.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
global gVar: int;

fun main() {
var x = gVar<int>;
return x;
}

/**
@compilation_should_fail
@stderr generic T not expected here
*/
Loading