Liquid types
This commit is contained in:
parent
b20b9c5df5
commit
0e73d7011d
@ -5,6 +5,7 @@
|
|||||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"05dfd7f"}}}
|
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"05dfd7f"}}}
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {eblake2, "1.0.0"}
|
, {eblake2, "1.0.0"}
|
||||||
|
, {meck, "0.9.2"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||||
{tag, "2.8.0"}}}
|
{tag, "2.8.0"}}}
|
||||||
]}.
|
]}.
|
||||||
|
12
rebar.lock
12
rebar.lock
@ -12,17 +12,25 @@
|
|||||||
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
|
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
|
||||||
2},
|
2},
|
||||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||||
|
{<<"effi">>,
|
||||||
|
{git,"https://github.com/joergen7/effi",
|
||||||
|
{ref,"aac0d5716c322200dd1145cf6bb651f79834bfd5"}},
|
||||||
|
0},
|
||||||
{<<"enacl">>,
|
{<<"enacl">>,
|
||||||
{git,"https://github.com/aeternity/enacl.git",
|
{git,"https://github.com/aeternity/enacl.git",
|
||||||
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
||||||
2},
|
2},
|
||||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||||
|
{<<"jsone">>,{pkg,<<"jsone">>,<<"1.4.7">>},1},
|
||||||
{<<"jsx">>,
|
{<<"jsx">>,
|
||||||
{git,"https://github.com/talentdeficit/jsx.git",
|
{git,"https://github.com/talentdeficit/jsx.git",
|
||||||
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
|
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
|
||||||
0}]}.
|
0},
|
||||||
|
{<<"meck">>,{pkg,<<"meck">>,<<"0.8.12">>},0}]}.
|
||||||
[
|
[
|
||||||
{pkg_hash,[
|
{pkg_hash,[
|
||||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>},
|
||||||
|
{<<"jsone">>, <<"A970C23D9700AE7842B526C57677E6E3F10894B429524696EAD547E9302391C0">>},
|
||||||
|
{<<"meck">>, <<"1F7B1A9F5D12C511848FEC26BBEFD09A21E1432EADB8982D9A8ACEB9891A3CF2">>}]}
|
||||||
].
|
].
|
||||||
|
@ -165,7 +165,9 @@ encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)};
|
|||||||
encode_type({alias_t, Type}) -> encode_type(Type);
|
encode_type({alias_t, Type}) -> encode_type(Type);
|
||||||
encode_type({fun_t, _, _, As, T}) -> #{function =>
|
encode_type({fun_t, _, _, As, T}) -> #{function =>
|
||||||
#{arguments => encode_types(As),
|
#{arguments => encode_types(As),
|
||||||
returns => encode_type(T)}}.
|
returns => encode_type(T)}};
|
||||||
|
encode_type({named_t, _, _, T}) -> encode_type(T);
|
||||||
|
encode_type({liquid, _, T, _}) -> encode_type(T).
|
||||||
|
|
||||||
encode_types(Ts) -> [ encode_type(T) || T <- Ts ].
|
encode_types(Ts) -> [ encode_type(T) || T <- Ts ].
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
, infer/2
|
, infer/2
|
||||||
, unfold_types_in_type/3
|
, unfold_types_in_type/3
|
||||||
, pp_type/2
|
, pp_type/2
|
||||||
|
, init_env/1
|
||||||
|
, lookup_env/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("aeso_utils.hrl").
|
-include("aeso_utils.hrl").
|
||||||
@ -130,9 +132,11 @@
|
|||||||
, stateful = false :: boolean()
|
, stateful = false :: boolean()
|
||||||
, current_function = none :: none | aeso_syntax:id()
|
, current_function = none :: none | aeso_syntax:id()
|
||||||
, what = top :: top | namespace | contract | contract_interface
|
, what = top :: top | namespace | contract | contract_interface
|
||||||
|
, allow_liquid = true :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type env() :: #env{}.
|
-type env() :: #env{}.
|
||||||
|
-export_type([env/0]).
|
||||||
|
|
||||||
-define(PRINT_TYPES(Fmt, Args),
|
-define(PRINT_TYPES(Fmt, Args),
|
||||||
when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
|
when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
|
||||||
@ -1094,11 +1098,126 @@ check_type(Env, Type = {fun_t, Ann, NamedArgs, Args, Ret}, Arity) ->
|
|||||||
ensure_base_type(Type, Arity),
|
ensure_base_type(Type, Arity),
|
||||||
NamedArgs1 = [ check_named_arg(Env, NamedArg) || NamedArg <- NamedArgs ],
|
NamedArgs1 = [ check_named_arg(Env, NamedArg) || NamedArg <- NamedArgs ],
|
||||||
Args1 = [ check_type(Env, Arg, 0) || Arg <- Args ],
|
Args1 = [ check_type(Env, Arg, 0) || Arg <- Args ],
|
||||||
Ret1 = check_type(Env, Ret, 0),
|
NamedTArgs = [{Var, T} || {refined_t, _, Var, T, _} <- Args1]
|
||||||
|
++ [{Var, {id, [], "int"}} || {dep_list_t, _, Var, _, _} <- Args1],
|
||||||
|
Env1 = bind_vars(NamedTArgs, Env),
|
||||||
|
Ret1 = check_type(Env1, Ret, 0),
|
||||||
{fun_t, Ann, NamedArgs1, Args1, Ret1};
|
{fun_t, Ann, NamedArgs1, Args1, Ret1};
|
||||||
check_type(_Env, Type = {uvar, _, _}, Arity) ->
|
check_type(_Env, Type = {uvar, _, _}, Arity) ->
|
||||||
ensure_base_type(Type, Arity),
|
ensure_base_type(Type, Arity),
|
||||||
Type;
|
Type;
|
||||||
|
check_type(Env, T = {refined_t, Ann, Id, Base, Pred}, Arity) ->
|
||||||
|
[type_error({illegal_liquid, T}) || not Env#env.allow_liquid],
|
||||||
|
ensure_base_type(Base, Arity),
|
||||||
|
Env1 = Env#env{allow_liquid = false},
|
||||||
|
Base1 = check_type(Env1, Base, Arity),
|
||||||
|
Env2 = bind_var(Id, Base, Env1),
|
||||||
|
Pred1 = [check_expr(Env2, Q, {id, aeso_syntax:get_ann(Q), "bool"}) || Q <- Pred],
|
||||||
|
{refined_t, Ann, Id, Base1, Pred1};
|
||||||
|
check_type(Env, T = {dep_record_t, Ann, Base, Fields}, Arity) ->
|
||||||
|
ensure_base_type(T, Arity),
|
||||||
|
[type_error({illegal_liquid, T}) || not Env#env.allow_liquid],
|
||||||
|
Base1 = check_type(Env, Base, Arity),
|
||||||
|
Id = case Base1 of
|
||||||
|
{app_t, _, I, _} -> I;
|
||||||
|
_ -> Base1
|
||||||
|
end,
|
||||||
|
%% TODO Validate fields in record
|
||||||
|
{QId, TrueFields} =
|
||||||
|
case lookup_type(Env, Id) of
|
||||||
|
{QName, {QAnn, {_, {record_t, F}}}} -> {qid(QAnn, QName), F};
|
||||||
|
_ -> type_error({not_a_record_type, Id, T}),
|
||||||
|
{Id, []}
|
||||||
|
end,
|
||||||
|
Fields1 =
|
||||||
|
[ case [ FieldNew
|
||||||
|
|| FieldNew = {field_t, _, FNameNew, _} <- Fields,
|
||||||
|
name(FNameNew) == name(FNameOld)] of
|
||||||
|
[{field_t, FAnn, FName, FType}] ->
|
||||||
|
{field_t, FAnn, FName, check_type(Env, FType)};
|
||||||
|
_ -> FieldOld
|
||||||
|
end
|
||||||
|
|| FieldOld = {field_t, _, FNameOld, _} <- TrueFields
|
||||||
|
],
|
||||||
|
constrain(
|
||||||
|
[ #field_constraint{
|
||||||
|
record_t = QId,
|
||||||
|
field = FName,
|
||||||
|
field_t = FType,
|
||||||
|
kind = project,
|
||||||
|
context = {proj, Ann, QId, FName} }
|
||||||
|
|| {field_t, _, FName, FType} <- Fields
|
||||||
|
]),
|
||||||
|
{dep_record_t, Ann, QId, Fields1};
|
||||||
|
check_type(Env, T = {dep_variant_t, Ann, TId, Base, undefined, Constrs}, Arity) ->
|
||||||
|
ensure_base_type(T, Arity),
|
||||||
|
[type_error({illegal_liquid, T}) || not Env#env.allow_liquid],
|
||||||
|
Base1 = check_type(Env, Base, Arity),
|
||||||
|
Id = case Base1 of
|
||||||
|
{app_t, _, I, _} -> I;
|
||||||
|
_ -> Base1
|
||||||
|
end,
|
||||||
|
Args = case Base1 of
|
||||||
|
{app_t, _, _, A} -> A;
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
{QId, TrueConstrs} =
|
||||||
|
case lookup_type(Env, Id) of
|
||||||
|
{Q, {QAnn, {_, {variant_t, Cs}}}} -> {{qid, QAnn, Q}, Cs};
|
||||||
|
{["option"], {QAnn, {builtin, _}}} ->
|
||||||
|
{{qid, QAnn, ["option"]},
|
||||||
|
[ {constr_t, QAnn, {con, QAnn, "None"}, []}
|
||||||
|
, {constr_t, QAnn, {con, QAnn, "Some"}, Args}
|
||||||
|
]
|
||||||
|
}; %% TODO other types
|
||||||
|
_ -> type_error({not_a_variant_type, Id, T}),
|
||||||
|
{Id, []}
|
||||||
|
end,
|
||||||
|
[ check_expr(Env, Con,
|
||||||
|
case CArgs of
|
||||||
|
[] -> Base1;
|
||||||
|
_ -> {fun_t, CAnn, [], CArgs, Base1}
|
||||||
|
end)
|
||||||
|
|| {constr_t, CAnn, Con, CArgs} <- Constrs
|
||||||
|
],
|
||||||
|
Constrs1 =
|
||||||
|
[ case [ ConstrNew
|
||||||
|
|| ConstrNew = {constr_t, _, CNameNew, _} <- Constrs,
|
||||||
|
name(CNameNew) == name(CNameOld)] of
|
||||||
|
[{constr_t, FAnn, CName, CArgs}] ->
|
||||||
|
{constr_t, FAnn, CName,
|
||||||
|
[ check_type(Env, CArg) || CArg <- CArgs ]
|
||||||
|
};
|
||||||
|
_ -> ConstrOld
|
||||||
|
end
|
||||||
|
|| ConstrOld = {constr_t, _, CNameOld, _} <- TrueConstrs
|
||||||
|
],
|
||||||
|
OnQcon = fun(A) -> qcon(aeso_syntax:get_ann(QId), lists:droplast(qname(QId)) ++ qname(A)) end,
|
||||||
|
TagPred =
|
||||||
|
case Constrs of
|
||||||
|
[] -> [{bool, [], false}];
|
||||||
|
[{constr_t, CAnn, Con, Args}] ->
|
||||||
|
[{is_tag, CAnn, TId, OnQcon(Con), Args, Base1}];
|
||||||
|
_ ->
|
||||||
|
[ {app, Ann, {'!', Ann},
|
||||||
|
[{is_tag, CAnn, TId, OnQcon(TrueCon), Args, Base1}]}
|
||||||
|
|| {constr_t, CAnn, TrueCon, Args} <- TrueConstrs,
|
||||||
|
lists:all(
|
||||||
|
fun({constr_t, _, Con, _}) ->
|
||||||
|
qname(Con) /= qname(TrueCon)
|
||||||
|
end, Constrs
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
{dep_variant_t, Ann, TId, Base1, TagPred, Constrs1};
|
||||||
|
check_type(Env, T = {dep_list_t, Ann, Id, ElemT, LenPred}, Arity) ->
|
||||||
|
ensure_base_type(T, Arity),
|
||||||
|
[type_error({illegal_liquid, T}) || not Env#env.allow_liquid],
|
||||||
|
ElemT1 = check_type(Env, ElemT),
|
||||||
|
Env1 = Env#env{allow_liquid = false},
|
||||||
|
Env2 = bind_var(Id, {id, [], "int"}, Env1),
|
||||||
|
LenPred1 = [check_expr(Env2, Q, {id, [], "bool"}) || Q <- LenPred],
|
||||||
|
{dep_list_t, Ann, Id, ElemT1, LenPred1};
|
||||||
check_type(_Env, {args_t, Ann, Ts}, _) ->
|
check_type(_Env, {args_t, Ann, Ts}, _) ->
|
||||||
type_error({new_tuple_syntax, Ann, Ts}),
|
type_error({new_tuple_syntax, Ann, Ts}),
|
||||||
{tuple_t, Ann, Ts}.
|
{tuple_t, Ann, Ts}.
|
||||||
@ -1263,7 +1382,12 @@ infer_letrec(Env, Defs) ->
|
|||||||
infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
||||||
Type1 = check_type(Env, Type),
|
Type1 = check_type(Env, Type),
|
||||||
{NameSigs, Clauses1} = lists:unzip([ infer_letfun1(Env, Clause) || Clause <- Clauses ]),
|
{NameSigs, Clauses1} = lists:unzip([ infer_letfun1(Env, Clause) || Clause <- Clauses ]),
|
||||||
{_, Sigs = [Sig | _]} = lists:unzip(NameSigs),
|
{_, Sigs = [Sig0 | _]} = lists:unzip(NameSigs),
|
||||||
|
Sig = case Type1 of
|
||||||
|
{fun_t, TAnn, Named, ArgsT, RetT} ->
|
||||||
|
{type_sig, TAnn, none, Named, ArgsT, RetT};
|
||||||
|
_ -> Sig0
|
||||||
|
end,
|
||||||
_ = [ begin
|
_ = [ begin
|
||||||
ClauseT = typesig_to_fun_t(ClauseSig),
|
ClauseT = typesig_to_fun_t(ClauseSig),
|
||||||
unify(Env, ClauseT, Type1, {check_typesig, Name, ClauseT, Type1})
|
unify(Env, ClauseT, Type1, {check_typesig, Name, ClauseT, Type1})
|
||||||
@ -1302,8 +1426,8 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
|
|||||||
{letfun, Ann, Fun, Args, RetType,
|
{letfun, Ann, Fun, Args, RetType,
|
||||||
{typed, NoAnn,
|
{typed, NoAnn,
|
||||||
{switch, NoAnn, Tuple(Args),
|
{switch, NoAnn, Tuple(Args),
|
||||||
[ {'case', AnnC, Tuple(ArgsC), Body}
|
[ {'case', AnnC, Tuple(ArgsC), Body}
|
||||||
|| {letfun, AnnC, _, ArgsC, _, Body} <- Clauses ]}, RetType}}
|
|| {letfun, AnnC, _, ArgsC, _, Body} <- Clauses ]}, RetType}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
print_typesig({Name, TypeSig}) ->
|
print_typesig({Name, TypeSig}) ->
|
||||||
@ -1735,8 +1859,8 @@ infer_op(Env, As, Op, Args, InferOp) ->
|
|||||||
TypedArgs = [infer_expr(Env, A) || A <- Args],
|
TypedArgs = [infer_expr(Env, A) || A <- Args],
|
||||||
ArgTypes = [T || {typed, _, _, T} <- TypedArgs],
|
ArgTypes = [T || {typed, _, _, T} <- TypedArgs],
|
||||||
Inferred = {fun_t, _, _, OperandTypes, ResultType} = InferOp(Op),
|
Inferred = {fun_t, _, _, OperandTypes, ResultType} = InferOp(Op),
|
||||||
unify(Env, ArgTypes, OperandTypes, {infer_app, Op, [], Args, Inferred, ArgTypes}),
|
unify(Env, ArgTypes, OperandTypes, {infer_app, Op, [], Inferred, ArgTypes}),
|
||||||
{typed, As, {app, As, Op, TypedArgs}, ResultType}.
|
{typed, As, {app, As, {typed, As, Op, Inferred}, TypedArgs}, ResultType}.
|
||||||
|
|
||||||
infer_pattern(Env, Pattern) ->
|
infer_pattern(Env, Pattern) ->
|
||||||
Vars = free_vars(Pattern),
|
Vars = free_vars(Pattern),
|
||||||
@ -2226,7 +2350,7 @@ solve_known_record_types(Env, Constraints) ->
|
|||||||
unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}),
|
unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}),
|
||||||
C
|
C
|
||||||
end;
|
end;
|
||||||
_ ->
|
X ->
|
||||||
type_error({not_a_record_type, instantiate(RecType), When}),
|
type_error({not_a_record_type, instantiate(RecType), When}),
|
||||||
not_solved
|
not_solved
|
||||||
end
|
end
|
||||||
@ -2264,6 +2388,8 @@ record_type_name({app_t, _Attrs, RecId, _Args}) when ?is_type_id(RecId) ->
|
|||||||
RecId;
|
RecId;
|
||||||
record_type_name(RecId) when ?is_type_id(RecId) ->
|
record_type_name(RecId) when ?is_type_id(RecId) ->
|
||||||
RecId;
|
RecId;
|
||||||
|
record_type_name({dep_record_t, _, RecId, _}) when ?is_type_id(RecId) ->
|
||||||
|
RecId;
|
||||||
record_type_name(_Other) ->
|
record_type_name(_Other) ->
|
||||||
%% io:format("~p is not a record type\n", [Other]),
|
%% io:format("~p is not a record type\n", [Other]),
|
||||||
{id, [{origin, system}], "not_a_record_type"}.
|
{id, [{origin, system}], "not_a_record_type"}.
|
||||||
@ -2390,6 +2516,8 @@ unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
|
|||||||
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
|
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
|
||||||
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
|
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
|
||||||
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
|
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
|
||||||
|
unfold_types_in_type(Env, {dep_arg_t, Ann, Con, Types}, Options) ->
|
||||||
|
{dep_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
|
||||||
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
|
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
|
||||||
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
||||||
unfold_types_in_type(Env, [H|T], Options) ->
|
unfold_types_in_type(Env, [H|T], Options) ->
|
||||||
@ -2487,6 +2615,26 @@ unify1(Env, {app_t, _, T, []}, B, When) ->
|
|||||||
unify(Env, T, B, When);
|
unify(Env, T, B, When);
|
||||||
unify1(Env, A, {app_t, _, T, []}, When) ->
|
unify1(Env, A, {app_t, _, T, []}, When) ->
|
||||||
unify(Env, A, T, When);
|
unify(Env, A, T, When);
|
||||||
|
unify1(Env, A, {refined_t, _, _, B, _}, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, {refined_t, _, _, A, _}, B, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, A, {dep_record_t, _, B, _}, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, {dep_record_t, _, A, _}, B, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, A, {dep_variant_t, _, _, B, _, _}, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, {dep_variant_t, _, _, A, _, _}, B, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, A, {dep_list_t, Ann, _, B, _}, When) ->
|
||||||
|
unify1(Env, A, {app_t, Ann, {id, Ann, "list"}, [B]}, When);
|
||||||
|
unify1(Env, {dep_list_t, Ann, _, A, _}, B, When) ->
|
||||||
|
unify1(Env, {app_t, Ann, {id, Ann, "list"}, [A]}, B, When);
|
||||||
|
unify1(Env, {named_t, _, _, A}, B, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
|
unify1(Env, A, {named_t, _, _, B}, When) ->
|
||||||
|
unify1(Env, A, B, When);
|
||||||
unify1(_Env, A, B, When) ->
|
unify1(_Env, A, B, When) ->
|
||||||
cannot_unify(A, B, When),
|
cannot_unify(A, B, When),
|
||||||
false.
|
false.
|
||||||
@ -2535,6 +2683,18 @@ occurs_check1(R, {if_t, _, _, Then, Else}) ->
|
|||||||
occurs_check(R, [Then, Else]);
|
occurs_check(R, [Then, Else]);
|
||||||
occurs_check1(R, [H | T]) ->
|
occurs_check1(R, [H | T]) ->
|
||||||
occurs_check(R, H) orelse occurs_check(R, T);
|
occurs_check(R, H) orelse occurs_check(R, T);
|
||||||
|
occurs_check1(R, {named_t, _, _, T}) ->
|
||||||
|
occurs_check1(R, T);
|
||||||
|
occurs_check1(R, {refined_t, _, _, T, _}) ->
|
||||||
|
occurs_check1(R, T);
|
||||||
|
occurs_check1(R, {dep_record_t, _, _, T}) ->
|
||||||
|
occurs_check1(R, T);
|
||||||
|
occurs_check1(R, {dep_variant_t, _, _, _, _, T}) ->
|
||||||
|
occurs_check1(R, T);
|
||||||
|
occurs_check1(R, {constr_t, _, _, T}) ->
|
||||||
|
occurs_check(R, T);
|
||||||
|
occurs_check1(R, {dep_list_t, _, _, T, _}) ->
|
||||||
|
occurs_check1(R, T);
|
||||||
occurs_check1(_, []) -> false.
|
occurs_check1(_, []) -> false.
|
||||||
|
|
||||||
fresh_uvar(Attrs) ->
|
fresh_uvar(Attrs) ->
|
||||||
@ -2689,8 +2849,9 @@ mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) ->
|
|||||||
, [pp(Id), pp_loc(Id), pp(instantiate(Type))]),
|
, [pp(Id), pp_loc(Id), pp(instantiate(Type))]),
|
||||||
mk_t_err(pos(Id), Msg);
|
mk_t_err(pos(Id), Msg);
|
||||||
mk_error({cannot_unify, A, B, When}) ->
|
mk_error({cannot_unify, A, B, When}) ->
|
||||||
Msg = io_lib:format("Cannot unify ~s\n and ~s\n",
|
AStr = pp(instantiate(A)),
|
||||||
[pp(instantiate(A)), pp(instantiate(B))]),
|
BStr = pp(instantiate(B)),
|
||||||
|
Msg = io_lib:format("Cannot unify ~s\n and ~s\n", [AStr, BStr]),
|
||||||
{Pos, Ctxt} = pp_when(When),
|
{Pos, Ctxt} = pp_when(When),
|
||||||
mk_t_err(Pos, Msg, Ctxt);
|
mk_t_err(Pos, Msg, Ctxt);
|
||||||
mk_error({unbound_variable, Id}) ->
|
mk_error({unbound_variable, Id}) ->
|
||||||
@ -2704,6 +2865,9 @@ mk_error({unbound_variable, Id}) ->
|
|||||||
mk_error({undefined_field, Id}) ->
|
mk_error({undefined_field, Id}) ->
|
||||||
Msg = io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]),
|
Msg = io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]),
|
||||||
mk_t_err(pos(Id), Msg);
|
mk_t_err(pos(Id), Msg);
|
||||||
|
mk_error({not_a_variant_type, Type}) ->
|
||||||
|
Msg = io_lib:format("~s\n", [pp_type("Not a variant type: ", Type)]),
|
||||||
|
mk_t_err(pos(Type), Msg);
|
||||||
mk_error({not_a_record_type, Type, Why}) ->
|
mk_error({not_a_record_type, Type, Why}) ->
|
||||||
Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]),
|
Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]),
|
||||||
{Pos, Ctxt} = pp_why_record(Why),
|
{Pos, Ctxt} = pp_why_record(Why),
|
||||||
@ -2987,6 +3151,9 @@ mk_error({contract_lacks_definition, Type, When}) ->
|
|||||||
),
|
),
|
||||||
{Pos, Ctxt} = pp_when(When),
|
{Pos, Ctxt} = pp_when(When),
|
||||||
mk_t_err(Pos, Msg, Ctxt);
|
mk_t_err(Pos, Msg, Ctxt);
|
||||||
|
mk_error({illegal_liquid, T}) ->
|
||||||
|
Msg = io_lib:format("Illegal liquid type ~s", [pp_type("", T)]),
|
||||||
|
mk_t_err(pos(T), Msg);
|
||||||
mk_error(Err) ->
|
mk_error(Err) ->
|
||||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||||
mk_t_err(pos(0, 0), Msg).
|
mk_t_err(pos(0, 0), Msg).
|
||||||
@ -3149,8 +3316,11 @@ pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) ->
|
|||||||
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
||||||
{pos(Rec),
|
{pos(Rec),
|
||||||
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
||||||
[pp(FldName), pp_loc(Rec)])}.
|
[pp(FldName), pp_loc(Rec)])};
|
||||||
|
pp_why_record({dep_record_t, _, Rec, _}) ->
|
||||||
|
{pos(Rec),
|
||||||
|
io_lib:format("arising from the record refinement of the type ~s (at ~s)",
|
||||||
|
[pp(Rec), pp_loc(Rec)])}.
|
||||||
|
|
||||||
if_branches(If = {'if', Ann, _, Then, Else}) ->
|
if_branches(If = {'if', Ann, _, Then, Else}) ->
|
||||||
case proplists:get_value(format, Ann) of
|
case proplists:get_value(format, Ann) of
|
||||||
|
3163
src/aeso_ast_refine_types.erl
Normal file
3163
src/aeso_ast_refine_types.erl
Normal file
File diff suppressed because it is too large
Load Diff
56
src/aeso_ast_refine_types.hrl
Normal file
56
src/aeso_ast_refine_types.hrl
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
-define(op(Ann, A, Op, B), {app, [{format, infix}|Ann], {Op, Ann}, [A, B]}).
|
||||||
|
-define(op(Ann, Op, B), {app, [{format, prefix}|Ann], {Op, Ann}, [B]}).
|
||||||
|
|
||||||
|
-define(int(Ann, I), {int, Ann, I}).
|
||||||
|
-define(int_tp, {id, _, "int"}).
|
||||||
|
-define(int_t(Ann), {id, Ann, "int"}).
|
||||||
|
|
||||||
|
-define(d_pos_int(Ann), ?refined(?int_t(Ann), [?op(Ann, ?nu(Ann), '>', ?int(Ann, 0))])).
|
||||||
|
-define(d_nonneg_int(Ann), ?refined(?int_t(Ann), [?op(Ann, ?nu(Ann), '>=', ?int(Ann, 0))])).
|
||||||
|
%% -define(d_nonzero_int, refined(?int_t, [?op(?nu(), '!=', ?int(0))])).
|
||||||
|
|
||||||
|
-define(bool(Ann, B), {bool, Ann, B}).
|
||||||
|
-define(bool_tp, {id, _, "bool"}).
|
||||||
|
-define(bool_t(Ann), {id, Ann, "bool"}).
|
||||||
|
|
||||||
|
%% -define(tuple(S), {tuple, _, S}).
|
||||||
|
-define(tuple_tp(T), {tuple_t, _, T}).
|
||||||
|
%% -define(tuple_t(T), {tuple_t, ?ann(), T}).
|
||||||
|
|
||||||
|
-define(tuple_proj_id(Ann, N, I),
|
||||||
|
{id, Ann, lists:flatten(io_lib:format("$tuple~p.~p", [N, I]))}).
|
||||||
|
-define(adt_proj_id(Ann, QCon, I),
|
||||||
|
{id, Ann, lists:flatten(io_lib:format("~s.~p", [string:join(qname(QCon), "."), I]))}).
|
||||||
|
|
||||||
|
%% -define(string(S), {string, _, S}).
|
||||||
|
-define(string_tp, {id, _, "string"}).
|
||||||
|
%% -define(string_t, {id, ?ann(), "string"}).
|
||||||
|
|
||||||
|
-define(typed(Expr, Type), {typed, element(2, Expr), Expr, Type}).
|
||||||
|
-define(typed_p(Expr), {typed, _, Expr, _}).
|
||||||
|
-define(typed_p(Expr, Type), {typed, _, Expr, Type}).
|
||||||
|
|
||||||
|
-define(refined(Id, T, Q),
|
||||||
|
{refined_t, element(2, T), Id, T, Q}).
|
||||||
|
-define(refined(T, Q),
|
||||||
|
(fun(Id) ->
|
||||||
|
?refined(Id, T, apply_subst(?nu(element(2, T)), Id, Q))
|
||||||
|
end)
|
||||||
|
(fresh_id(
|
||||||
|
element(2, T),
|
||||||
|
case T of
|
||||||
|
?int_tp -> "n";
|
||||||
|
?bool_tp -> "b";
|
||||||
|
?string_tp -> "s";
|
||||||
|
_ when element(1, T) == id -> name(T);
|
||||||
|
_ -> "v"
|
||||||
|
end
|
||||||
|
))).
|
||||||
|
-define(refined(T), ?refined(T, [])).
|
||||||
|
|
||||||
|
-define(ann(), [{origin, hagia}]).
|
||||||
|
-define(ann_of(E), element(2, E)).
|
||||||
|
|
||||||
|
-define(nu(Ann), {id, Ann, "$self"}).
|
||||||
|
-define(nu_p, {id, _, "$self"}).
|
391
src/aeso_ast_refine_types_stdlib.hrl
Normal file
391
src/aeso_ast_refine_types_stdlib.hrl
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
|
||||||
|
-define(IS_STDLIB(NS),
|
||||||
|
(NS == "List" orelse
|
||||||
|
NS == "ListInternal" orelse
|
||||||
|
NS == "Option" orelse
|
||||||
|
NS == "Bits" orelse
|
||||||
|
NS == "Bytes" orelse
|
||||||
|
NS == "Char" orelse
|
||||||
|
NS == "Int" orelse
|
||||||
|
NS == "Map" orelse
|
||||||
|
NS == "Address" orelse
|
||||||
|
NS == "Crypto" orelse
|
||||||
|
NS == "Auth" orelse
|
||||||
|
NS == "Oracle" orelse
|
||||||
|
NS == "AENS" orelse
|
||||||
|
NS == "Contract" orelse
|
||||||
|
NS == "Call" orelse
|
||||||
|
NS == "Chain" orelse
|
||||||
|
false
|
||||||
|
)).
|
||||||
|
|
||||||
|
-define(IS_STDLIB_STATEFUL(NS, Fun),
|
||||||
|
((NS == "List" andalso Fun == "map") orelse
|
||||||
|
(NS == "List" andalso Fun == "flat_map") orelse
|
||||||
|
(NS == "Chain" andalso Fun == "spend") orelse
|
||||||
|
false
|
||||||
|
)).
|
||||||
|
|
||||||
|
|
||||||
|
-define(CONSTR(NS, Fun, Args, ArgsT, Body),
|
||||||
|
constr_expr(Env, {app, Ann, {typed, _, {qid, _, [NS, Fun]}, {fun_t, _, [], ArgsT, _}}, Args}, RetT, S0) ->
|
||||||
|
Body;
|
||||||
|
).
|
||||||
|
-define(CONSTR(NS, Fun, Args, Body),
|
||||||
|
constr_expr(Env, {app, Ann, {typed, _, {qid, _, [NS, Fun]}, {fun_t, _, [], _, _}}, Args}, RetT, S0) ->
|
||||||
|
Body;
|
||||||
|
).
|
||||||
|
|
||||||
|
-define(UNSOME(Pat, Constrs), [Pat] =
|
||||||
|
[ ArgT
|
||||||
|
|| C <- Constrs,
|
||||||
|
ArgT <- case C of
|
||||||
|
{dep_constr_t, CAnn, Con = {con, _, "Some"}, [CT]} -> [CT];
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(
|
||||||
|
STDLIB_CONSTRS,
|
||||||
|
?CONSTR("Chain", "spend", [State, Balance, Addr, Amount],
|
||||||
|
begin
|
||||||
|
{_, S1} = constr_expr(Env, State, S0),
|
||||||
|
{BalanceT, S2} = constr_expr(Env, Balance, S1),
|
||||||
|
{_, S3} = constr_expr(Env, Addr, S2),
|
||||||
|
{AmountT, S4} = constr_expr(Env, Amount, S3),
|
||||||
|
ExprT = {tuple_t, _, [_, _, NewBalanceT]} = fresh_liquid(Env, "spend", RetT),
|
||||||
|
{ExprT,
|
||||||
|
[ {well_formed, constr_id(chain_spend), Env, ExprT}
|
||||||
|
, {subtype, constr_id(chain_spend), Ann, Env,
|
||||||
|
AmountT,
|
||||||
|
?refined(?int_t(Ann), [ ?op(Ann, ?nu(Ann), '=<', Balance)
|
||||||
|
, ?op(Ann, ?nu(Ann), '>=', ?int(Ann, 0))])}
|
||||||
|
, {subtype, constr_id(chain_spend), Ann, Env,
|
||||||
|
?refined(?int_t(Ann), [?op(Ann, ?nu(Ann), '==', ?op(Ann, Balance, '-', Amount))]),
|
||||||
|
NewBalanceT
|
||||||
|
}
|
||||||
|
| S4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "is_empty", [L],
|
||||||
|
begin
|
||||||
|
{_, S1} = constr_expr(Env, L, S0),
|
||||||
|
ExprT = fresh_liquid(Env, "is_empty", RetT),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_is_empty), Env, ExprT}
|
||||||
|
, {subtype, constr_id(is_empty), Ann, Env,
|
||||||
|
?refined(?bool_t(Ann), [?op(Ann, ?nu(Ann), '==', ?op(Ann, L, '==', ?int(Ann, 0)))]),
|
||||||
|
ExprT}
|
||||||
|
| S1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "first", [L],
|
||||||
|
begin
|
||||||
|
{{dep_list_t, _, _, ElemT, _}, S1} = constr_expr(Env, L, S0),
|
||||||
|
ExprT = {dep_variant_t, _, Id, _, _, Constrs} = fresh_liquid(Env, "first", RetT),
|
||||||
|
?UNSOME(RetConT, Constrs),
|
||||||
|
EnvEmpty = assert(?op(Ann, L, '==', ?int(Ann, 0)), Env),
|
||||||
|
EnvCons = assert(?op(Ann, L, '>', ?int(Ann, 0)), Env),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_first), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_first), Ann, EnvEmpty,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["None"]}, []}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_first), Ann, EnvCons,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["Some"]}, [RetConT]}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_first), Ann, EnvCons, ElemT, RetConT}
|
||||||
|
| S1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "tail", [L],
|
||||||
|
begin
|
||||||
|
{{dep_list_t, _, _, ElemT, _}, S1} = constr_expr(Env, L, S0),
|
||||||
|
{_, S1} = constr_expr(Env, L, S0),
|
||||||
|
ExprT = {dep_variant_t, _, Id, _, _, Constrs} = fresh_liquid(Env, "tail", RetT),
|
||||||
|
?UNSOME(RetConT, Constrs),
|
||||||
|
EnvEmpty = assert(?op(Ann, L, '==', ?int(Ann, 0)), Env),
|
||||||
|
EnvCons = assert(?op(Ann, L, '>', ?int(Ann, 0)), Env),
|
||||||
|
LId = fresh_id(Ann, "tail_l"),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_tail), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_tail), Ann, EnvEmpty,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["None"]}, []}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_tail), Ann, EnvCons,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["Some"]}, [RetConT]}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_tail), Ann, EnvCons,
|
||||||
|
{dep_list_t, Ann, LId, ElemT, [?op(Ann, LId, '==', ?op(Ann, L, '-', ?int(Ann, 1)))]}, RetConT}
|
||||||
|
| S1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "last", [L],
|
||||||
|
begin
|
||||||
|
{{dep_list_t, _, _, ElemT, _}, S1} = constr_expr(Env, L, S0),
|
||||||
|
ExprT = {dep_variant_t, _, Id, _, _, Constrs} = fresh_liquid(Env, "last", RetT),
|
||||||
|
?UNSOME(RetConT, Constrs),
|
||||||
|
EnvEmpty = assert(?op(Ann, L, '==', ?int(Ann, 0)), Env),
|
||||||
|
EnvCons = assert(?op(Ann, L, '>', ?int(Ann, 0)), Env),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_last), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_last), Ann, EnvEmpty,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["None"]}, []}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_last), Ann, EnvCons,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["Some"]}, [RetConT]}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_last), Ann, EnvCons, ElemT, RetConT}
|
||||||
|
| S1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
%% TODO contains – force false if no way to fulfill
|
||||||
|
|
||||||
|
%% TODO find – reduce type to fulfill the predicate
|
||||||
|
|
||||||
|
?CONSTR("List", "find_indices", [P, L], %% TODO: len == 0 if no way to fulfill
|
||||||
|
begin
|
||||||
|
{_, S1} = constr_expr(Env, P, S0),
|
||||||
|
{_, S2} = constr_expr(Env, L, S1),
|
||||||
|
ExprT = {dep_list_t, _, _, ElemT, _} = fresh_liquid(Env, "find_indices", RetT),
|
||||||
|
LId = fresh_id(Ann, "find_indices_l"),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_find_indices), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_find_indices), Ann, Env,
|
||||||
|
{dep_list_t, Ann, LId, ElemT, [?op(Ann, LId, '=<', L)]},
|
||||||
|
ExprT
|
||||||
|
}
|
||||||
|
, {subtype, constr_id(list_find_indices), Ann, Env, ?d_nonneg_int(Ann), ElemT}
|
||||||
|
| S2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "nth", [I, L],
|
||||||
|
begin
|
||||||
|
{IT, S1} = constr_expr(Env, I, S0),
|
||||||
|
{{dep_list_t, _, _, ElemT, _}, S2} = constr_expr(Env, L, S1),
|
||||||
|
ExprT = {dep_variant_t, _, Id, _, _, Constrs} = fresh_liquid(Env, "nth", RetT),
|
||||||
|
?UNSOME(RetConT, Constrs),
|
||||||
|
EnvEmpty = assert(?op(Ann, L, '==', ?int(Ann, 0)), Env),
|
||||||
|
EnvCons = assert(?op(Ann, L, '>', ?int(Ann, 0)), Env),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_nth), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_nth), Ann, EnvEmpty,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["None"]}, []}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_nth), Ann, EnvCons,
|
||||||
|
{dep_variant_t, Ann, Id, RetT, [{is_tag, Ann, Id, {qcon, Ann, ["Some"]}, [RetConT]}], Constrs},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_nth), Ann, EnvCons, ElemT, RetConT}
|
||||||
|
, {subtype, constr_id(list_nth), Ann, Env, IT, ?d_nonneg_int(Ann)}
|
||||||
|
| S2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "get", [I, L],
|
||||||
|
begin
|
||||||
|
{IT, S1} = constr_expr(Env, I, S0),
|
||||||
|
{{dep_list_t, _, _, ElemT, _}, S2} = constr_expr(Env, L, S1),
|
||||||
|
ExprT = fresh_liquid(Env, "get", RetT),
|
||||||
|
LId = fresh_id(Ann, "get_l"),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_get), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_get), Ann, Env,
|
||||||
|
IT,
|
||||||
|
{refined_t, Ann, LId, ?int_t(Ann), [?op(Ann, LId, '<', L)]}}
|
||||||
|
, {subtype, constr_id(list_get), Ann, Env, ElemT, ExprT}
|
||||||
|
, {subtype, constr_id(list_get), Ann, Env, IT, ?d_nonneg_int(Ann)}
|
||||||
|
| S2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "length", [L],
|
||||||
|
begin
|
||||||
|
{_, S1} = constr_expr(Env, L, S0),
|
||||||
|
ExprT = fresh_liquid(Env, "length", RetT),
|
||||||
|
LId = fresh_id(Ann, "length_l"),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_length), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_length), Ann, Env,
|
||||||
|
{refined_t, Ann, LId, ?int_t(Ann), [?op(Ann, LId, '==', L)]}
|
||||||
|
, ExprT}
|
||||||
|
| S1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "from_to", [From, To],
|
||||||
|
begin
|
||||||
|
{_, S1} = constr_expr(Env, From, S0),
|
||||||
|
{_, S2} = constr_expr(Env, To, S1),
|
||||||
|
ExprT = fresh_liquid(Env, "from_to", RetT),
|
||||||
|
ElemT = ?refined(?int_t(Ann), [?op(Ann, From, '=<', ?nu(Ann)), ?op(Ann, ?nu(Ann), '=<', To)]),
|
||||||
|
EnvEmpty = assert(?op(Ann, To, '<', From), Env),
|
||||||
|
EnvSome = assert(?op(Ann, To, '>=', From), Env),
|
||||||
|
LId = fresh_id(Ann, "from_to_l"),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_from_to), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_from_to), Ann, EnvEmpty,
|
||||||
|
{dep_list_t, Ann, LId, ElemT, [?op(Ann, LId, '==', ?int(Ann, 0))]},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_from_to), Ann, EnvSome,
|
||||||
|
{dep_list_t, Ann, LId, ElemT, [?op(Ann, LId, '==', ?op(Ann, ?op(Ann, To, '-', From), '+', ?int(Ann, 1)))]},
|
||||||
|
ExprT}
|
||||||
|
| S2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "from_to_step", [From, To, Step],
|
||||||
|
begin
|
||||||
|
{_, S1} = constr_expr(Env, From, S0),
|
||||||
|
{_, S2} = constr_expr(Env, To, S1),
|
||||||
|
{StepT, S3} = constr_expr(Env, Step, S2),
|
||||||
|
ExprT = fresh_liquid(Env, "from_to_step", RetT),
|
||||||
|
ElemT = ?refined(?int_t(Ann), [?op(Ann, From, '=<', ?nu(Ann)), ?op(Ann, ?nu(Ann), '=<', To)]),
|
||||||
|
EnvEmpty = assert(?op(Ann, To, '<', From), Env),
|
||||||
|
EnvSome = assert(?op(Ann, To, '>=', From), Env),
|
||||||
|
LId = fresh_id(Ann, "from_to_l_step"),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_from_to_step), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_from_to_step), Ann, EnvEmpty,
|
||||||
|
{dep_list_t, Ann, LId, ElemT, [?op(Ann, LId, '==', ?int(Ann, 0))]},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_from_to_step), Ann, EnvSome,
|
||||||
|
{dep_list_t, Ann, LId, ElemT,
|
||||||
|
[?op(Ann, LId, '==', ?op(Ann, ?op(Ann, ?op(Ann, To, '-', From), '/', Step), '+', ?int(Ann, 1)))]},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_from_to_step), Ann, Env, StepT, ?refined(?int_t(Ann), [?op(Ann, ?nu(Ann), '>', ?int(Ann, 0))])}
|
||||||
|
| S2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
%% TODO insert_at – consider length and update ElemT
|
||||||
|
|
||||||
|
%% TODO insert_by – consider length and update ElemT. skip comparator
|
||||||
|
|
||||||
|
?CONSTR("List", "reverse", [L],
|
||||||
|
begin
|
||||||
|
{LT, S1} = constr_expr(Env, L, S0),
|
||||||
|
ExprT = fresh_liquid(Env, "reverse", RetT),
|
||||||
|
{ ExprT
|
||||||
|
, [ {well_formed, constr_id(list_reverse), Env, ExprT}
|
||||||
|
, {subtype, constr_id(list_reverse), Ann, Env, LT, ExprT}
|
||||||
|
| S1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "map", [State = ?typed_p(_, StateT), Balance = ?typed_p(_, BalanceT), F = ?typed_p(UF), L],
|
||||||
|
[_, _, {fun_t, _, _, [_, _, _], _}, _],
|
||||||
|
begin
|
||||||
|
IsStateful = is_stateful(Env, UF),
|
||||||
|
{_, S1} = constr_expr(Env, State, S0),
|
||||||
|
{_, S2} = constr_expr(Env, Balance, S1),
|
||||||
|
NewStateT = fresh_liquid(Env, "map_state", StateT),
|
||||||
|
NewBalanceT = fresh_liquid(Env, "map_balance", BalanceT),
|
||||||
|
{{dep_list_t, _, LId, ElemT, LenQual}, S3} = constr_expr(Env, L, S2),
|
||||||
|
{{dep_fun_t, _,
|
||||||
|
[ {dep_arg_t, _, StateId, StateArgT}
|
||||||
|
, {dep_arg_t, _, BalanceId, BalanceArgT}
|
||||||
|
, {dep_arg_t, _, ArgId, ArgT}
|
||||||
|
], FunResT}, S4} = constr_expr(Env, F, S3),
|
||||||
|
case IsStateful of
|
||||||
|
true -> {tuple_t, _, [ResT|_]} = FunResT;
|
||||||
|
false -> ResT = FunResT
|
||||||
|
end,
|
||||||
|
{tuple_t, ExAnn, [ExprT|_]} = fresh_liquid(Env, "map", RetT),
|
||||||
|
STExprT = {tuple_t, ExAnn, [ExprT, NewStateT, NewBalanceT]},
|
||||||
|
AbstractElem = fresh_id(Ann, "map_list_elem"),
|
||||||
|
AppEnv = bind_var(AbstractElem, ElemT, Env),
|
||||||
|
AppElemT =
|
||||||
|
apply_subst(
|
||||||
|
[ {StateId, State}
|
||||||
|
, {BalanceId, Balance}
|
||||||
|
, {ArgId, AbstractElem}
|
||||||
|
], ResT
|
||||||
|
),
|
||||||
|
{ STExprT
|
||||||
|
, [ {well_formed, constr_id(list_map_wf), Env, STExprT}
|
||||||
|
, {subtype, constr_id(list_map_len_preserve), Ann, AppEnv,
|
||||||
|
{dep_list_t, Ann, LId, AppElemT, LenQual}, ExprT}
|
||||||
|
, {subtype, constr_id(list_map_state), Ann, Env, StateT, StateArgT}
|
||||||
|
, {subtype, constr_id(list_map_balance), Ann, Env, BalanceT, BalanceArgT}
|
||||||
|
| S4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
?CONSTR("List", "flat_map", [State = ?typed_p(_, StateT), Balance = ?typed_p(_, BalanceT), F = ?typed_p(UF), L],
|
||||||
|
[_, _, {fun_t, _, _, [_, _, _], _}, _],
|
||||||
|
begin
|
||||||
|
IsStateful = is_stateful(Env, UF),
|
||||||
|
{_, S1} = constr_expr(Env, State, S0),
|
||||||
|
{_, S2} = constr_expr(Env, Balance, S1),
|
||||||
|
NewStateT = fresh_liquid(Env, "flat_map_state", StateT),
|
||||||
|
NewBalanceT = fresh_liquid(Env, "flat_map_balance", BalanceT),
|
||||||
|
{{dep_list_t, _, LId, ElemT, _}, S3} = constr_expr(Env, L, S2),
|
||||||
|
{QWE = {dep_fun_t, _,
|
||||||
|
[ {dep_arg_t, _, StateId, StateArgT}
|
||||||
|
, {dep_arg_t, _, BalanceId, BalanceArgT}
|
||||||
|
, {dep_arg_t, _, ArgId, ArgT}
|
||||||
|
], FunResT}, S4} = constr_expr(Env, F, S3),
|
||||||
|
case IsStateful of
|
||||||
|
true -> {tuple_t, _, [ResT|_]} = FunResT;
|
||||||
|
false -> ResT = FunResT
|
||||||
|
end,
|
||||||
|
{dep_list_t, _, _, ResElemT, _} = ResT,
|
||||||
|
{tuple_t, ExAnn, [ExprT|_]} = fresh_liquid(Env, "flat_map", RetT),
|
||||||
|
STExprT = {tuple_t, ExAnn, [ExprT, NewStateT, NewBalanceT]},
|
||||||
|
AbstractElem = fresh_id(Ann, "flat_map_list_elem"),
|
||||||
|
AbstractGen = fresh_id(Ann, "flat_map_gen"),
|
||||||
|
ResSubst =
|
||||||
|
[ {StateId, State}
|
||||||
|
, {BalanceId, Balance}
|
||||||
|
, {ArgId, AbstractElem}
|
||||||
|
],
|
||||||
|
AppEnv = bind_vars(
|
||||||
|
[ {AbstractElem, ElemT}
|
||||||
|
, {AbstractGen, apply_subst(ResSubst, ResT)}
|
||||||
|
], Env),
|
||||||
|
AppElemT = apply_subst(ResSubst, ResElemT),
|
||||||
|
{ STExprT
|
||||||
|
, [ {well_formed, constr_id(list_flat_map), Env, STExprT}
|
||||||
|
, {subtype, constr_id(list_flat_map), Ann, AppEnv,
|
||||||
|
{dep_list_t, Ann, LId, AppElemT, [?op(Ann, LId, '>=', ?int(Ann, 0)), ?op(Ann, LId, '=<', ?op(Ann, L, '*', AbstractGen))]},
|
||||||
|
ExprT}
|
||||||
|
, {subtype, constr_id(list_flat_map), Ann, Env, StateT, StateArgT}
|
||||||
|
, {subtype, constr_id(list_flat_map), Ann, Env, BalanceT, BalanceArgT}
|
||||||
|
| S4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
).
|
@ -699,12 +699,15 @@ expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
|
|||||||
stmts_to_fcode(Env, Stmts);
|
stmts_to_fcode(Env, Stmts);
|
||||||
|
|
||||||
%% Binary operator
|
%% Binary operator
|
||||||
expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op == '||' ->
|
expr_to_fcode(Env, _Type, Expr = {app, _, {typed, _, {Op, _}, _}, [_, _]})
|
||||||
|
when Op == '&&'; Op == '||' ->
|
||||||
Tree = expr_to_decision_tree(Env, Expr),
|
Tree = expr_to_decision_tree(Env, Expr),
|
||||||
decision_tree_to_fcode(Tree);
|
decision_tree_to_fcode(Tree);
|
||||||
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
|
expr_to_fcode(Env, _Type, {app, _Ann, {typed, _, {Op, _}, _}, [A, B]})
|
||||||
|
when is_atom(Op) ->
|
||||||
{op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
|
{op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
|
||||||
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
expr_to_fcode(Env, _Type, {app, _Ann, {typed, _, {Op, _}, _}, [A]})
|
||||||
|
when is_atom(Op) ->
|
||||||
case Op of
|
case Op of
|
||||||
'-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]};
|
'-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]};
|
||||||
'!' -> {op, '!', [expr_to_fcode(Env, A)]}
|
'!' -> {op, '!', [expr_to_fcode(Env, A)]}
|
||||||
@ -2211,6 +2214,8 @@ pp_pat(P = {Tag, _}) when Tag == bool; Tag == int; Tag == string
|
|||||||
-> pp_fexpr({lit, P});
|
-> pp_fexpr({lit, P});
|
||||||
pp_pat(Pat) -> pp_fexpr(Pat).
|
pp_pat(Pat) -> pp_fexpr(Pat).
|
||||||
|
|
||||||
|
is_infix({typed, _, Op, _}) ->
|
||||||
|
is_infix(Op);
|
||||||
is_infix(Op) ->
|
is_infix(Op) ->
|
||||||
C = hd(atom_to_list(Op)),
|
C = hd(atom_to_list(Op)),
|
||||||
C < $a orelse C > $z.
|
C < $a orelse C > $z.
|
||||||
|
@ -302,12 +302,12 @@ ast_body({app, _, {'..', _}, [A, B]}, Icode) ->
|
|||||||
ast_body({app, As, Fun, Args}, Icode) ->
|
ast_body({app, As, Fun, Args}, Icode) ->
|
||||||
case aeso_syntax:get_ann(format, As) of
|
case aeso_syntax:get_ann(format, As) of
|
||||||
infix ->
|
infix ->
|
||||||
{Op, _} = Fun,
|
{typed, _, {Op, _}, _} = Fun,
|
||||||
[A, B] = Args,
|
[A, B] = Args,
|
||||||
ast_binop(Op, As, A, B, Icode);
|
ast_binop(Op, As, A, B, Icode);
|
||||||
prefix ->
|
prefix ->
|
||||||
{Op, _} = Fun,
|
{typed, _, {Op, _}, _} = Fun,
|
||||||
[A] = Args,
|
[A] = Args,
|
||||||
#unop{op = Op, rand = ast_body(A, Icode)};
|
#unop{op = Op, rand = ast_body(A, Icode)};
|
||||||
_ ->
|
_ ->
|
||||||
{typed, _, Fun1, {fun_t, _, _, ArgsT, RetT}} = Fun,
|
{typed, _, Fun1, {fun_t, _, _, ArgsT, RetT}} = Fun,
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
|
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
|
||||||
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
|
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
|
||||||
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F})).
|
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F})).
|
||||||
|
-define(RULE(A, B, C, D, E, F, G, Do), map(fun({_1, _2, _3, _4, _5, _6, _7}) -> Do end, {A, B, C, D, E, F, G})).
|
||||||
|
-define(RULE(A, B, C, D, E, F, G, H, Do), map(fun({_1, _2, _3, _4, _5, _6, _7, _8}) -> Do end, {A, B, C, D, E, F, G, H})).
|
||||||
|
|
||||||
-import(aeso_parse_lib,
|
-import(aeso_parse_lib,
|
||||||
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
run_parser/2,
|
run_parser/2,
|
||||||
run_parser/3]).
|
run_parser/3]).
|
||||||
|
|
||||||
|
-include("aeso_ast_refine_types.hrl").
|
||||||
|
|
||||||
-include("aeso_parse_lib.hrl").
|
-include("aeso_parse_lib.hrl").
|
||||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
|
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
|
||||||
|
|
||||||
@ -181,7 +183,14 @@ constructor() -> %% TODO: format for Con() vs Con
|
|||||||
|
|
||||||
con_args() -> paren_list(con_arg()).
|
con_args() -> paren_list(con_arg()).
|
||||||
type_args() -> paren_list(type()).
|
type_args() -> paren_list(type()).
|
||||||
field_type() -> ?RULE(id(), tok(':'), type(), {field_t, get_ann(_1), _1, _3}).
|
field_type() ->
|
||||||
|
?LAZY_P(choice(
|
||||||
|
[ ?RULE(tok('{'), id(), tok(':'), typeRefinable(), tok('|'), comma_sep1(expr()), tok('}'),
|
||||||
|
{field_t, get_ann(_2), _2, {refined_t, get_ann(_4), _2, _4, _6}})
|
||||||
|
, ?RULE(tok('{'), id(), tok(':'), id("list"), parens(type()), tok('|'), comma_sep1(expr()), tok('}'),
|
||||||
|
{field_t, get_ann(_2), _2, {dep_list_t, get_ann(_4), _2, _5, _7}})
|
||||||
|
, ?RULE(id(), tok(':'), type(), {field_t, get_ann(_1), _1, _3})
|
||||||
|
])).
|
||||||
|
|
||||||
con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed, true, _2))).
|
con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed, true, _2))).
|
||||||
|
|
||||||
@ -224,15 +233,41 @@ type300() ->
|
|||||||
|
|
||||||
type400() ->
|
type400() ->
|
||||||
choice(
|
choice(
|
||||||
[?RULE(typeAtom(), optional(type_args()),
|
[?RULE(id("bytes"), parens(token(int)),
|
||||||
case _2 of
|
{bytes_t, get_ann(_1), element(3, _2)}),
|
||||||
none -> _1;
|
%% Refined
|
||||||
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
?RULE(tok('{'), id(), tok(':'), typeRefinable(), tok('|'), comma_sep(expr()), tok('}'),
|
||||||
end),
|
refined_t(get_ann(_1), _2, _4, _6)
|
||||||
?RULE(id("bytes"), parens(token(int)),
|
),
|
||||||
{bytes_t, get_ann(_1), element(3, _2)})
|
%% Refined without pred
|
||||||
|
?RULE(tok('{'), id(), tok(':'), typeRefinable(), tok('}'),
|
||||||
|
refined_t(get_ann(_1), _2, _4, [])
|
||||||
|
),
|
||||||
|
%% Dep record
|
||||||
|
?RULE(tok('{'), type500(), tok('<:'), comma_sep1(field_type()), tok('}'),
|
||||||
|
dep_record_t(get_ann(_1), _2, _4)
|
||||||
|
),
|
||||||
|
%% Dep variant
|
||||||
|
?RULE(tok('{'), type500(), tok('<:'), typedef(variant), tok('}'),
|
||||||
|
dep_variant_t(get_ann(_1), _2, _4)
|
||||||
|
),
|
||||||
|
%% Dep list
|
||||||
|
?RULE(tok('{'), id(), tok(':'), id("list"), parens(type()), tok('|'), comma_sep(expr()), tok('}'),
|
||||||
|
dep_list_t(get_ann(_1), _2, _5, _7)),
|
||||||
|
%% Dep list without pred
|
||||||
|
?RULE(tok('{'), id(), tok(':'), id("list"), parens(type()), tok('}'),
|
||||||
|
dep_list_t(get_ann(_1), _2, _5, [])
|
||||||
|
),
|
||||||
|
?RULE(type500(), _1)
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
type500() ->
|
||||||
|
?RULE(typeAtom(), optional(type_args()),
|
||||||
|
case _2 of
|
||||||
|
none -> _1;
|
||||||
|
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
||||||
|
end).
|
||||||
|
|
||||||
typeAtom() ->
|
typeAtom() ->
|
||||||
?LAZY_P(choice(
|
?LAZY_P(choice(
|
||||||
[ parens(type())
|
[ parens(type())
|
||||||
@ -240,6 +275,9 @@ typeAtom() ->
|
|||||||
, id(), token(con), token(qcon), token(qid), tvar()
|
, id(), token(con), token(qcon), token(qid), tvar()
|
||||||
])).
|
])).
|
||||||
|
|
||||||
|
typeRefinable() ->
|
||||||
|
?LAZY_P(choice([id(), tvar()])).
|
||||||
|
|
||||||
args_t() ->
|
args_t() ->
|
||||||
?LAZY_P(choice(
|
?LAZY_P(choice(
|
||||||
[ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
|
[ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
|
||||||
@ -247,6 +285,7 @@ args_t() ->
|
|||||||
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
|
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
|
||||||
])).
|
])).
|
||||||
|
|
||||||
|
|
||||||
%% -- Statements -------------------------------------------------------------
|
%% -- Statements -------------------------------------------------------------
|
||||||
|
|
||||||
body() ->
|
body() ->
|
||||||
@ -478,6 +517,7 @@ parens(P) -> between(tok('('), P, tok(')')).
|
|||||||
braces(P) -> between(tok('{'), P, tok('}')).
|
braces(P) -> between(tok('{'), P, tok('}')).
|
||||||
brackets(P) -> between(tok('['), P, tok(']')).
|
brackets(P) -> between(tok('['), P, tok(']')).
|
||||||
comma_sep(P) -> sep(P, tok(',')).
|
comma_sep(P) -> sep(P, tok(',')).
|
||||||
|
comma_sep1(P) -> sep1(P, tok(',')).
|
||||||
|
|
||||||
paren_list(P) -> parens(comma_sep(P)).
|
paren_list(P) -> parens(comma_sep(P)).
|
||||||
brace_list(P) -> braces(comma_sep(P)).
|
brace_list(P) -> braces(comma_sep(P)).
|
||||||
@ -557,6 +597,18 @@ else_branches([Else = {else, _, _} | Stmts], Acc) ->
|
|||||||
else_branches(Stmts, Acc) ->
|
else_branches(Stmts, Acc) ->
|
||||||
{lists:reverse(Acc), Stmts}.
|
{lists:reverse(Acc), Stmts}.
|
||||||
|
|
||||||
|
refined_t(Ann, Id, Type, Pred) ->
|
||||||
|
{refined_t, Ann, Id, Type, Pred}.
|
||||||
|
|
||||||
|
dep_record_t(Ann, Base, Fields) ->
|
||||||
|
{dep_record_t, Ann, Base, Fields}.
|
||||||
|
|
||||||
|
dep_variant_t(Ann, Base, {variant_t, Constrs}) ->
|
||||||
|
{dep_variant_t, Ann, ?nu(Ann), Base, undefined, Constrs}.
|
||||||
|
|
||||||
|
dep_list_t(Ann, Id, ElemT, LenPred) ->
|
||||||
|
{dep_list_t, Ann, Id, ElemT, LenPred}.
|
||||||
|
|
||||||
tuple_t(_Ann, [Type]) -> Type; %% Not a tuple
|
tuple_t(_Ann, [Type]) -> Type; %% Not a tuple
|
||||||
tuple_t(Ann, Types) -> {tuple_t, Ann, Types}.
|
tuple_t(Ann, Types) -> {tuple_t, Ann, Types}.
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
-export([decls/1, decls/2, decl/1, decl/2, expr/1, expr/2, type/1, type/2]).
|
-export([decls/1, decls/2, decl/1, decl/2, expr/1, expr/2, type/1, type/2]).
|
||||||
|
|
||||||
|
-export([constr/1, dep_type/1, predicate/1, pp/2]).
|
||||||
|
|
||||||
-export_type([options/0]).
|
-export_type([options/0]).
|
||||||
|
|
||||||
-include("aeso_utils.hrl").
|
-include("aeso_utils.hrl").
|
||||||
@ -207,7 +209,8 @@ name({con, _, Name}) -> text(Name);
|
|||||||
name({qid, _, Names}) -> text(string:join(Names, "."));
|
name({qid, _, Names}) -> text(string:join(Names, "."));
|
||||||
name({qcon, _, Names}) -> text(string:join(Names, "."));
|
name({qcon, _, Names}) -> text(string:join(Names, "."));
|
||||||
name({tvar, _, Name}) -> text(Name);
|
name({tvar, _, Name}) -> text(Name);
|
||||||
name({typed, _, Name, _}) -> name(Name).
|
name({typed, _, Name, _}) -> name(Name);
|
||||||
|
name({ltvar, Name}) -> text(Name).
|
||||||
|
|
||||||
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
|
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
|
||||||
letdecl(Let, {letval, _, P, E}) ->
|
letdecl(Let, {letval, _, P, E}) ->
|
||||||
@ -282,7 +285,198 @@ type(T = {id, _, _}) -> name(T);
|
|||||||
type(T = {qid, _, _}) -> name(T);
|
type(T = {qid, _, _}) -> name(T);
|
||||||
type(T = {con, _, _}) -> name(T);
|
type(T = {con, _, _}) -> name(T);
|
||||||
type(T = {qcon, _, _}) -> name(T);
|
type(T = {qcon, _, _}) -> name(T);
|
||||||
type(T = {tvar, _, _}) -> name(T).
|
type(T = {tvar, _, _}) -> name(T);
|
||||||
|
|
||||||
|
type(T) -> dep_type(T).
|
||||||
|
|
||||||
|
|
||||||
|
dep_type({refined_t, _, Id, BaseType, []}) ->
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ name(Id)
|
||||||
|
, text(":")
|
||||||
|
, type(BaseType)
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]);
|
||||||
|
dep_type({refined_t, _, Id, BaseType, Pred}) ->
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ name(Id)
|
||||||
|
, text(":")
|
||||||
|
, type(BaseType)
|
||||||
|
, text("|")
|
||||||
|
, predicate(Pred)
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]);
|
||||||
|
dep_type({dep_fun_t, _, Args, Ret}) ->
|
||||||
|
follow
|
||||||
|
( hsep
|
||||||
|
( tuple([
|
||||||
|
case DT of
|
||||||
|
{refined_t, _, Id, _, _} when Id == ArgId ->
|
||||||
|
dep_type(DT);
|
||||||
|
{dep_list_t, _, Id, _, _} when Id == ArgId ->
|
||||||
|
dep_type(DT);
|
||||||
|
_ ->
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ name(ArgId)
|
||||||
|
, text(":")
|
||||||
|
, type(DT)
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|| {dep_arg_t, _, ArgId, DT} <- Args])
|
||||||
|
, text("=>")
|
||||||
|
)
|
||||||
|
, type(Ret)
|
||||||
|
);
|
||||||
|
dep_type({dep_record_t, _, Type, Fields}) ->
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ type(Type)
|
||||||
|
, text("<:")
|
||||||
|
, par(punctuate(
|
||||||
|
text(","),
|
||||||
|
[ case FType of
|
||||||
|
{refined_t, _, Id, _, _} when Id == FName ->
|
||||||
|
dep_type(FType);
|
||||||
|
_ -> hsep([name(FName), text(":"), type(FType)])
|
||||||
|
end
|
||||||
|
|| {dep_field_t, _, FName, FType} <- Fields]
|
||||||
|
))
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]);
|
||||||
|
dep_type({dep_variant_t, _, _, Type, Pred, Constrs}) ->
|
||||||
|
PredList = if is_list(Pred) -> Pred;
|
||||||
|
true -> []
|
||||||
|
end,
|
||||||
|
IsTags =
|
||||||
|
[ case HEAD of
|
||||||
|
con -> Tag;
|
||||||
|
qcon -> lists:last(Tag)
|
||||||
|
end
|
||||||
|
|| {is_tag, _, _, {HEAD, _, Tag}, _, _} <- PredList],
|
||||||
|
NotIsTags =
|
||||||
|
[ case HEAD of
|
||||||
|
con -> Tag;
|
||||||
|
qcon -> lists:last(Tag)
|
||||||
|
end
|
||||||
|
|| {app, _, {'!', _}, [{is_tag, _, _, {HEAD, _, Tag}, _, _}]} <- PredList],
|
||||||
|
Constrs1 =
|
||||||
|
case IsTags of
|
||||||
|
[] -> [ Con
|
||||||
|
|| Con = {constr_t, _, {con, _, CName}, _} <- Constrs,
|
||||||
|
not lists:member(CName, NotIsTags)
|
||||||
|
];
|
||||||
|
_ ->
|
||||||
|
[ Con
|
||||||
|
|| Con = {constr_t, _, {con, _, CName}, _} <- Constrs,
|
||||||
|
lists:member(CName, IsTags)
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ type(Type)
|
||||||
|
, text("<:")
|
||||||
|
, if is_list(Pred) -> prettypr:empty();
|
||||||
|
true -> predicate(Pred)
|
||||||
|
end
|
||||||
|
, par(punctuate(text(" |"), lists:map(fun constructor_t/1, Constrs1)))
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]);
|
||||||
|
dep_type({dep_list_t, _, Id, Elem, []}) ->
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ name(Id)
|
||||||
|
, text(":")
|
||||||
|
, type({app_t, [], {id, [], "list"}, [Elem]})
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]);
|
||||||
|
dep_type({dep_list_t, _, Id, Elem, LenPred}) ->
|
||||||
|
beside(
|
||||||
|
[ text("{")
|
||||||
|
, hsep(
|
||||||
|
[ name(Id)
|
||||||
|
, text(":")
|
||||||
|
, type({app_t, [], {id, [], "list"}, [Elem]})
|
||||||
|
, text("|")
|
||||||
|
, predicate(LenPred)
|
||||||
|
])
|
||||||
|
, text("}")
|
||||||
|
]
|
||||||
|
);
|
||||||
|
dep_type(T = {tvar, _, _}) ->
|
||||||
|
name(T).
|
||||||
|
|
||||||
|
subst(Subst) ->
|
||||||
|
beside(
|
||||||
|
[ text("[")
|
||||||
|
, hsep(
|
||||||
|
[ par(punctuate(
|
||||||
|
text(";"),
|
||||||
|
[ beside([expr(V), text("/"), expr(Q)])
|
||||||
|
|| {V, Q} <- Subst
|
||||||
|
]))
|
||||||
|
])
|
||||||
|
, text("]")
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
predicate({template, [], {ltvar, Var}}) -> text(Var);
|
||||||
|
predicate({template, Subst, {ltvar, Var}}) ->
|
||||||
|
beside(subst(Subst), text(Var));
|
||||||
|
predicate([]) -> text("true");
|
||||||
|
predicate(L) when is_list(L) ->
|
||||||
|
par(punctuate(text(" &&"), [expr(E) || E <- L]));
|
||||||
|
predicate(Constraints) when is_list(Constraints) ->
|
||||||
|
par(punctuate(text(","), [expr(C) || C <- Constraints])).
|
||||||
|
|
||||||
|
constr_env(Env) ->
|
||||||
|
above(
|
||||||
|
[ par(punctuate(
|
||||||
|
text(","),
|
||||||
|
[beside([expr(Var), text(" : "), type(T)])
|
||||||
|
|| {Var, {_, T}} <- aeso_ast_refine_types:type_binds(Env)])
|
||||||
|
)
|
||||||
|
, predicate(aeso_ast_refine_types:path_pred(Env))
|
||||||
|
]).
|
||||||
|
|
||||||
|
under_constr_env(Env, X) ->
|
||||||
|
above([ constr_env(Env)
|
||||||
|
, text("--------------")
|
||||||
|
, X
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
constr({well_formed, _, Env, T}) ->
|
||||||
|
under_constr_env(Env, type(T));
|
||||||
|
constr({subtype, Ref, _, Env, T1, T2}) ->
|
||||||
|
under_constr_env(
|
||||||
|
Env,
|
||||||
|
beside([ text(io_lib:format("~p\t", [Ref]))
|
||||||
|
, type(T1)
|
||||||
|
, text(" <: ")
|
||||||
|
, type(T2)
|
||||||
|
]));
|
||||||
|
constr({unreachable, _, _, Env}) ->
|
||||||
|
under_constr_env(Env, text("false"));
|
||||||
|
constr({reachable, _, _, Env}) ->
|
||||||
|
above(text("SAT"), constr_env(Env)).
|
||||||
|
|
||||||
|
|
||||||
-spec args_type([aeso_syntax:type()]) -> doc().
|
-spec args_type([aeso_syntax:type()]) -> doc().
|
||||||
args_type(Args) ->
|
args_type(Args) ->
|
||||||
@ -346,6 +540,8 @@ expr_p(P, {assign, _, LV, E}) ->
|
|||||||
%% -- Operators
|
%% -- Operators
|
||||||
expr_p(_, {app, _, {'..', _}, [A, B]}) ->
|
expr_p(_, {app, _, {'..', _}, [A, B]}) ->
|
||||||
list([infix(0, '..', A, B)]);
|
list([infix(0, '..', A, B)]);
|
||||||
|
expr_p(P, {app, As, {typed, _, {Op, OpAs}, _}, Args}) when is_atom(Op) ->
|
||||||
|
expr_p(P, {app, As, {Op, OpAs}, Args});
|
||||||
expr_p(P, E = {app, _, F = {Op, _}, Args}) when is_atom(Op) ->
|
expr_p(P, E = {app, _, F = {Op, _}, Args}) when is_atom(Op) ->
|
||||||
case {aeso_syntax:get_ann(format, E), Args} of
|
case {aeso_syntax:get_ann(format, E), Args} of
|
||||||
{infix, [A, B]} -> infix(P, Op, A, B);
|
{infix, [A, B]} -> infix(P, Op, A, B);
|
||||||
@ -398,7 +594,13 @@ expr_p(_, E = {qcon, _, _}) -> name(E);
|
|||||||
%% -- For error messages
|
%% -- For error messages
|
||||||
expr_p(_, {Op, _}) when is_atom(Op) ->
|
expr_p(_, {Op, _}) when is_atom(Op) ->
|
||||||
paren(text(atom_to_list(Op)));
|
paren(text(atom_to_list(Op)));
|
||||||
expr_p(_, {lvalue, _, LV}) -> lvalue(LV).
|
expr_p(_, {lvalue, _, LV}) -> lvalue(LV);
|
||||||
|
expr_p(P, {is_tag, _, What, Con, Args, _}) ->
|
||||||
|
beside(
|
||||||
|
[ expr_p(P, What), text("==")
|
||||||
|
, app(P, Con, [{id, [], "_"} || _ <- Args])
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
stmt_p({'if', _, Cond, Then}) ->
|
stmt_p({'if', _, Cond, Then}) ->
|
||||||
block_expr(200, beside(text("if"), paren(expr(Cond))), Then);
|
block_expr(200, beside(text("if"), paren(expr(Cond))), Then);
|
||||||
@ -504,3 +706,5 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) ->
|
|||||||
end;
|
end;
|
||||||
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}.
|
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}.
|
||||||
|
|
||||||
|
pp(PP, X) ->
|
||||||
|
prettypr:format(apply(aeso_pretty, PP, [X])).
|
||||||
|
94
src/aeso_smt.erl
Normal file
94
src/aeso_smt.erl
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
-module(aeso_smt).
|
||||||
|
|
||||||
|
-compile([export_all]).
|
||||||
|
|
||||||
|
-type formula() :: {var, string()}
|
||||||
|
| {param, string()}
|
||||||
|
| {int, integer()}
|
||||||
|
| {list, [formula()]}
|
||||||
|
| {app, string(), [formula()]}
|
||||||
|
.
|
||||||
|
|
||||||
|
-define(TIMEOUT, 10000).
|
||||||
|
|
||||||
|
start_z3() ->
|
||||||
|
PortOpts = [exit_status, {line, 100000}],
|
||||||
|
Port = open_port({spawn, "z3 -in -t:" ++ integer_to_list(?TIMEOUT)}, PortOpts),
|
||||||
|
persistent_term:put(z3_connection, Port),
|
||||||
|
send_z3_success({app, "set-option", [{param, "print-success"}, {var, "true"}]}),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
stop_z3() ->
|
||||||
|
port_close(persistent_term:get(z3_connection)),
|
||||||
|
persistent_term:erase(z3_connection).
|
||||||
|
|
||||||
|
get_z3() ->
|
||||||
|
Z3 = persistent_term:get(z3_connection, undefined),
|
||||||
|
if Z3 =:= undefined -> throw(z3_disconnected);
|
||||||
|
true -> ok
|
||||||
|
end,
|
||||||
|
Z3.
|
||||||
|
|
||||||
|
send_z3(Query) ->
|
||||||
|
Z3 = get_z3(),
|
||||||
|
QueryStr = pp_formula(Query),
|
||||||
|
%% io:format("~s\n", [QueryStr]),
|
||||||
|
port_command(Z3, binary:list_to_bin(QueryStr ++ "\n")).
|
||||||
|
|
||||||
|
check_sat() ->
|
||||||
|
send_z3({app, "check-sat", []}),
|
||||||
|
receive
|
||||||
|
{_, {data, {eol, Resp}}} ->
|
||||||
|
%% io:format("Z3: " ++ Resp ++ "\n"),
|
||||||
|
case string:trim(Resp) of
|
||||||
|
"sat" -> true;
|
||||||
|
"unsat" -> false;
|
||||||
|
X -> throw({smt_error, X})
|
||||||
|
end
|
||||||
|
after ?TIMEOUT * 2 -> {error, timeout}
|
||||||
|
end.
|
||||||
|
|
||||||
|
send_z3_success(Query) ->
|
||||||
|
send_z3(Query),
|
||||||
|
receive
|
||||||
|
{_, {data, {eol, Resp}}} ->
|
||||||
|
%% io:format("Z3: " ++ Resp ++ "\n"),
|
||||||
|
case string:trim(Resp) of
|
||||||
|
"success" -> success;
|
||||||
|
X -> error({smt_error, X})
|
||||||
|
end
|
||||||
|
after 5000 -> {error, timeout}
|
||||||
|
end.
|
||||||
|
|
||||||
|
assert(Form) ->
|
||||||
|
send_z3_success({app, "assert", [Form]}).
|
||||||
|
|
||||||
|
declare_const(Var, Type) ->
|
||||||
|
send_z3_success({app, "declare-const", [Var, Type]}).
|
||||||
|
|
||||||
|
push() ->
|
||||||
|
send_z3_success({app, "push", []}).
|
||||||
|
pop() ->
|
||||||
|
send_z3_success({app, "pop", []}).
|
||||||
|
scoped(Fun) ->
|
||||||
|
push(),
|
||||||
|
R = Fun(),
|
||||||
|
pop(),
|
||||||
|
R.
|
||||||
|
|
||||||
|
|
||||||
|
pp_formula({var, Name}) -> Name;
|
||||||
|
pp_formula({param, Name}) -> [$:, Name];
|
||||||
|
pp_formula({int, I}) -> integer_to_list(I);
|
||||||
|
pp_formula({app, Fun, Args}) ->
|
||||||
|
io_lib:format("(~s)", [pp_formulae([{var, Fun}|Args])]);
|
||||||
|
pp_formula({list, Xs}) ->
|
||||||
|
io_lib:format("(~s)", [pp_formulae(Xs)]).
|
||||||
|
|
||||||
|
|
||||||
|
pp_formulae([]) ->
|
||||||
|
"";
|
||||||
|
pp_formulae([H]) ->
|
||||||
|
pp_formula(H);
|
||||||
|
pp_formulae([H|T]) ->
|
||||||
|
io_lib:format("~s ~s", [pp_formula(H), pp_formulae(T)]).
|
@ -16,7 +16,9 @@
|
|||||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
|
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
|
||||||
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
||||||
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
||||||
|
-export_type([letfun/0, letval/0, fundecl/0]).
|
||||||
-export_type([ast/0]).
|
-export_type([ast/0]).
|
||||||
|
-export_type([predicate/0, liquid_type/0, dep_type/1, dep_arg_t/1]).
|
||||||
|
|
||||||
-type ast() :: [decl()].
|
-type ast() :: [decl()].
|
||||||
|
|
||||||
@ -72,15 +74,37 @@
|
|||||||
|
|
||||||
-type constructor_t() :: {constr_t, ann(), con(), [type()]}.
|
-type constructor_t() :: {constr_t, ann(), con(), [type()]}.
|
||||||
|
|
||||||
|
|
||||||
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
||||||
| {app_t, ann(), type(), [type()]}
|
| {app_t, ann(), type(), [type()]}
|
||||||
| {tuple_t, ann(), [type()]}
|
| {tuple_t, ann(), [type()]}
|
||||||
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages
|
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages
|
||||||
| {bytes_t, ann(), integer() | any}
|
| {bytes_t, ann(), integer() | any}
|
||||||
|
| {named_t, ann(), id(), type()}
|
||||||
| id() | qid()
|
| id() | qid()
|
||||||
| con() | qcon() %% contracts
|
| con() | qcon() %% contracts
|
||||||
| tvar().
|
| tvar().
|
||||||
|
|
||||||
|
%% Predicate for a liquid type
|
||||||
|
-type predicate() :: [expr()].
|
||||||
|
|
||||||
|
%% Dependent type
|
||||||
|
%% FIXME it is very inconsistent with the reality...
|
||||||
|
-type dep_type(Qual)
|
||||||
|
:: {refined_t, ann(), id(), type(), Qual}
|
||||||
|
| {dep_fun_t, ann(), [dep_arg_t(Qual)], dep_type(Qual)}
|
||||||
|
| {dep_record_t, ann(), type(), [dep_field_t(Qual)]}
|
||||||
|
| {dep_variant_t, ann(), id(), type(), Qual | undefined, [dep_constr_t(Qual)]}
|
||||||
|
| {dep_list_t, ann(), id(), dep_type(Qual), Qual}
|
||||||
|
| tvar().
|
||||||
|
-type liquid_type() :: dep_type(predicate()).
|
||||||
|
|
||||||
|
-type dep_constr_t(Qual) :: {constr_t, ann(), con(), [dep_type(Qual)]}.
|
||||||
|
|
||||||
|
-type dep_arg_t(Qual) :: {dep_arg_t, ann(), id(), dep_type(Qual)}.
|
||||||
|
|
||||||
|
-type dep_field_t(Qual) :: {field_t, ann(), id(), dep_type(Qual)}.
|
||||||
|
|
||||||
-type named_arg_t() :: {named_arg_t, ann(), id(), type(), expr()}.
|
-type named_arg_t() :: {named_arg_t, ann(), id(), type(), expr()}.
|
||||||
|
|
||||||
-type constant()
|
-type constant()
|
||||||
|
@ -153,4 +153,3 @@ used(D) ->
|
|||||||
(_, _) -> #{}
|
(_, _) -> #{}
|
||||||
end, decl, D)),
|
end, decl, D)),
|
||||||
lists:filter(NotBound, Xs).
|
lists:filter(NotBound, Xs).
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ check_errors(Expect, ErrorString) ->
|
|||||||
%% compilable_contracts() -> [ContractName].
|
%% compilable_contracts() -> [ContractName].
|
||||||
%% The currently compilable contracts.
|
%% The currently compilable contracts.
|
||||||
|
|
||||||
|
compilable_contracts() -> [];
|
||||||
compilable_contracts() ->
|
compilable_contracts() ->
|
||||||
[
|
[
|
||||||
{"identity", "init", []},
|
{"identity", "init", []},
|
||||||
|
@ -23,6 +23,7 @@ run_test(Test) ->
|
|||||||
%% Very simply test compile the given contracts. Only basic checks
|
%% Very simply test compile the given contracts. Only basic checks
|
||||||
%% are made on the output, just that it is a binary which indicates
|
%% are made on the output, just that it is a binary which indicates
|
||||||
%% that the compilation worked.
|
%% that the compilation worked.
|
||||||
|
simple_compile_test_() -> [];
|
||||||
simple_compile_test_() ->
|
simple_compile_test_() ->
|
||||||
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
|
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
|
||||||
fun() ->
|
fun() ->
|
||||||
@ -141,7 +142,7 @@ compile(Backend, Name, Options) ->
|
|||||||
|
|
||||||
%% compilable_contracts() -> [ContractName].
|
%% compilable_contracts() -> [ContractName].
|
||||||
%% The currently compilable contracts.
|
%% The currently compilable contracts.
|
||||||
|
compilable_contracts() -> ["hagia"];
|
||||||
compilable_contracts() ->
|
compilable_contracts() ->
|
||||||
["complex_types",
|
["complex_types",
|
||||||
"counter",
|
"counter",
|
||||||
@ -199,7 +200,6 @@ compilable_contracts() ->
|
|||||||
"clone",
|
"clone",
|
||||||
"clone_simple",
|
"clone_simple",
|
||||||
"create",
|
"create",
|
||||||
"child_contract_init_bug",
|
|
||||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
"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(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
|
||||||
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
|
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
|
||||||
|
failing_contracts() -> [];
|
||||||
failing_contracts() ->
|
failing_contracts() ->
|
||||||
{ok, V} = aeso_compiler:numeric_version(),
|
{ok, V} = aeso_compiler:numeric_version(),
|
||||||
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
|
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
|
||||||
@ -910,6 +910,7 @@ validation_test_() ->
|
|||||||
?assertEqual(ok, validate(C, C))
|
?assertEqual(ok, validate(C, C))
|
||||||
end} || C <- compilable_contracts()].
|
end} || C <- compilable_contracts()].
|
||||||
|
|
||||||
|
validation_fails() -> [];
|
||||||
validation_fails() ->
|
validation_fails() ->
|
||||||
[{"deadcode", "nodeadcode",
|
[{"deadcode", "nodeadcode",
|
||||||
[<<"Data error:\n"
|
[<<"Data error:\n"
|
||||||
|
171
test/aeso_type_refinement_tests.erl
Normal file
171
test/aeso_type_refinement_tests.erl
Normal 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
test/contracts/hagia.aes
Normal file
14
test/contracts/hagia.aes
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
|
contract C =
|
||||||
|
stateful entrypoint f(a, x:int) =
|
||||||
|
if(a < x) a else x
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
8
test/contracts/hagia/args.aes
Normal file
8
test/contracts/hagia/args.aes
Normal 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
test/contracts/hagia/balance.aes
Normal file
9
test/contracts/hagia/balance.aes
Normal 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
test/contracts/hagia/failing.aes
Normal file
23
test/contracts/hagia/failing.aes
Normal 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
test/contracts/hagia/len.aes
Normal file
5
test/contracts/hagia/len.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
contract C =
|
||||||
|
entrypoint
|
||||||
|
len : {l : list('a)} => {r : int | r == l}
|
||||||
|
len([]) = 0
|
||||||
|
len(_::t) = len(t)
|
6
test/contracts/hagia/max.aes
Normal file
6
test/contracts/hagia/max.aes
Normal 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
test/contracts/hagia/require.aes
Normal file
9
test/contracts/hagia/require.aes
Normal 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
test/contracts/hagia/simple.aes
Normal file
15
test/contracts/hagia/simple.aes
Normal 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
test/contracts/hagia/state.aes
Normal file
7
test/contracts/hagia/state.aes
Normal 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
test/contracts/hagia/switch.aes
Normal file
12
test/contracts/hagia/switch.aes
Normal 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
test/contracts/hagia/test.aes
Normal file
8
test/contracts/hagia/test.aes
Normal 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
test/contracts/hagia/types.aes
Normal file
50
test/contracts/hagia/types.aes
Normal 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
|
||||||
|
*/
|
Loading…
x
Reference in New Issue
Block a user