Merge pull request #241 from aeternity/GH-203-protected-calls
Add support for protected contract calls
This commit is contained in:
commit
42cd47d1b3
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
|
|
||||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"5ef5d45"}}}
|
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"7f0d309"}}}
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {eblake2, "1.0.0"}
|
, {eblake2, "1.0.0"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{"1.1.0",
|
{"1.1.0",
|
||||||
[{<<"aebytecode">>,
|
[{<<"aebytecode">>,
|
||||||
{git,"https://github.com/aeternity/aebytecode.git",
|
{git,"https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"5ef5d455b4255518aa0f5c91c626e9fb0deb980f"}},
|
{ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}},
|
||||||
0},
|
0},
|
||||||
{<<"aeserialization">>,
|
{<<"aeserialization">>,
|
||||||
{git,"https://github.com/aeternity/aeserialization.git",
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
| aeso_syntax:id() | aeso_syntax:qid()
|
| aeso_syntax:id() | aeso_syntax:qid()
|
||||||
| aeso_syntax:con() | aeso_syntax:qcon() %% contracts
|
| aeso_syntax:con() | aeso_syntax:qcon() %% contracts
|
||||||
| aeso_syntax:tvar()
|
| aeso_syntax:tvar()
|
||||||
|
| {if_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), utype()} %% Can branch on named argument (protected)
|
||||||
| uvar().
|
| uvar().
|
||||||
|
|
||||||
-type uvar() :: {uvar, aeso_syntax:ann(), reference()}.
|
-type uvar() :: {uvar, aeso_syntax:ann(), reference()}.
|
||||||
@ -43,7 +44,14 @@
|
|||||||
name :: aeso_syntax:id(),
|
name :: aeso_syntax:id(),
|
||||||
type :: utype()}).
|
type :: utype()}).
|
||||||
|
|
||||||
-type named_argument_constraint() :: #named_argument_constraint{}.
|
-record(dependent_type_constraint,
|
||||||
|
{ named_args_t :: named_args_t()
|
||||||
|
, named_args :: [aeso_syntax:arg_expr()]
|
||||||
|
, general_type :: utype()
|
||||||
|
, specialized_type :: utype()
|
||||||
|
, context :: term() }).
|
||||||
|
|
||||||
|
-type named_argument_constraint() :: #named_argument_constraint{} | #dependent_type_constraint{}.
|
||||||
|
|
||||||
-record(field_constraint,
|
-record(field_constraint,
|
||||||
{ record_t :: utype()
|
{ record_t :: utype()
|
||||||
@ -232,16 +240,21 @@ bind_fields([], Env) -> Env;
|
|||||||
bind_fields([{Id, Info} | Rest], Env) ->
|
bind_fields([{Id, Info} | Rest], Env) ->
|
||||||
bind_fields(Rest, bind_field(Id, Info, Env)).
|
bind_fields(Rest, bind_field(Id, Info, Env)).
|
||||||
|
|
||||||
%% Contract entrypoints take two named arguments (gas : int = Call.gas_left(), value : int = 0).
|
%% Contract entrypoints take three named arguments
|
||||||
|
%% gas : int = Call.gas_left()
|
||||||
|
%% value : int = 0
|
||||||
|
%% protected : bool = false
|
||||||
contract_call_type({fun_t, Ann, [], Args, Ret}) ->
|
contract_call_type({fun_t, Ann, [], Args, Ret}) ->
|
||||||
Id = fun(X) -> {id, Ann, X} end,
|
Id = fun(X) -> {id, Ann, X} end,
|
||||||
Int = Id("int"),
|
Int = Id("int"),
|
||||||
Typed = fun(E, T) -> {typed, Ann, E, T} end,
|
Typed = fun(E, T) -> {typed, Ann, E, T} end,
|
||||||
Named = fun(Name, Default) -> {named_arg_t, Ann, Id(Name), Int, Default} end,
|
Named = fun(Name, Default = {typed, _, _, T}) -> {named_arg_t, Ann, Id(Name), T, Default} end,
|
||||||
{fun_t, Ann, [Named("gas", Typed({app, Ann, Typed({qid, Ann, ["Call", "gas_left"]},
|
{fun_t, Ann, [Named("gas", Typed({app, Ann, Typed({qid, Ann, ["Call", "gas_left"]},
|
||||||
{fun_t, Ann, [], [], Int}),
|
{fun_t, Ann, [], [], Int}),
|
||||||
[]}, Int)),
|
[]}, Int)),
|
||||||
Named("value", Typed({int, Ann, 0}, Int))], Args, Ret}.
|
Named("value", Typed({int, Ann, 0}, Int)),
|
||||||
|
Named("protected", Typed({bool, Ann, false}, Id("bool")))],
|
||||||
|
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
|
||||||
|
|
||||||
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
|
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
|
||||||
bind_contract({contract, Ann, Id, Contents}, Env) ->
|
bind_contract({contract, Ann, Id, Contents}, Env) ->
|
||||||
@ -1359,7 +1372,7 @@ infer_expr(Env, {typed, As, Body, Type}) ->
|
|||||||
Type1 = check_type(Env, Type),
|
Type1 = check_type(Env, Type),
|
||||||
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
|
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
|
||||||
{typed, As, NewBody, NewType};
|
{typed, As, NewBody, NewType};
|
||||||
infer_expr(Env, {app, Ann, Fun, Args0}) ->
|
infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||||
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
||||||
Args = Args0 -- NamedArgs,
|
Args = Args0 -- NamedArgs,
|
||||||
case aeso_syntax:get_ann(format, Ann) of
|
case aeso_syntax:get_ann(format, Ann) of
|
||||||
@ -1374,8 +1387,16 @@ infer_expr(Env, {app, Ann, Fun, Args0}) ->
|
|||||||
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
|
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
|
||||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||||
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
||||||
|
GeneralResultType = fresh_uvar(Ann),
|
||||||
ResultType = fresh_uvar(Ann),
|
ResultType = fresh_uvar(Ann),
|
||||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, ResultType}, {infer_app, Fun, Args, FunType, ArgTypes}),
|
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||||
|
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||||
|
add_named_argument_constraint(
|
||||||
|
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||||
|
named_args = NamedArgs1,
|
||||||
|
general_type = GeneralResultType,
|
||||||
|
specialized_type = ResultType,
|
||||||
|
context = {check_return, App} }),
|
||||||
{typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
{typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
||||||
end;
|
end;
|
||||||
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
||||||
@ -1534,7 +1555,7 @@ 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, [], Args, Inferred, ArgTypes}),
|
||||||
{typed, As, {app, As, Op, TypedArgs}, ResultType}.
|
{typed, As, {app, As, Op, TypedArgs}, ResultType}.
|
||||||
|
|
||||||
infer_pattern(Env, Pattern) ->
|
infer_pattern(Env, Pattern) ->
|
||||||
@ -1705,12 +1726,12 @@ create_constraints() ->
|
|||||||
create_field_constraints().
|
create_field_constraints().
|
||||||
|
|
||||||
destroy_and_report_unsolved_constraints(Env) ->
|
destroy_and_report_unsolved_constraints(Env) ->
|
||||||
|
solve_field_constraints(Env),
|
||||||
solve_named_argument_constraints(Env),
|
solve_named_argument_constraints(Env),
|
||||||
solve_bytes_constraints(Env),
|
solve_bytes_constraints(Env),
|
||||||
solve_field_constraints(Env),
|
|
||||||
destroy_and_report_unsolved_field_constraints(Env),
|
|
||||||
destroy_and_report_unsolved_bytes_constraints(Env),
|
destroy_and_report_unsolved_bytes_constraints(Env),
|
||||||
destroy_and_report_unsolved_named_argument_constraints(Env).
|
destroy_and_report_unsolved_named_argument_constraints(Env),
|
||||||
|
destroy_and_report_unsolved_field_constraints(Env).
|
||||||
|
|
||||||
%% -- Named argument constraints --
|
%% -- Named argument constraints --
|
||||||
|
|
||||||
@ -1750,8 +1771,43 @@ check_named_argument_constraint(Env,
|
|||||||
type_error({bad_named_argument, Args, Id}),
|
type_error({bad_named_argument, Args, Id}),
|
||||||
false;
|
false;
|
||||||
[T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true
|
[T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true
|
||||||
|
end;
|
||||||
|
check_named_argument_constraint(Env,
|
||||||
|
#dependent_type_constraint{ named_args_t = NamedArgsT0,
|
||||||
|
named_args = NamedArgs,
|
||||||
|
general_type = GenType,
|
||||||
|
specialized_type = SpecType,
|
||||||
|
context = {check_return, App} }) ->
|
||||||
|
NamedArgsT = dereference(NamedArgsT0),
|
||||||
|
case dereference(NamedArgsT0) of
|
||||||
|
[_ | _] = NamedArgsT ->
|
||||||
|
GetVal = fun(Name, Default) ->
|
||||||
|
hd([ Val || {named_arg, _, {id, _, N}, Val} <- NamedArgs, N == Name] ++
|
||||||
|
[ Default ])
|
||||||
|
end,
|
||||||
|
ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)}
|
||||||
|
|| {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]),
|
||||||
|
GenType1 = specialize_dependent_type(ArgEnv, GenType),
|
||||||
|
unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}),
|
||||||
|
true;
|
||||||
|
_ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
specialize_dependent_type(Env, Type) ->
|
||||||
|
case dereference(Type) of
|
||||||
|
{if_t, _, {id, _, Arg}, Then, Else} ->
|
||||||
|
Val = maps:get(Arg, Env),
|
||||||
|
case Val of
|
||||||
|
{typed, _, {bool, _, true}, _} -> Then;
|
||||||
|
{typed, _, {bool, _, false}, _} -> Else;
|
||||||
|
_ ->
|
||||||
|
type_error({named_argument_must_be_literal_bool, Arg, Val}),
|
||||||
|
fresh_uvar(aeso_syntax:get_ann(Val))
|
||||||
|
end;
|
||||||
|
_ -> Type %% Currently no deep dependent types
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
destroy_and_report_unsolved_named_argument_constraints(Env) ->
|
destroy_and_report_unsolved_named_argument_constraints(Env) ->
|
||||||
Unsolved = solve_named_argument_constraints(Env, get_named_argument_constraints()),
|
Unsolved = solve_named_argument_constraints(Env, get_named_argument_constraints()),
|
||||||
[ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ],
|
[ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ],
|
||||||
@ -1977,9 +2033,11 @@ destroy_and_report_unsolved_field_constraints(Env) ->
|
|||||||
{FieldCs, OtherCs} =
|
{FieldCs, OtherCs} =
|
||||||
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
||||||
get_field_constraints()),
|
get_field_constraints()),
|
||||||
{CreateCs, ContractCs} =
|
{CreateCs, OtherCs1} =
|
||||||
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
|
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
|
||||||
OtherCs),
|
OtherCs),
|
||||||
|
{ContractCs, []} =
|
||||||
|
lists:partition(fun(#is_contract_constraint{}) -> true; (_) -> false end, OtherCs1),
|
||||||
Unknown = solve_known_record_types(Env, FieldCs),
|
Unknown = solve_known_record_types(Env, FieldCs),
|
||||||
if Unknown == [] -> ok;
|
if Unknown == [] -> ok;
|
||||||
true ->
|
true ->
|
||||||
@ -2053,7 +2111,8 @@ unfold_record_types(Env, T) ->
|
|||||||
unfold_types(Env, T, [unfold_record_types]).
|
unfold_types(Env, T, [unfold_record_types]).
|
||||||
|
|
||||||
unfold_types(Env, {typed, Attr, E, Type}, Options) ->
|
unfold_types(Env, {typed, Attr, E, Type}, Options) ->
|
||||||
{typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options)};
|
Options1 = [{ann, Attr} | lists:keydelete(ann, 1, Options)],
|
||||||
|
{typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options1)};
|
||||||
unfold_types(Env, {arg, Attr, Id, Type}, Options) ->
|
unfold_types(Env, {arg, Attr, Id, Type}, Options) ->
|
||||||
{arg, Attr, Id, unfold_types_in_type(Env, Type, Options)};
|
{arg, Attr, Id, unfold_types_in_type(Env, Type, Options)};
|
||||||
unfold_types(Env, {type_sig, Ann, Constr, NamedArgs, Args, Ret}, Options) ->
|
unfold_types(Env, {type_sig, Ann, Constr, NamedArgs, Args, Ret}, Options) ->
|
||||||
@ -2079,7 +2138,8 @@ unfold_types_in_type(Env, T) ->
|
|||||||
|
|
||||||
unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]}, Options) ->
|
unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]}, Options) ->
|
||||||
Args1 = [KeyType, _] = unfold_types_in_type(Env, Args, Options),
|
Args1 = [KeyType, _] = unfold_types_in_type(Env, Args, Options),
|
||||||
[ type_error({map_in_map_key, KeyType0}) || has_maps(KeyType) ],
|
Ann1 = proplists:get_value(ann, Options, aeso_syntax:get_ann(KeyType0)),
|
||||||
|
[ type_error({map_in_map_key, Ann1, KeyType0}) || has_maps(KeyType) ],
|
||||||
{app_t, Ann, Id, Args1};
|
{app_t, Ann, Id, Args1};
|
||||||
unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) ->
|
unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) ->
|
||||||
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
|
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
|
||||||
@ -2153,8 +2213,13 @@ subst_tvars1(_Env, X) ->
|
|||||||
unify(_, {id, _, "_"}, _, _When) -> true;
|
unify(_, {id, _, "_"}, _, _When) -> true;
|
||||||
unify(_, _, {id, _, "_"}, _When) -> true;
|
unify(_, _, {id, _, "_"}, _When) -> true;
|
||||||
unify(Env, A, B, When) ->
|
unify(Env, A, B, When) ->
|
||||||
A1 = dereference(unfold_types_in_type(Env, A)),
|
Options =
|
||||||
B1 = dereference(unfold_types_in_type(Env, B)),
|
case When of %% Improve source location for map_in_map_key errors
|
||||||
|
{check_expr, E, _, _} -> [{ann, aeso_syntax:get_ann(E)}];
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
A1 = dereference(unfold_types_in_type(Env, A, Options)),
|
||||||
|
B1 = dereference(unfold_types_in_type(Env, B, Options)),
|
||||||
unify1(Env, A1, B1, When).
|
unify1(Env, A1, B1, When).
|
||||||
|
|
||||||
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _When) ->
|
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _When) ->
|
||||||
@ -2185,6 +2250,9 @@ unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
|
|||||||
true;
|
true;
|
||||||
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
||||||
true;
|
true;
|
||||||
|
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) ->
|
||||||
|
unify(Env, Then1, Then2, When) andalso
|
||||||
|
unify(Env, Else1, Else2, When);
|
||||||
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
||||||
unify(Env, Named1, Named2, When) andalso
|
unify(Env, Named1, Named2, When) andalso
|
||||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||||
@ -2245,6 +2313,8 @@ occurs_check1(R, {record_t, Fields}) ->
|
|||||||
occurs_check(R, Fields);
|
occurs_check(R, Fields);
|
||||||
occurs_check1(R, {field_t, _, _, T}) ->
|
occurs_check1(R, {field_t, _, _, T}) ->
|
||||||
occurs_check(R, T);
|
occurs_check(R, T);
|
||||||
|
occurs_check1(R, {if_t, _, _, 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(_, []) -> false.
|
occurs_check1(_, []) -> false.
|
||||||
@ -2588,10 +2658,10 @@ mk_error({new_tuple_syntax, Ann, Ts}) ->
|
|||||||
Msg = io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n",
|
Msg = io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n",
|
||||||
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]),
|
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]),
|
||||||
mk_t_err(pos(Ann), Msg);
|
mk_t_err(pos(Ann), Msg);
|
||||||
mk_error({map_in_map_key, KeyType}) ->
|
mk_error({map_in_map_key, Ann, KeyType}) ->
|
||||||
Msg = io_lib:format("Invalid key type\n~s\n", [pp_type(" ", KeyType)]),
|
Msg = io_lib:format("Invalid key type\n~s\n", [pp_type(" ", KeyType)]),
|
||||||
Cxt = "Map keys cannot contain other maps.\n",
|
Cxt = "Map keys cannot contain other maps.\n",
|
||||||
mk_t_err(pos(KeyType), Msg, Cxt);
|
mk_t_err(pos(Ann), Msg, Cxt);
|
||||||
mk_error({cannot_call_init_function, Ann}) ->
|
mk_error({cannot_call_init_function, Ann}) ->
|
||||||
Msg = "The 'init' function is called exclusively by the create contract transaction\n"
|
Msg = "The 'init' function is called exclusively by the create contract transaction\n"
|
||||||
"and cannot be called from the contract code.\n",
|
"and cannot be called from the contract code.\n",
|
||||||
@ -2641,6 +2711,9 @@ mk_error({mixed_record_and_map, Expr}) ->
|
|||||||
Msg = io_lib:format("Mixed record fields and map keys in\n~s",
|
Msg = io_lib:format("Mixed record fields and map keys in\n~s",
|
||||||
[pp_expr(" ", Expr)]),
|
[pp_expr(" ", Expr)]),
|
||||||
mk_t_err(pos(Expr), Msg);
|
mk_t_err(pos(Expr), Msg);
|
||||||
|
mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
|
||||||
|
Msg = io_lib:format("Invalid '~s' argument\n~s\nIt must be either 'true' or 'false'.", [Name, pp_expr(" ", instantiate(Arg))]),
|
||||||
|
mk_t_err(pos(Arg), 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).
|
||||||
@ -2659,7 +2732,7 @@ pp_when({check_typesig, Name, Inferred, Given}) ->
|
|||||||
" inferred type: ~s\n"
|
" inferred type: ~s\n"
|
||||||
" given type: ~s\n",
|
" given type: ~s\n",
|
||||||
[Name, pp_loc(Given), pp(instantiate(Inferred)), pp(instantiate(Given))])};
|
[Name, pp_loc(Given), pp(instantiate(Inferred)), pp(instantiate(Given))])};
|
||||||
pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) ->
|
pp_when({infer_app, Fun, NamedArgs, Args, Inferred0, ArgTypes0}) ->
|
||||||
Inferred = instantiate(Inferred0),
|
Inferred = instantiate(Inferred0),
|
||||||
ArgTypes = instantiate(ArgTypes0),
|
ArgTypes = instantiate(ArgTypes0),
|
||||||
{pos(Fun),
|
{pos(Fun),
|
||||||
@ -2668,6 +2741,7 @@ pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) ->
|
|||||||
"to arguments\n~s",
|
"to arguments\n~s",
|
||||||
[pp_loc(Fun),
|
[pp_loc(Fun),
|
||||||
pp_typed(" ", Fun, Inferred),
|
pp_typed(" ", Fun, Inferred),
|
||||||
|
[ [pp_expr(" ", NamedArg), "\n"] || NamedArg <- NamedArgs ] ++
|
||||||
[ [pp_typed(" ", Arg, ArgT), "\n"]
|
[ [pp_typed(" ", Arg, ArgT), "\n"]
|
||||||
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])};
|
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])};
|
||||||
pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
||||||
@ -2744,6 +2818,12 @@ pp_when({list_comp, BindExpr, Inferred0, Expected0}) ->
|
|||||||
io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n"
|
io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n"
|
||||||
"against type \n~s\n",
|
"against type \n~s\n",
|
||||||
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)])};
|
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)])};
|
||||||
|
pp_when({check_named_arg_constraint, C}) ->
|
||||||
|
{id, _, Name} = Arg = C#named_argument_constraint.name,
|
||||||
|
[Type | _] = [ Type || {named_arg_t, _, {id, _, Name1}, Type, _} <- C#named_argument_constraint.args, Name1 == Name ],
|
||||||
|
Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s",
|
||||||
|
[pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]),
|
||||||
|
{pos(Arg), Err};
|
||||||
pp_when(unknown) -> {pos(0,0), ""}.
|
pp_when(unknown) -> {pos(0,0), ""}.
|
||||||
|
|
||||||
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
||||||
@ -2821,6 +2901,8 @@ pp({uvar, _, Ref}) ->
|
|||||||
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
||||||
pp({tvar, _, Name}) ->
|
pp({tvar, _, Name}) ->
|
||||||
Name;
|
Name;
|
||||||
|
pp({if_t, _, Id, Then, Else}) ->
|
||||||
|
["if(", pp([Id, Then, Else]), ")"];
|
||||||
pp({tuple_t, _, []}) ->
|
pp({tuple_t, _, []}) ->
|
||||||
"unit";
|
"unit";
|
||||||
pp({tuple_t, _, Cpts}) ->
|
pp({tuple_t, _, Cpts}) ->
|
||||||
@ -2832,8 +2914,8 @@ pp({app_t, _, T, []}) ->
|
|||||||
pp(T);
|
pp(T);
|
||||||
pp({app_t, _, Type, Args}) ->
|
pp({app_t, _, Type, Args}) ->
|
||||||
[pp(Type), "(", pp(Args), ")"];
|
[pp(Type), "(", pp(Args), ")"];
|
||||||
pp({named_arg_t, _, Name, Type, Default}) ->
|
pp({named_arg_t, _, Name, Type, _Default}) ->
|
||||||
[pp(Name), " : ", pp(Type), " = ", pp(Default)];
|
[pp(Name), " : ", pp(Type)];
|
||||||
pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->
|
pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->
|
||||||
["(", pp(Named), " | ", pp(As), ") => ", pp(B)];
|
["(", pp(Named), " | ", pp(As), ") => ", pp(B)];
|
||||||
pp({fun_t, _, Named, As, B}) when is_list(Named) ->
|
pp({fun_t, _, Named, As, B}) when is_list(Named) ->
|
||||||
|
@ -468,6 +468,8 @@ type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
|
|||||||
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
|
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
|
||||||
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
|
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
|
||||||
{function, FNamed ++ FArgs, type_to_fcode(Env, Sub, Res)};
|
{function, FNamed ++ FArgs, type_to_fcode(Env, Sub, Res)};
|
||||||
|
type_to_fcode(Env, Sub, {if_t, _, _, _, Else}) ->
|
||||||
|
type_to_fcode(Env, Sub, Else); %% Hacky: this is only for remote calls, in which case we want the unprotected type
|
||||||
type_to_fcode(_Env, _Sub, Type) ->
|
type_to_fcode(_Env, _Sub, Type) ->
|
||||||
error({todo, Type}).
|
error({todo, Type}).
|
||||||
|
|
||||||
@ -1217,8 +1219,8 @@ lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExp
|
|||||||
lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) ->
|
lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) ->
|
||||||
FVs = free_vars(Ct),
|
FVs = free_vars(Ct),
|
||||||
Ct1 = lambda_lift_expr(Layout, Ct),
|
Ct1 = lambda_lift_expr(Layout, Ct),
|
||||||
GasAndValueArgs = 2,
|
NamedArgCount = 3,
|
||||||
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + GasAndValueArgs) ],
|
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ],
|
||||||
Args = [{var, X} || X <- Xs],
|
Args = [{var, X} || X <- Xs],
|
||||||
make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args});
|
make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args});
|
||||||
lambda_lift_expr(Layout, Expr) ->
|
lambda_lift_expr(Layout, Expr) ->
|
||||||
|
@ -934,7 +934,9 @@ ast_typerep1({variant_t, Cons}, Icode) ->
|
|||||||
{variant, [ begin
|
{variant, [ begin
|
||||||
{constr_t, _, _, Args} = Con,
|
{constr_t, _, _, Args} = Con,
|
||||||
[ ast_typerep1(Arg, Icode) || Arg <- Args ]
|
[ ast_typerep1(Arg, Icode) || Arg <- Args ]
|
||||||
end || Con <- Cons ]}.
|
end || Con <- Cons ]};
|
||||||
|
ast_typerep1({if_t, _, _, _, Else}, Icode) ->
|
||||||
|
ast_typerep1(Else, Icode). %% protected remote calls are not in AEVM
|
||||||
|
|
||||||
ttl_t(Icode) ->
|
ttl_t(Icode) ->
|
||||||
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
||||||
|
@ -302,11 +302,13 @@ to_scode1(Env, {funcall, Fun, Args}) ->
|
|||||||
to_scode1(Env, {builtin, B, Args}) ->
|
to_scode1(Env, {builtin, B, Args}) ->
|
||||||
builtin_to_scode(Env, B, Args);
|
builtin_to_scode(Env, B, Args);
|
||||||
|
|
||||||
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value | Args]}) ->
|
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) ->
|
||||||
Lbl = make_function_id(Fun),
|
Lbl = make_function_id(Fun),
|
||||||
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
|
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
|
||||||
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
|
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
|
||||||
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
|
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
|
||||||
|
case Protected of
|
||||||
|
{lit, {bool, false}} ->
|
||||||
case Gas of
|
case Gas of
|
||||||
{builtin, call_gas_left, _} ->
|
{builtin, call_gas_left, _} ->
|
||||||
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
|
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
|
||||||
@ -315,6 +317,13 @@ to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value | Args]}) ->
|
|||||||
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
|
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
|
||||||
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
|
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
|
||||||
end;
|
end;
|
||||||
|
{lit, {bool, true}} ->
|
||||||
|
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)),
|
||||||
|
call_to_scode(Env, Call, [Ct, Value, Gas | Args]);
|
||||||
|
_ ->
|
||||||
|
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a),
|
||||||
|
call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args])
|
||||||
|
end;
|
||||||
|
|
||||||
to_scode1(_Env, {get_state, Reg}) ->
|
to_scode1(_Env, {get_state, Reg}) ->
|
||||||
[push(?s(Reg))];
|
[push(?s(Reg))];
|
||||||
@ -773,6 +782,7 @@ attributes(I) ->
|
|||||||
{'CALL', A} -> Impure(?a, [A]);
|
{'CALL', A} -> Impure(?a, [A]);
|
||||||
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
|
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
|
||||||
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
||||||
|
{'CALL_PGR', A, _, B, C, D, E, F} -> Impure(?a, [A, B, C, D, E, F]);
|
||||||
{'CALL_T', A} -> Impure(pc, [A]);
|
{'CALL_T', A} -> Impure(pc, [A]);
|
||||||
{'CALL_VALUE', A} -> Pure(A, []);
|
{'CALL_VALUE', A} -> Pure(A, []);
|
||||||
{'JUMP', _} -> Impure(pc, []);
|
{'JUMP', _} -> Impure(pc, []);
|
||||||
@ -1683,6 +1693,7 @@ split_calls(Ref, [], Acc, Blocks) ->
|
|||||||
split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
|
split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
|
||||||
element(1, I) == 'CALL_R';
|
element(1, I) == 'CALL_R';
|
||||||
element(1, I) == 'CALL_GR';
|
element(1, I) == 'CALL_GR';
|
||||||
|
element(1, I) == 'CALL_PGR';
|
||||||
element(1, I) == 'jumpif' ->
|
element(1, I) == 'jumpif' ->
|
||||||
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
|
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
|
||||||
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
|
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
|
||||||
|
@ -257,6 +257,8 @@ type({args_t, _, Args}) ->
|
|||||||
type({bytes_t, _, any}) -> text("bytes(_)");
|
type({bytes_t, _, any}) -> text("bytes(_)");
|
||||||
type({bytes_t, _, Len}) ->
|
type({bytes_t, _, Len}) ->
|
||||||
text(lists:concat(["bytes(", Len, ")"]));
|
text(lists:concat(["bytes(", Len, ")"]));
|
||||||
|
type({if_t, _, Id, Then, Else}) ->
|
||||||
|
beside(text("if"), args_type([Id, Then, Else]));
|
||||||
type({named_arg_t, _, Name, Type, _Default}) ->
|
type({named_arg_t, _, Name, Type, _Default}) ->
|
||||||
%% Drop the default value
|
%% Drop the default value
|
||||||
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
||||||
@ -283,12 +285,9 @@ tuple_type(Factors) ->
|
|||||||
, text(")")
|
, text(")")
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-spec arg_expr(aeso_syntax:arg_expr()) -> doc().
|
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
|
||||||
arg_expr({named_arg, _, Name, E}) ->
|
expr_p(P, {named_arg, _, Name, E}) ->
|
||||||
follow(hsep(expr(Name), text("=")), expr(E));
|
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
|
||||||
arg_expr(E) -> expr(E).
|
|
||||||
|
|
||||||
-spec expr_p(integer(), aeso_syntax:expr()) -> doc().
|
|
||||||
expr_p(P, {lam, _, Args, E}) ->
|
expr_p(P, {lam, _, Args, E}) ->
|
||||||
paren(P > 100, follow(hsep(args(Args), text("=>")), expr_p(100, E)));
|
paren(P > 100, follow(hsep(args(Args), text("=>")), expr_p(100, E)));
|
||||||
expr_p(P, If = {'if', Ann, Cond, Then, Else}) ->
|
expr_p(P, If = {'if', Ann, Cond, Then, Else}) ->
|
||||||
@ -447,7 +446,7 @@ prefix(P, Op, A) ->
|
|||||||
app(P, F, Args) ->
|
app(P, F, Args) ->
|
||||||
paren(P > 900,
|
paren(P > 900,
|
||||||
beside(expr_p(900, F),
|
beside(expr_p(900, F),
|
||||||
tuple(lists:map(fun arg_expr/1, Args)))).
|
tuple(lists:map(fun expr/1, Args)))).
|
||||||
|
|
||||||
field({field, _, LV, E}) ->
|
field({field, _, LV, E}) ->
|
||||||
follow(hsep(lvalue(LV), text("=")), expr(E));
|
follow(hsep(lvalue(LV), text("=")), expr(E));
|
||||||
|
@ -169,12 +169,13 @@ compilable_contracts() ->
|
|||||||
"qualified_constructor",
|
"qualified_constructor",
|
||||||
"let_patterns",
|
"let_patterns",
|
||||||
"lhs_matching",
|
"lhs_matching",
|
||||||
"more_strings"
|
"more_strings",
|
||||||
|
"protected_call"
|
||||||
].
|
].
|
||||||
|
|
||||||
not_yet_compilable(fate) -> [];
|
not_yet_compilable(fate) -> [];
|
||||||
not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update", "basic_auth_tx", "more_strings",
|
not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update", "basic_auth_tx", "more_strings",
|
||||||
"unapplied_builtins", "bytes_to_x", "state_handling"].
|
"unapplied_builtins", "bytes_to_x", "state_handling", "protected_call"].
|
||||||
|
|
||||||
%% Contracts that should produce type errors
|
%% Contracts that should produce type errors
|
||||||
|
|
||||||
@ -376,10 +377,10 @@ failing_contracts() ->
|
|||||||
[<<?Pos(12, 42)
|
[<<?Pos(12, 42)
|
||||||
"Cannot unify int\n"
|
"Cannot unify int\n"
|
||||||
" and string\n"
|
" and string\n"
|
||||||
"when checking the record projection at line 12, column 42\n"
|
"when checking the type of the expression at line 12, column 42\n"
|
||||||
" r.foo : (gas : int, value : int) => Remote.themap\n"
|
" r.foo() : map(int, string)\n"
|
||||||
"against the expected type\n"
|
"against the expected type\n"
|
||||||
" (gas : int, value : int) => map(string, int)">>])
|
" map(string, int)">>])
|
||||||
, ?TYPE_ERROR(bad_include_and_ns,
|
, ?TYPE_ERROR(bad_include_and_ns,
|
||||||
[<<?Pos(2, 11)
|
[<<?Pos(2, 11)
|
||||||
"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
|
"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
|
||||||
@ -540,11 +541,15 @@ failing_contracts() ->
|
|||||||
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
|
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
|
||||||
])
|
])
|
||||||
, ?TYPE_ERROR(map_as_map_key,
|
, ?TYPE_ERROR(map_as_map_key,
|
||||||
[<<?Pos(5, 25)
|
[<<?Pos(5, 47)
|
||||||
"Invalid key type\n"
|
"Invalid key type\n"
|
||||||
" map(int, int)\n"
|
" map(int, int)\n"
|
||||||
"Map keys cannot contain other maps.">>,
|
"Map keys cannot contain other maps.">>,
|
||||||
<<?Pos(6, 25)
|
<<?Pos(6, 31)
|
||||||
|
"Invalid key type\n"
|
||||||
|
" list(map(int, int))\n"
|
||||||
|
"Map keys cannot contain other maps.">>,
|
||||||
|
<<?Pos(6, 31)
|
||||||
"Invalid key type\n"
|
"Invalid key type\n"
|
||||||
" lm\n"
|
" lm\n"
|
||||||
"Map keys cannot contain other maps.">>])
|
"Map keys cannot contain other maps.">>])
|
||||||
@ -628,6 +633,12 @@ failing_contracts() ->
|
|||||||
"Empty record/map update\n"
|
"Empty record/map update\n"
|
||||||
" r {}">>
|
" r {}">>
|
||||||
])
|
])
|
||||||
|
, ?TYPE_ERROR(bad_protected_call,
|
||||||
|
[<<?Pos(6, 22)
|
||||||
|
"Invalid 'protected' argument\n"
|
||||||
|
" (0 : int) == (1 : int) : bool\n"
|
||||||
|
"It must be either 'true' or 'false'.">>
|
||||||
|
])
|
||||||
].
|
].
|
||||||
|
|
||||||
-define(Path(File), "code_errors/" ??File).
|
-define(Path(File), "code_errors/" ??File).
|
||||||
|
6
test/contracts/bad_protected_call.aes
Normal file
6
test/contracts/bad_protected_call.aes
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
contract Remote =
|
||||||
|
entrypoint id : int => int
|
||||||
|
|
||||||
|
contract ProtectedCall =
|
||||||
|
entrypoint bad(r : Remote) =
|
||||||
|
r.id(protected = 0 == 1, 18)
|
14
test/contracts/protected_call.aes
Normal file
14
test/contracts/protected_call.aes
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
contract Remote =
|
||||||
|
entrypoint id : int => int
|
||||||
|
|
||||||
|
contract ProtectedCall =
|
||||||
|
|
||||||
|
function higher_order(r : Remote) =
|
||||||
|
r.id
|
||||||
|
|
||||||
|
entrypoint test_ok(r : Remote) =
|
||||||
|
let f = higher_order(r)
|
||||||
|
let Some(n) = r.id(protected = true, 10)
|
||||||
|
let Some(m) = f(protected = true, 5)
|
||||||
|
n + m + r.id(protected = false, 100) + f(1)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user