From 244ef6a6e243c822080426eb2e7db9300af76469 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 10:18:45 +0200 Subject: [PATCH 01/10] Add a constraint field to type_sig --- src/aeso_ast_infer_types.erl | 50 +++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 42888ee..658315d 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -81,7 +81,9 @@ -type type() :: aeso_syntax:type(). -type name() :: string(). -type qname() :: [string()]. --type typesig() :: {type_sig, aeso_syntax:ann(), [aeso_syntax:named_arg_t()], [type()], type()}. +-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}. + +-type type_constraints() :: none. -type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. -type type_info() :: {aeso_syntax:ann(), typedef()}. @@ -206,7 +208,7 @@ bind_state(Env) -> false -> Unit end, Env1 = bind_funs([{"state", State}, - {"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env), + {"put", {type_sig, [stateful | Ann], none, [], [State], Unit}}], Env), case lookup_type(Env, {id, Ann, "event"}) of {E, _} -> @@ -370,16 +372,16 @@ global_env() -> Option = fun(T) -> {app_t, Ann, {id, Ann, "option"}, [T]} end, Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end, Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end, - Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end, + Fun = fun(Ts, T) -> {type_sig, Ann, none, [], Ts, T} end, Fun1 = fun(S, T) -> Fun([S], T) end, %% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end, %% Lambda1 = fun(S, T) -> Lambda([S], T) end, - StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end, + StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end, TVar = fun(X) -> {tvar, Ann, "'" ++ X} end, SignId = {id, Ann, "signature"}, SignDef = {bytes, Ann, <<0:64/unit:8>>}, Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}}, - SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end, + SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [Signature], Ts, T} end, TTL = {qid, Ann, ["Chain", "ttl"]}, Fee = Int, [A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]), @@ -669,7 +671,7 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) -> check_typedef_sccs(Env2, TypeMap, SCCs, Acc1); {variant_t, Cons} -> Target = check_type(Env1, app_t(Ann, D, Xs)), - ConType = fun([]) -> Target; (Args) -> {type_sig, Ann, [], Args, Target} end, + ConType = fun([]) -> Target; (Args) -> {type_sig, Ann, none, [], Args, Target} end, ConTypes = [ begin {constr_t, _, {con, _, Con}, Args} = ConDef, {Con, ConType(Args)} @@ -842,7 +844,7 @@ check_constructor_overlap(Env, Con = {con, Ann, Name}, NewType) -> case lookup_env(Env, term, Ann, Name) of false -> ok; {_, {Ann, Type}} -> - OldType = case Type of {type_sig, _, _, _, T} -> T; + OldType = case Type of {type_sig, _, _, _, _, T} -> T; _ -> Type end, OldCon = {con, Ann, Name}, type_error({repeated_constructor, [{OldCon, OldType}, {Con, NewType}]}) @@ -884,10 +886,10 @@ check_reserved_entrypoints(Funs) -> -spec check_fundecl(env(), aeso_syntax:decl()) -> {{name(), typesig()}, aeso_syntax:decl()}. check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) -> Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type), - {{Name, {type_sig, Ann, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}}; + {{Name, {type_sig, Ann, none, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}}; check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) -> type_error({fundecl_must_have_funtype, Ann, Id, Type}), - {{Name, {type_sig, Ann, [], [], Type}}, check_type(Env, Type)}. + {{Name, {type_sig, Ann, none, [], [], Type}}, check_type(Env, Type)}. infer_nonrec(Env, LetFun) -> create_constraints(), @@ -901,7 +903,7 @@ infer_nonrec(Env, LetFun) -> %% Currenty only the init function. check_special_funs(Env, {{"init", Type}, _}) -> - {type_sig, Ann, _Named, _Args, Res} = Type, + {type_sig, Ann, _Constr, _Named, _Args, Res} = Type, State = %% We might have implicit (no) state. case lookup_type(Env, {id, [], "state"}) of @@ -911,7 +913,8 @@ check_special_funs(Env, {{"init", Type}, _}) -> unify(Env, Res, State, {checking_init_type, Ann}); check_special_funs(_, _) -> ok. -typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}. +typesig_to_fun_t({type_sig, Ann, _Constr, Named, Args, Res}) -> + {fun_t, Ann, Named, Args, Res}. infer_letrec(Env, Defs) -> create_constraints(), @@ -945,7 +948,7 @@ infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Bo NewArgs = [{arg, A1, {id, A2, ArgName}, T} || {{_, T}, {arg, A1, {id, A2, ArgName}, _}} <- lists:zip(ArgTypes, Args)], NamedArgs = [], - TypeSig = {type_sig, Attrib, NamedArgs, [T || {arg, _, _, T} <- NewArgs], ResultType}, + TypeSig = {type_sig, Attrib, none, NamedArgs, [T || {arg, _, _, T} <- NewArgs], ResultType}, {{Name, TypeSig}, {letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}. @@ -981,14 +984,14 @@ lookup_name(Env, As, Id, Options) -> Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of - {type_sig, _, _, _, _} -> freshen_type(As, typesig_to_fun_t(Ty)); - _ when Freshen -> freshen_type(As, Ty); - _ -> Ty + {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty); + _ when Freshen -> freshen_type(As, Ty); + _ -> Ty end, {set_qname(QId, Id), Ty1} end. -check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) -> +check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _, _}) -> case aeso_syntax:get_ann(stateful, Type, false) of false -> ok; true -> @@ -1803,8 +1806,8 @@ unfold_types(Env, {typed, Attr, E, Type}, Options) -> {typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options)}; unfold_types(Env, {arg, Attr, Id, Type}, Options) -> {arg, Attr, Id, unfold_types_in_type(Env, Type, Options)}; -unfold_types(Env, {type_sig, Ann, NamedArgs, Args, Ret}, Options) -> - {type_sig, Ann, +unfold_types(Env, {type_sig, Ann, Constr, NamedArgs, Args, Ret}, Options) -> + {type_sig, Ann, Constr, unfold_types_in_type(Env, NamedArgs, Options), unfold_types_in_type(Env, Args, Options), unfold_types_in_type(Env, Ret, Options)}; @@ -2032,6 +2035,13 @@ freshen(Ann, [A | B]) -> freshen(_, X) -> X. +freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) -> + FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), + apply_typesig_constraint(Ann, Constr, FunT), + FunT. + +apply_typesig_constraint(_Ann, none, _FunT) -> ok. + %% Dereferences all uvars and replaces the uninstantiated ones with a %% succession of tvars. instantiate(E) -> @@ -2443,7 +2453,7 @@ if_branches(If = {'if', Ann, _, Then, Else}) -> end; if_branches(E) -> [E]. -pp_typed(Label, E, T = {type_sig, _, _, _, _}) -> pp_typed(Label, E, typesig_to_fun_t(T)); +pp_typed(Label, E, T = {type_sig, _, _, _, _, _}) -> pp_typed(Label, E, typesig_to_fun_t(T)); pp_typed(Label, {typed, _, Expr, _}, Type) -> pp_typed(Label, Expr, Type); pp_typed(Label, Expr, Type) -> @@ -2475,7 +2485,7 @@ pp_loc(T) -> plural(No, _Yes, [_]) -> No; plural(_No, Yes, _) -> Yes. -pp(T = {type_sig, _, _, _, _}) -> +pp(T = {type_sig, _, _, _, _, _}) -> pp(typesig_to_fun_t(T)); pp([]) -> ""; From 9eeb9ab11d09a9afcfb49bf9fe5b842f93b8a17e Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 10:32:21 +0200 Subject: [PATCH 02/10] Don't freshen types in list comprehension generators --- src/aeso_ast_infer_types.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 658315d..1763354 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1135,7 +1135,7 @@ infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, Type, E}|Rest] }; infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), - FunT = freshen_type(AsLF, typesig_to_fun_t(TypeSig)), + FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, AsLF, Name}, FunT, Env), {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = infer_expr(NewE, {list_comp, AsLC, Yield, Rest}), From 0f612ead901ab5262d642a18e29d058cfd5ba672 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 12:22:40 +0200 Subject: [PATCH 03/10] Sort errors by position --- src/aeso_errors.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeso_errors.erl b/src/aeso_errors.erl index c3f60ff..0dc9eda 100644 --- a/src/aeso_errors.erl +++ b/src/aeso_errors.erl @@ -59,7 +59,8 @@ pos(File, Line, Col) -> -spec throw(_) -> ok | no_return(). throw([]) -> ok; throw(Errs) when is_list(Errs) -> - erlang:throw({error, Errs}); + SortedErrs = lists:sort(fun(E1, E2) -> E1#err.pos =< E2#err.pos end, Errs), + erlang:throw({error, SortedErrs}); throw(#err{} = Err) -> erlang:throw({error, [Err]}). From e2ab41eeb22b1cd6776fa3d1badc3e993fd83ae4 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 12:23:13 +0200 Subject: [PATCH 04/10] Add Bytes.concat and Bytes.split to type checker --- src/aeso_ast_infer_types.erl | 92 ++++++++++++++++++++++++----- test/aeso_compiler_tests.erl | 44 ++++++++++++++ test/contracts/bad_bytes_concat.aes | 19 ++++++ test/contracts/bad_bytes_split.aes | 20 +++++++ 4 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 test/contracts/bad_bytes_concat.aes create mode 100644 test/contracts/bad_bytes_split.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 1763354..7b725de 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -372,7 +372,8 @@ global_env() -> Option = fun(T) -> {app_t, Ann, {id, Ann, "option"}, [T]} end, Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end, Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end, - Fun = fun(Ts, T) -> {type_sig, Ann, none, [], Ts, T} end, + FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end, + Fun = fun(Ts, T) -> FunC(none, Ts, T) end, Fun1 = fun(S, T) -> Fun([S], T) end, %% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end, %% Lambda1 = fun(S, T) -> Lambda([S], T) end, @@ -510,7 +511,10 @@ global_env() -> BytesScope = #scope { funs = MkDefs( [{"to_int", Fun1(Bytes(any), Int)}, - {"to_str", Fun1(Bytes(any), String)}]) }, + {"to_str", Fun1(Bytes(any), String)}, + {"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))}, + {"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))} + ]) }, %% Conversion IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, @@ -942,8 +946,8 @@ infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Bo Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), current_function = Fun }, check_unique_arg_names(Fun, Args), - ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args], - ExpectedType = check_type(Env, arg_type(What)), + ArgTypes = [{ArgName, check_type(Env, arg_type(ArgAnn, T))} || {arg, ArgAnn, ArgName, T} <- Args], + ExpectedType = check_type(Env, arg_type(NameAttrib, What)), NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType), NewArgs = [{arg, A1, {id, A2, ArgName}, T} || {{_, T}, {arg, A1, {id, A2, ArgName}, _}} <- lists:zip(ArgTypes, Args)], @@ -962,11 +966,14 @@ check_unique_arg_names(Fun, Args) -> print_typesig({Name, TypeSig}) -> ?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]). -arg_type({id, Attrs, "_"}) -> - fresh_uvar(Attrs); -arg_type({app_t, Attrs, Name, Args}) -> - {app_t, Attrs, Name, [arg_type(T) || T <- Args]}; -arg_type(T) -> +arg_type(ArgAnn, {id, Ann, "_"}) -> + case aeso_syntax:get_ann(origin, Ann, user) of + system -> fresh_uvar(ArgAnn); + user -> fresh_uvar(Ann) + end; +arg_type(ArgAnn, {app_t, Attrs, Name, Args}) -> + {app_t, Attrs, Name, [arg_type(ArgAnn, T) || T <- Args]}; +arg_type(_, T) -> T. app_t(_Ann, Name, []) -> Name; @@ -1118,7 +1125,7 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re , {list_comp, AttrsL, TypedYield, [{comprehension_if, AttrsIF, NewCond}|TypedRest]} , ResType}; infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, Type, E}|Rest]}) -> - NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, AsLV, E, arg_type(Type)}), + NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, AsLV, E, arg_type(AsLV, Type)}), BlockType = fresh_uvar(AsLV), {'case', _, NewPattern, NewRest} = infer_case( Env @@ -1342,7 +1349,7 @@ infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> NewE = bind_var({id, Ann, Name}, FunT, Env), [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) -> - NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}), + NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(aeso_syntax:get_ann(Pattern), Type)}), {'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} = infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType), [{letval, Attrs, NewPattern, Type, NewE}|NewRest]; @@ -1542,7 +1549,8 @@ destroy_and_report_unsolved_named_argument_constraints(Env) -> %% -- Bytes constraints -- --type byte_constraint() :: {is_bytes, utype()}. +-type byte_constraint() :: {is_bytes, utype()} + | {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}. create_bytes_constraints() -> ets_new(bytes_constraints, [bag]). @@ -1554,21 +1562,52 @@ get_bytes_constraints() -> add_bytes_constraint(Constraint) -> ets_insert(bytes_constraints, Constraint). -solve_bytes_constraints(_Env) -> +solve_bytes_constraints(Env) -> + [ solve_bytes_constraint(Env, C) || C <- get_bytes_constraints() ], ok. +solve_bytes_constraint(_Env, {is_bytes, _}) -> ok; +solve_bytes_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) -> + A = unfold_types_in_type(Env, dereference(A0)), + B = unfold_types_in_type(Env, dereference(B0)), + C = unfold_types_in_type(Env, dereference(C0)), + case {A, B, C} of + {{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); + {{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); + {_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + _ -> ok + end. + destroy_bytes_constraints() -> ets_delete(bytes_constraints). destroy_and_report_unsolved_bytes_constraints(Env) -> - [ check_bytes_constraint(Env, C) || C <- get_bytes_constraints() ], + Constraints = get_bytes_constraints(), + InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints, + T <- [A, B, C], + element(1, T) /= bytes_t ], + %% Skip is_bytes constraints for types that occur in add_bytes constraints + %% (no need to generate error messages for both is_bytes and add_bytes). + Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint); + (_) -> false end, + [ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ], destroy_bytes_constraints(). check_bytes_constraint(Env, {is_bytes, Type}) -> Type1 = unfold_types_in_type(Env, instantiate(Type)), case Type1 of {bytes_t, _, _} -> ok; - _ -> type_error({cannot_unify, Type1, {bytes_t, [], any}, {at, Type}}) + _ -> + type_error({unknown_byte_length, Type}) + end; +check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) -> + A = unfold_types_in_type(Env, instantiate(A0)), + B = unfold_types_in_type(Env, instantiate(B0)), + C = unfold_types_in_type(Env, instantiate(C0)), + case {A, B, C} of + {{bytes_t, _, _M}, {bytes_t, _, _N}, {bytes_t, _, _R}} -> + ok; %% If all are solved we checked M + N == R in solve_bytes_constraint. + _ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C}) end. %% -- Field constraints -- @@ -1999,6 +2038,8 @@ occurs_check1(R, [H | T]) -> occurs_check(R, H) orelse occurs_check(R, T); occurs_check1(_, []) -> false. +fresh_uvar([{origin, system}]) -> + error(oh_no_you_dont); fresh_uvar(Attrs) -> {uvar, Attrs, make_ref()}. @@ -2040,7 +2081,11 @@ freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) -> apply_typesig_constraint(Ann, Constr, FunT), FunT. -apply_typesig_constraint(_Ann, none, _FunT) -> ok. +apply_typesig_constraint(_Ann, none, _FunT) -> ok; +apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> + add_bytes_constraint({add_bytes, Ann, concat, A, B, C}); +apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> + add_bytes_constraint({add_bytes, Ann, split, A, B, C}). %% Dereferences all uvars and replaces the uninstantiated ones with a %% succession of tvars. @@ -2326,6 +2371,21 @@ mk_error({bad_top_level_decl, Decl}) -> Msg = io_lib:format("The definition of '~s' must appear inside a ~s.\n", [pp_expr("", Id), What]), mk_t_err(pos(Decl), Msg); +mk_error({unknown_byte_length, Type}) -> + Msg = io_lib:format("Cannot resolve length of byte array.\n", []), + mk_t_err(pos(Type), Msg); +mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) -> + Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n" + "~s (at ~s)\n~s (at ~s)\nand result type\n~s (at ~s)\n", + [pp_type(" - ", A), pp_loc(A), pp_type(" - ", B), + pp_loc(B), pp_type(" - ", C), pp_loc(C)]), + mk_t_err(pos(Ann), Msg); +mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) -> + Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.split with argument of type\n" + "~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)\n", + [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), + pp_type(" - ", B), pp_loc(B)]), + mk_t_err(pos(Ann), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 3ba5fc9..9f19e82 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -511,6 +511,50 @@ failing_contracts() -> [<>]) + , ?TYPE_ERROR(bad_bytes_concat, + [<>, + <>, + <>, + <>, + <>]) + , ?TYPE_ERROR(bad_bytes_split, + [<>, + <>, + <>]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/bad_bytes_concat.aes b/test/contracts/bad_bytes_concat.aes new file mode 100644 index 0000000..fa8ca01 --- /dev/null +++ b/test/contracts/bad_bytes_concat.aes @@ -0,0 +1,19 @@ +contract BytesConcat = + + entrypoint test1(x : bytes(10), y : bytes(20)) = + Bytes.concat(x, y) + + entrypoint test2(x : bytes(10), y) : bytes(15) = + Bytes.concat(x, y) + + entrypoint test3(x, y : bytes(20)) : bytes(25) = + Bytes.concat(x, y) + + entrypoint fail1(x, y) : bytes(10) = Bytes.concat(x, y) + entrypoint fail2(x, y) = Bytes.concat(x, y) + entrypoint fail3(x : bytes(6), y : bytes(20)) : bytes(25) = + Bytes.concat(x, y) + entrypoint fail4(x : bytes(6), y) : _ = + Bytes.concat(x, y) + + entrypoint fail5(x) = Bytes.to_str(x) diff --git a/test/contracts/bad_bytes_split.aes b/test/contracts/bad_bytes_split.aes new file mode 100644 index 0000000..97fcb89 --- /dev/null +++ b/test/contracts/bad_bytes_split.aes @@ -0,0 +1,20 @@ +contract BytesSplit = + + entrypoint test1(x) : bytes(10) * bytes(20) = + Bytes.split(x) + + entrypoint test2(x : bytes(15)) : bytes(10) * _ = + Bytes.split(x) + + entrypoint test3(x : bytes(25)) : _ * bytes(20) = + Bytes.split(x) + + entrypoint fail1(x) : _ * bytes(20) = + Bytes.split(x) + + entrypoint fail2(x : bytes(15)) : _ = + Bytes.split(x) + + entrypoint fail3(x) : bytes(20) * _ = + Bytes.split(x) + From 3ceeee22fa18ea98280f24319e29134d56b4e613 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 12:33:47 +0200 Subject: [PATCH 05/10] Don't forget to solve constraints --- src/aeso_ast_infer_types.erl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 7b725de..fef1e60 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -899,7 +899,6 @@ infer_nonrec(Env, LetFun) -> create_constraints(), NewLetFun = infer_letfun(Env, LetFun), check_special_funs(Env, NewLetFun), - solve_constraints(Env), destroy_and_report_unsolved_constraints(Env), Result = {TypeSig, _} = instantiate(NewLetFun), print_typesig(TypeSig), @@ -1491,12 +1490,10 @@ create_constraints() -> create_bytes_constraints(), create_field_constraints(). -solve_constraints(Env) -> +destroy_and_report_unsolved_constraints(Env) -> solve_named_argument_constraints(Env), solve_bytes_constraints(Env), - solve_field_constraints(Env). - -destroy_and_report_unsolved_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). From 3ea8470dc852b94a887b6f86a8e2814efebfd78e Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 16:01:35 +0200 Subject: [PATCH 06/10] Compile Bytes.concat and split to FATE --- src/aeso_ast_to_fcode.erl | 5 ++++- src/aeso_fcode_to_fate.erl | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index d813348..e795594 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -198,7 +198,7 @@ builtins() -> {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, - {["Bytes"], [{"to_int", 1}, {"to_str", 1}]}, + {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, {["Int"], [{"to_str", 1}]}, {["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], @@ -427,6 +427,9 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) -> validate_aens_resolve_type(Ann, ResType, AensType), TypeArgs = [{lit, {typerep, AensType}}], {builtin_u, B, Ar, TypeArgs}; + {builtin_u, B = bytes_split, Ar} -> + {fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type, + {builtin_u, B, Ar, [{lit, {int, N}}]}; Other -> Other end; diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 9412c79..96a61b5 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -106,6 +106,8 @@ Op =:= 'AUTH_TX_HASH' orelse Op =:= 'BYTES_TO_INT' orelse Op =:= 'BYTES_TO_STR' orelse + Op =:= 'BYTES_CONCAT' orelse + Op =:= 'BYTES_SPLIT' orelse Op =:= 'ORACLE_CHECK' orelse Op =:= 'ORACLE_CHECK_QUERY' orelse Op =:= 'IS_ORACLE' orelse @@ -490,6 +492,10 @@ builtin_to_scode(Env, bytes_to_int, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_to_int(?a, ?a), Args); builtin_to_scode(Env, bytes_to_str, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_to_str(?a, ?a), Args); +builtin_to_scode(Env, bytes_concat, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_concat(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_split, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args); builtin_to_scode(Env, abort, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:abort(?a), Args); builtin_to_scode(Env, chain_spend, [_, _] = Args) -> @@ -846,6 +852,8 @@ attributes(I) -> {'AUTH_TX_HASH', A} -> Pure(A, []); {'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_STR', A, B} -> Pure(A, [B]); + {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]); {'ORACLE_CHECK', A, B, C, D} -> Impure(A, [B, C, D]); {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]); {'IS_ORACLE', A, B} -> Impure(A, [B]); From cc531f9957c92b069c4f783d83fa6b5916e7dba1 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 16:01:51 +0200 Subject: [PATCH 07/10] Test case for Bytes.concat/split --- test/aeso_compiler_tests.erl | 1 + test/contracts/bytes_concat.aes | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 test/contracts/bytes_concat.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 9f19e82..802d129 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -143,6 +143,7 @@ compilable_contracts() -> "address_chain", "namespace_bug", "bytes_to_x", + "bytes_concat", "aens", "tuple_match", "cyclic_include", diff --git a/test/contracts/bytes_concat.aes b/test/contracts/bytes_concat.aes new file mode 100644 index 0000000..05da86c --- /dev/null +++ b/test/contracts/bytes_concat.aes @@ -0,0 +1,4 @@ +contract BytesConcat = + entrypoint rot(a : bytes(3)) = + switch (Bytes.split(a)) + (b, c) => Bytes.concat(c : bytes(2), b) From f09198b588308a4348bc8cae7667aeedd05eb54c Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 16:01:59 +0200 Subject: [PATCH 08/10] aebytecode commit --- rebar.config | 2 +- rebar.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index cb7a7cb..befd980 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,"3106ca1"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"17c9656"}}} , {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 63bdeee..00572a9 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"3106ca13063cf12f23e459cf60c6f987777a7e83"}}, + {ref,"17c9656f5ca60f6522d598aaf2999660030f8664"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", From f1b36c99acb5ae3b7668dd3ffeefe7bd277d5d50 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 18:23:49 +0200 Subject: [PATCH 09/10] Compile Bytes.concat/split for AEVM --- src/aeso_ast_to_icode.erl | 9 +++++ src/aeso_builtins.erl | 80 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 3f5ce4c..d369b30 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -496,6 +496,8 @@ is_builtin_fun({qid, _, ["Address", "is_contract"]}, _Icode) -> is_builtin_fun({qid, _, ["Address", "is_payable"]}, _Icode) -> true; is_builtin_fun({qid, _, ["Bytes", "to_int"]}, _Icode) -> true; is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bytes", "concat"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bytes", "split"]}, _Icode) -> true; is_builtin_fun(_, _) -> false. %% -- Code generation for builtin functions -- @@ -718,6 +720,13 @@ builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) -> builtin_code(_, {qid, _, ["Bytes", "to_str"]}, [Bytes], _, _, Icode) -> {typed, _, _, {bytes_t, _, N}} = Bytes, builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]); +builtin_code(_, {qid, _, ["Bytes", "concat"]}, [A, B], [TypeA, TypeB], _, Icode) -> + {bytes_t, _, M} = TypeA, + {bytes_t, _, N} = TypeB, + builtin_call({bytes_concat, M, N}, [ast_body(A, Icode), ast_body(B, Icode)]); +builtin_code(_, {qid, _, ["Bytes", "split"]}, [A], _, ResType, Icode) -> + {tuple_t, _, [{bytes_t, _, M}, {bytes_t, _, N}]} = ResType, + builtin_call({bytes_split, M, N}, [ast_body(A, Icode)]); builtin_code(_As, Fun, _Args, _ArgsT, _RetT, _Icode) -> gen_error({missing_code_for, Fun}). diff --git a/src/aeso_builtins.erl b/src/aeso_builtins.erl index 79677c3..211d7fd 100644 --- a/src/aeso_builtins.erl +++ b/src/aeso_builtins.erl @@ -90,7 +90,13 @@ option_some(X) -> {tuple, [{integer, 1}, X]}. -define(BSL(X, B), op('bsl', ?MUL(B, 8), X)). -define(BSR(X, B), op('bsr', ?MUL(B, 8), X)). -op(Op, A, B) -> {binop, Op, operand(A), operand(B)}. +op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}). + +%% We generate a lot of B * 8 for integer B from BSL and BSR. +simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 -> + {integer, A * B}; +simpl(Op) -> Op. + operand(A) when is_atom(A) -> v(A); operand(I) when is_integer(I) -> {integer, I}; @@ -162,6 +168,8 @@ builtin_function(BF) -> {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); {bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N)); {bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N)); + {bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B)); + {bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B)); bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker()); string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse_ -> bfun(BF, builtin_string_reverse_()) @@ -576,6 +584,75 @@ builtin_string_reverse_() -> builtin_addr_to_str() -> {[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}. +%% At most one word +%% | ..... | ========= | ........ | +%% Offs ^ ^- Len -^ TotalLen ^ +bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 -> + %% Bytes are packed into a single word + Masked = + case Offs of + 0 -> Bytes; + _ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8)) + end, + Unpadded = + case 32 - (Offs + Len) of + 0 -> Masked; + N -> ?BSR(Masked, N) + end, + case Len of + 32 -> Unpadded; + _ -> ?BSL(Unpadded, 32 - Len) + end; +bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 -> + %% Bytes is a pointer to memory. The VM can read at non-aligned addresses. + %% Might read one word more than necessary. + Word = op('!', Offs, Bytes), + case Len == 32 of + true -> Word; + _ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len) + end. + +builtin_bytes_concat(A, B) -> + Type = fun(N) when N =< 32 -> word; (_) -> pointer end, + MkBytes = fun([W]) -> W; + (Ws) -> {tuple, Ws} end, + Words = fun(N) -> (N + 31) div 32 end, + WordsRes = Words(A + B), + Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a)); + (I) when 32 * I < A -> + Len = A rem 32, + Hi = bytes_slice(32 * I, Len, A, ?V(a)), + Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)), + ?ADD(Hi, ?BSR(Lo, Len)); + (I) -> + Offs = 32 * I - A, + Len = min(32, B - Offs), + bytes_slice(Offs, Len, B, ?V(b)) + end, + Body = + case {A, B} of + {0, _} -> ?V(b); + {_, 0} -> ?V(a); + _ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ]) + end, + {[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}. + +builtin_bytes_split(A, B) -> + Type = fun(N) when N =< 32 -> word; (_) -> pointer end, + MkBytes = fun([W]) -> W; + (Ws) -> {tuple, Ws} end, + Word = fun(I, Max) -> + bytes_slice(I, min(32, Max - I), A + B, ?V(c)) + end, + Body = + case {A, B} of + {0, _} -> [?I(0), ?V(c)]; + {_, 0} -> [?V(c), ?I(0)]; + _ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]), + MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])] + end, + {[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}. + bytes_to_raw_string(N, Term) when N =< 32 -> {tuple, [?I(N), Term]}; bytes_to_raw_string(N, Term) when N > 32 -> @@ -583,3 +660,4 @@ bytes_to_raw_string(N, Term) when N > 32 -> end, Words = (N + 31) div 32, ?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}). + From c849184c72b5e7b60122de3d84bbe9023cccbbdd Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 9 Sep 2019 18:47:06 +0200 Subject: [PATCH 10/10] type spec --- src/aeso_ast_infer_types.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index fef1e60..87066ec 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -83,7 +83,7 @@ -type qname() :: [string()]. -type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}. --type type_constraints() :: none. +-type type_constraints() :: none | bytes_concat | bytes_split. -type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. -type type_info() :: {aeso_syntax:ann(), typedef()}.