Liquid types
This commit is contained in:
@@ -93,6 +93,7 @@ check_errors(Expect, ErrorString) ->
|
||||
%% compilable_contracts() -> [ContractName].
|
||||
%% The currently compilable contracts.
|
||||
|
||||
compilable_contracts() -> [];
|
||||
compilable_contracts() ->
|
||||
[
|
||||
{"identity", "init", []},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
%% }
|
||||
%% }
|
||||
].
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
contract C =
|
||||
stateful entrypoint f(a, x:int) =
|
||||
if(a < x) a else x
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
entrypoint
|
||||
len : {l : list('a)} => {r : int | r == l}
|
||||
len([]) = 0
|
||||
len(_::t) = len(t)
|
||||
@@ -0,0 +1,6 @@
|
||||
contract C =
|
||||
entrypoint max(a : int, b : int) =
|
||||
if(a >= b) a else b
|
||||
|
||||
entrypoint trim(x) =
|
||||
max(0, x)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,7 @@
|
||||
contract C =
|
||||
record state = {x : int}
|
||||
|
||||
function inc(x) = x + 1
|
||||
stateful entrypoint f() =
|
||||
let s = state
|
||||
state.x
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
include "List.aes"
|
||||
|
||||
contract Test =
|
||||
stateful entrypoint f(l, a) =
|
||||
require(Contract.balance > 10, "xd")
|
||||
Chain.spend(l, 10)
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
Reference in New Issue
Block a user