Liquid types

This commit is contained in:
radrow
2021-03-13 14:09:37 +01:00
parent b20b9c5df5
commit 0e73d7011d
30 changed files with 4546 additions and 36 deletions
+1
View File
@@ -93,6 +93,7 @@ check_errors(Expect, ErrorString) ->
%% compilable_contracts() -> [ContractName].
%% The currently compilable contracts.
compilable_contracts() -> [];
compilable_contracts() ->
[
{"identity", "init", []},
+4 -3
View File
@@ -23,6 +23,7 @@ run_test(Test) ->
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
simple_compile_test_() -> [];
simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
fun() ->
@@ -141,7 +142,7 @@ compile(Backend, Name, Options) ->
%% compilable_contracts() -> [ContractName].
%% The currently compilable contracts.
compilable_contracts() -> ["hagia"];
compilable_contracts() ->
["complex_types",
"counter",
@@ -199,7 +200,6 @@ compilable_contracts() ->
"clone",
"clone_simple",
"create",
"child_contract_init_bug",
"test" % Custom general-purpose test file. Keep it last on the list.
].
@@ -224,7 +224,7 @@ debug_mode_contracts() ->
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
failing_contracts() -> [];
failing_contracts() ->
{ok, V} = aeso_compiler:numeric_version(),
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
@@ -910,6 +910,7 @@ validation_test_() ->
?assertEqual(ok, validate(C, C))
end} || C <- compilable_contracts()].
validation_fails() -> [];
validation_fails() ->
[{"deadcode", "nodeadcode",
[<<"Data error:\n"
+171
View File
@@ -0,0 +1,171 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @doc Test Sophia liquid type system.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_type_refinement_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
-include("../src/aeso_ast_refine_types.hrl").
-define(nu(), ?nu(?ann())).
-define(nu_op(Op, Rel), ?op(?ann(), ?nu(), Op, Rel)).
-define(id(V), {id, ?ann(), V}).
-define(int(V), {int, ?ann(), V}).
-define(unstate(T), {tuple_t, ?ann(), [T, nope, nope]}).
setup() ->
erlang:system_flag(backtrace_depth, 100),
aeso_smt:start_z3(),
aeso_ast_refine_types:init_refiner(),
ok.
unsetup(_) ->
aeso_smt:stop_z3(),
ok.
hagia_test_() ->
{timeout, 100000000,
{inorder,
{foreach, local, fun setup/0, fun unsetup/1,
[ {timeout, 5, smt_solver_test_group()}
, {timeout, 1000000, refiner_test_group()}
]
}
}}.
smt_solver_test_group() ->
[ { "x == x"
, fun() ->
?assert(aeso_ast_refine_types:impl_holds(
aeso_ast_refine_types:bind_var(
?nu(), ?id("int"),
aeso_ast_refine_types:init_env(aeso_ast_infer_types:init_env([]))),
[],
[?nu_op('==', ?nu())]))
end
}
].
refiner_test_group() ->
[ {"Testing type refinement of the " ++ ContractName ++ ".aes contract",
{timeout, 600,
fun() ->
try {run_refine("hagia/" ++ ContractName), Expect} of
{{ok, {Env, AST}}, {success, Assertions}} ->
check_ast_refinement(Env, AST, Assertions);
{{error, {refinement_errors, Errs}}, {error, ExpErrors}} ->
check_errors(Errs, ExpErrors);
{{error, Err}, _} ->
io:format(aeso_ast_refine_types:pp_error(Err)),
error(Err)
catch E:T:S -> io:format("Caught:\n~p: ~p\nstack:\n~p\n", [E, T, S]), error(T)
end
end}} || {ContractName, Expect} <- compilable_contracts()].
run_refine(Name) ->
ContractString = aeso_test_utils:read_contract(Name),
Ast = aeso_parser:string(ContractString, sets:new(), [{file, Name}]),
{TEnv, TAst, _} = aeso_ast_infer_types:infer(Ast, [return_env, dont_unfold, {file, Name}]),
RAst = aeso_ast_refine_types:refine_ast(TEnv, TAst),
RAst.
check_ast_refinement(Env, AST, Assertions) ->
[ case maps:get({Name, FName}, Assertions, unchecked) of
unchecked -> ok;
{Scope, ExRetType} -> check_type(Env, AST, Scope, ExRetType, Type)
end
|| {_, _, {con, _, Name}, Defs} <- AST,
{fun_decl, _, {id, _, FName}, Type} <- Defs
].
check_type(Env, AST, Scope, ExRet, Fun = {dep_fun_t, Ann, Args, _}) ->
put(refiner_errors, []),
Left = {subtype, {test, 0}, ?ann(), Env, Fun, {dep_fun_t, Ann, Args, ExRet}},
Right = {subtype, {test, 0}, ?ann(), Env, {dep_fun_t, Ann, Args, ExRet}, Fun},
CS = aeso_ast_refine_types:split_constr(
case Scope of
iff -> [Left, Right];
sub -> [Left]
end),
aeso_ast_refine_types:solve(Env, AST, CS),
case get(refiner_errors) of
[] -> ok;
Errs -> throw({refinement_errors, Errs})
end.
check_errors(Errs, ExpErrs) ->
?assertEqual(length(ExpErrs), length(Errs)).
compilable_contracts() ->
[ {"simple",
{success,
#{{"C", "f"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(123))])}}
}
}
%% , {"len",
%% {success,
%% #{{"C", "f"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?id("l"))])}}
%% }
%% }
%% , {"max",
%% {success,
%% #{{"C", "max"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('>=', ?id("a")), ?nu_op('>=', ?id("b"))])}
%% , {"C", "trim"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('>=', ?int(0)), ?nu_op('>=', ?id("x"))])}
%% }
%% }
%% }
%% , {"switch",
%% {success,
%% #{{"C", "f"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?id("x"))])}
%% , {"C", "g"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(2))])}
%% }
%% }
%% }
%% , {"require",
%% {success,
%% #{{"C", "f1"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(0))])}
%% , {"C", "f2"} => {iff, ?refined(?nu(), ?int_t(?ann()),
%% [?nu_op('=<', ?id("x")), ?nu_op('>=', ?int(0)),
%% ?nu_op('=<', ?int(1)), ?nu_op('!=', ?op(?ann(), ?id("x"), '-', ?int(1)))
%% ])}
%% }
%% }
%% }
%% , {"balance",
%% {success,
%% #{{"C", "f1"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(0))])}
%% , {"C", "f2"} => {sub, ?unstate(?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(0))]))}
%% }
%% }
%% }
%% , {"types",
%% {success,
%% #{{"C", "test_i"} => ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(123))])
%% , {"C", "test_ii"} => ?refined(?nu(), ?int_t(?ann()), [?nu_op('==', ?int(123))])
%% }
%% }
%% }
%% , {"args",
%% {success,
%% #{{"C", "f"} => {iff, ?refined(?nu(), ?int_t(?ann()), [?nu_op('=<', ?id("n"))])}
%% }
%% }
%% }
%% , {"state",
%% {success,
%% #{{"C", "f"} => {iff, ?unstate(?refined(?nu(), ?int_t(?ann()), [?nu_op('==', {proj, [], ?id("$init_state"), ?id("C.state.x")})]))}
%% }
%% }
%% }
%% , {"failing",
%% {error,
%% lists:seq(1, 7)
%% }
%% }
].
+14
View File
@@ -0,0 +1,14 @@
contract C =
stateful entrypoint f(a, x:int) =
if(a < x) a else x
+8
View File
@@ -0,0 +1,8 @@
contract C =
entrypoint fff() = 123
function
f : {n : int | n > 0} => {res : int | res =< n}
f(x) =
switch(x)
_ => 1 / x
+9
View File
@@ -0,0 +1,9 @@
contract C =
entrypoint f1() =
1 / (Contract.balance + 2)
stateful entrypoint f2(a) =
require(Contract.balance > 11, "")
Chain.spend(a, 10)
1 / Contract.balance
+23
View File
@@ -0,0 +1,23 @@
contract C =
entrypoint f() = 1 / 0
entrypoint g(x) = 1 / x
stateful entrypoint h(a, x) = Chain.spend(a, x)
entrypoint i(x) =
switch(x)
0 => 1
entrypoint j(x) =
switch(x)
_ => 1
_ => 2
entrypoint k(x) =
let 0 = x
x
entrypoint
l : () => {n : int | n > 0}
l() = 0
+5
View File
@@ -0,0 +1,5 @@
contract C =
entrypoint
len : {l : list('a)} => {r : int | r == l}
len([]) = 0
len(_::t) = len(t)
+6
View File
@@ -0,0 +1,6 @@
contract C =
entrypoint max(a : int, b : int) =
if(a >= b) a else b
entrypoint trim(x) =
max(0, x)
+9
View File
@@ -0,0 +1,9 @@
contract C =
stateful entrypoint f1(a) =
require(Contract.balance == 10, "xd")
Chain.spend(a, 10)
Contract.balance
entrypoint f2(x) =
require(x > 0, "")
1 / x
+15
View File
@@ -0,0 +1,15 @@
include "List.aes"
contract C =
payable stateful entrypoint split(targets : list(address)) =
require(targets != [], "NO_TARGETS")
let value_per_person = Call.value / List.length(targets)
spend_to_all(value_per_person, targets)
stateful function
spend_to_all : ({v : int | v >= 0}, list(address)) => unit
spend_to_all(_, []) = ()
spend_to_all(value, addr::rest) =
require(value < Contract.balance, "")
Chain.spend(addr, value)
spend_to_all(value, rest)
+7
View File
@@ -0,0 +1,7 @@
contract C =
record state = {x : int}
function inc(x) = x + 1
stateful entrypoint f() =
let s = state
state.x
+12
View File
@@ -0,0 +1,12 @@
contract C =
entrypoint f(x) =
switch(x)
1 => x
2 => 2
y => (x + y) / 2
function
g : {n : int | n > 0 && n < 4} => {r : int | r == 2}
g(1) = 2
g(2) = 2
g(3) = g(1) + 0
+8
View File
@@ -0,0 +1,8 @@
include "List.aes"
contract Test =
stateful entrypoint f(l, a) =
require(Contract.balance > 10, "xd")
Chain.spend(l, 10)
+50
View File
@@ -0,0 +1,50 @@
contract C =
type i('a) = 'a
type ii = i(int)
datatype iii = III(ii)
datatype ib = I(i(int)) | B(bool) | IB(int, bool)
datatype d_nest = DNest(ib)
datatype maybe('a) = Nothing | Just('a)
type maybemaybe('a) = maybe(maybe('a))
type maybe_int = maybe(int)
record r = {i : int, b : bool}
record rr = {r : r}
entrypoint
test_i : () => {res : int | res == 123}
test_i() = 123
entrypoint
test_ii : (ii) => {res : int | res == 123}
test_ii(x) = x - x + 123
entrypoint
test_iii1() = III(123)
entrypoint
test_iii2 : () => {iii <: III({res : int | res > 0})}
test_iii2() = III(123)
/*
entrypoint
test_ib1() = I(1)
entrypoint
test_ib2() = B(true)
entrypoint
test_ib3() = IB(123, true)
function
test_ib4 : {ib <: I({n : int | n == 0})} => {res : int | res == 1}
test_ib4(I(0)) = 1
function
test_ib5 : {ib <: I({n : int | n == 0})} => {res : int | res == 1}
test_ib5(q) = switch(q)
_ => 1
function
test_ib6 : bool => {ib <: IB({n : int | n == 0}, bool)}
test_ib6(b) = IB(0, b)
entrypoint test_maybemaybe() = Just(Nothing)
entrypoint test_maybe_int() = Nothing
*/