diff --git a/rebar.config b/rebar.config index 9572cf0..a41b9ef 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {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"} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", diff --git a/rebar.lock b/rebar.lock index e2166cf..54e0e24 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"5ef5d455b4255518aa0f5c91c626e9fb0deb980f"}}, + {ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0b21dfe..62e6c7a 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -20,6 +20,7 @@ | aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon() %% contracts | aeso_syntax:tvar() + | {if_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), utype()} %% Can branch on named argument (protected) | uvar(). -type uvar() :: {uvar, aeso_syntax:ann(), reference()}. @@ -43,7 +44,14 @@ name :: aeso_syntax:id(), 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_t :: utype() @@ -232,16 +240,21 @@ bind_fields([], Env) -> Env; bind_fields([{Id, Info} | Rest], 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}) -> Id = fun(X) -> {id, Ann, X} end, Int = Id("int"), 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, [], [], 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(). bind_contract({contract, Ann, Id, Contents}, Env) -> @@ -1359,7 +1372,7 @@ infer_expr(Env, {typed, As, Body, Type}) -> Type1 = check_type(Env, Type), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {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 ], Args = Args0 -- NamedArgs, 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), NewArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- NewArgs], + GeneralResultType = 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)} end; 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], ArgTypes = [T || {typed, _, _, T} <- TypedArgs], 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}. infer_pattern(Env, Pattern) -> @@ -1705,12 +1726,12 @@ create_constraints() -> create_field_constraints(). destroy_and_report_unsolved_constraints(Env) -> + solve_field_constraints(Env), solve_named_argument_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_named_argument_constraints(Env). + destroy_and_report_unsolved_named_argument_constraints(Env), + destroy_and_report_unsolved_field_constraints(Env). %% -- Named argument constraints -- @@ -1750,8 +1771,43 @@ check_named_argument_constraint(Env, type_error({bad_named_argument, Args, Id}), false; [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. +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) -> Unsolved = solve_named_argument_constraints(Env, get_named_argument_constraints()), [ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ], @@ -1977,9 +2033,11 @@ destroy_and_report_unsolved_field_constraints(Env) -> {FieldCs, OtherCs} = lists:partition(fun(#field_constraint{}) -> true; (_) -> false end, get_field_constraints()), - {CreateCs, ContractCs} = + {CreateCs, OtherCs1} = lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end, OtherCs), + {ContractCs, []} = + lists:partition(fun(#is_contract_constraint{}) -> true; (_) -> false end, OtherCs1), Unknown = solve_known_record_types(Env, FieldCs), if Unknown == [] -> ok; true -> @@ -2053,7 +2111,8 @@ unfold_record_types(Env, T) -> unfold_types(Env, T, [unfold_record_types]). 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) -> {arg, Attr, Id, unfold_types_in_type(Env, Type, 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) -> 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}; 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), @@ -2153,8 +2213,13 @@ subst_tvars1(_Env, X) -> unify(_, {id, _, "_"}, _, _When) -> true; unify(_, _, {id, _, "_"}, _When) -> true; unify(Env, A, B, When) -> - A1 = dereference(unfold_types_in_type(Env, A)), - B1 = dereference(unfold_types_in_type(Env, B)), + Options = + 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, {uvar, _, R}, {uvar, _, R}, _When) -> @@ -2185,6 +2250,9 @@ unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) -> true; unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) -> 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) -> unify(Env, Named1, Named2, When) andalso 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_check1(R, {field_t, _, _, T}) -> occurs_check(R, T); +occurs_check1(R, {if_t, _, _, Then, Else}) -> + occurs_check(R, [Then, Else]); occurs_check1(R, [H | T]) -> occurs_check(R, H) orelse occurs_check(R, T); 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", [pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]), 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)]), 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}) -> Msg = "The 'init' function is called exclusively by the create contract transaction\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", [pp_expr(" ", Expr)]), 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) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). @@ -2659,7 +2732,7 @@ pp_when({check_typesig, Name, Inferred, Given}) -> " inferred type: ~s\n" " given type: ~s\n", [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), ArgTypes = instantiate(ArgTypes0), {pos(Fun), @@ -2668,6 +2741,7 @@ pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) -> "to arguments\n~s", [pp_loc(Fun), pp_typed(" ", Fun, Inferred), + [ [pp_expr(" ", NamedArg), "\n"] || NamedArg <- NamedArgs ] ++ [ [pp_typed(" ", Arg, ArgT), "\n"] || {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])}; 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" "against type \n~s\n", [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), ""}. -spec pp_why_record(why_record()) -> {pos(), iolist()}. @@ -2821,6 +2901,8 @@ pp({uvar, _, Ref}) -> ["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ]; pp({tvar, _, Name}) -> Name; +pp({if_t, _, Id, Then, Else}) -> + ["if(", pp([Id, Then, Else]), ")"]; pp({tuple_t, _, []}) -> "unit"; pp({tuple_t, _, Cpts}) -> @@ -2832,8 +2914,8 @@ pp({app_t, _, T, []}) -> pp(T); pp({app_t, _, Type, Args}) -> [pp(Type), "(", pp(Args), ")"]; -pp({named_arg_t, _, Name, Type, Default}) -> - [pp(Name), " : ", pp(Type), " = ", pp(Default)]; +pp({named_arg_t, _, Name, Type, _Default}) -> + [pp(Name), " : ", pp(Type)]; pp({fun_t, _, Named = {uvar, _, _}, As, B}) -> ["(", pp(Named), " | ", pp(As), ") => ", pp(B)]; pp({fun_t, _, Named, As, B}) when is_list(Named) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index becbb91..2736cd4 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -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], FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args], {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) -> 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}) -> FVs = free_vars(Ct), Ct1 = lambda_lift_expr(Layout, Ct), - GasAndValueArgs = 2, - Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + GasAndValueArgs) ], + NamedArgCount = 3, + Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ], Args = [{var, X} || X <- Xs], make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args}); lambda_lift_expr(Layout, Expr) -> diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index a7c158b..b1a1874 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -934,7 +934,9 @@ ast_typerep1({variant_t, Cons}, Icode) -> {variant, [ begin {constr_t, _, _, Args} = Con, [ 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) -> ast_typerep({qid, [], ["Chain", "ttl"]}, Icode). diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 99bd026..623b2ec 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -302,18 +302,27 @@ to_scode1(Env, {funcall, Fun, Args}) -> to_scode1(Env, {builtin, 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), {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), RetType = ?i(aeb_fate_data:make_typerep(RetType0)), - case Gas of - {builtin, call_gas_left, _} -> - Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), - call_to_scode(Env, Call, [Ct, Value | Args]); + case Protected of + {lit, {bool, false}} -> + case Gas of + {builtin, call_gas_left, _} -> + Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), + call_to_scode(Env, Call, [Ct, Value | Args]); + _ -> + Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a), + call_to_scode(Env, Call, [Ct, Value, Gas | Args]) + 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_gr(?a, Lbl, ArgType, RetType, ?a, ?a), - 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}) -> @@ -773,6 +782,7 @@ attributes(I) -> {'CALL', A} -> Impure(?a, [A]); {'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_PGR', A, _, B, C, D, E, F} -> Impure(?a, [A, B, C, D, E, F]); {'CALL_T', A} -> Impure(pc, [A]); {'CALL_VALUE', A} -> Pure(A, []); {'JUMP', _} -> Impure(pc, []); @@ -1683,6 +1693,7 @@ split_calls(Ref, [], Acc, Blocks) -> split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL'; element(1, I) == 'CALL_R'; element(1, I) == 'CALL_GR'; + element(1, I) == 'CALL_PGR'; element(1, I) == 'jumpif' -> split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]); split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 919c9ef..39dc19e 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -257,6 +257,8 @@ type({args_t, _, Args}) -> type({bytes_t, _, any}) -> text("bytes(_)"); type({bytes_t, _, 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}) -> %% Drop the default value %% follow(hsep(typed(name(Name), Type), text("=")), expr(Default)); @@ -283,12 +285,9 @@ tuple_type(Factors) -> , text(")") ]). --spec arg_expr(aeso_syntax:arg_expr()) -> doc(). -arg_expr({named_arg, _, Name, E}) -> - follow(hsep(expr(Name), text("=")), expr(E)); -arg_expr(E) -> expr(E). - --spec expr_p(integer(), aeso_syntax:expr()) -> doc(). +-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc(). +expr_p(P, {named_arg, _, Name, E}) -> + paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E))); expr_p(P, {lam, _, Args, E}) -> paren(P > 100, follow(hsep(args(Args), text("=>")), expr_p(100, E))); expr_p(P, If = {'if', Ann, Cond, Then, Else}) -> @@ -447,7 +446,7 @@ prefix(P, Op, A) -> app(P, F, Args) -> paren(P > 900, beside(expr_p(900, F), - tuple(lists:map(fun arg_expr/1, Args)))). + tuple(lists:map(fun expr/1, Args)))). field({field, _, LV, E}) -> follow(hsep(lvalue(LV), text("=")), expr(E)); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 97ad893..77fdd52 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -169,12 +169,13 @@ compilable_contracts() -> "qualified_constructor", "let_patterns", "lhs_matching", - "more_strings" + "more_strings", + "protected_call" ]. not_yet_compilable(fate) -> []; 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 @@ -376,10 +377,10 @@ failing_contracts() -> [< Remote.themap\n" + "when checking the type of the expression at line 12, column 42\n" + " r.foo() : map(int, string)\n" "against the expected type\n" - " (gas : int, value : int) => map(string, int)">>]) + " map(string, int)">>]) , ?TYPE_ERROR(bad_include_and_ns, [<>, @@ -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">> ]) , ?TYPE_ERROR(map_as_map_key, - [<>, - <>, + <>]) @@ -628,6 +633,12 @@ failing_contracts() -> "Empty record/map update\n" " r {}">> ]) + , ?TYPE_ERROR(bad_protected_call, + [<> + ]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/bad_protected_call.aes b/test/contracts/bad_protected_call.aes new file mode 100644 index 0000000..ba1f59c --- /dev/null +++ b/test/contracts/bad_protected_call.aes @@ -0,0 +1,6 @@ +contract Remote = + entrypoint id : int => int + +contract ProtectedCall = + entrypoint bad(r : Remote) = + r.id(protected = 0 == 1, 18) diff --git a/test/contracts/protected_call.aes b/test/contracts/protected_call.aes new file mode 100644 index 0000000..d377399 --- /dev/null +++ b/test/contracts/protected_call.aes @@ -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) +