From 9e955d59585556b196a803743c30485b2c8a8183 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 30 Aug 2019 13:17:47 +0200 Subject: [PATCH 01/20] Remove unused aeso_constants --- src/aeso_ast_infer_types.erl | 9 +------- src/aeso_constants.erl | 42 ------------------------------------ 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 src/aeso_constants.erl diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 24fda40..0bf16e4 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -12,7 +12,7 @@ -module(aeso_ast_infer_types). --export([infer/1, infer/2, infer_constant/1, unfold_types_in_type/3]). +-export([infer/1, infer/2, unfold_types_in_type/3]). -type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} | {app_t, aeso_syntax:ann(), utype(), [utype()]} @@ -599,13 +599,6 @@ infer_contract_top(Env, Kind, Defs0, _Options) -> Defs = desugar(Defs0), infer_contract(Env, Kind, Defs). -%% TODO: revisit -infer_constant({letval, Attrs,_Pattern, Type, E}) -> - ets_init(), %% Init the ETS table state - {typed, _, _, PatType} = - infer_expr(global_env(), {typed, Attrs, E, arg_type(Type)}), - instantiate(PatType). - %% infer_contract takes a proplist mapping global names to types, and %% a list of definitions. -spec infer_contract(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. diff --git a/src/aeso_constants.erl b/src/aeso_constants.erl deleted file mode 100644 index 7475c2a..0000000 --- a/src/aeso_constants.erl +++ /dev/null @@ -1,42 +0,0 @@ --module(aeso_constants). - --export([string/1, get_type/1]). - -string(Str) -> - case aeso_parser:string("let _ = " ++ Str) of - {ok, [{letval, _, _, _, E}]} -> {ok, E}; - {ok, Other} -> error({internal_error, should_be_letval, Other}); - Err -> Err - end. - -get_type(Str) -> - case aeso_parser:string("let _ = " ++ Str) of - {ok, [Ast]} -> - AstT = aeso_ast_infer_types:infer_constant(Ast), - T = ast_to_type(AstT), - {ok, T}; - {ok, Other} -> error({internal_error, should_be_letval, Other}); - Err -> Err - end. - -ast_to_type({id, _, T}) -> - T; -ast_to_type({tuple_t, _, []}) -> "()"; -ast_to_type({tuple_t, _, Ts}) -> - "(" ++ list_ast_to_type(Ts) ++ ")"; -ast_to_type({app_t,_, {id, _, "list"}, [T]}) -> - lists:flatten("list(" ++ ast_to_type(T) ++ ")"); -ast_to_type({app_t,_, {id, _, "option"}, [T]}) -> - lists:flatten("option(" ++ ast_to_type(T) ++ ")"). - -list_ast_to_type([T]) -> - ast_to_type(T); -list_ast_to_type([T|Ts]) -> - ast_to_type(T) - ++ ", " - ++ list_ast_to_type(Ts). - - - - - From 249b61238e92563a5d2c9263f237d60395de22f1 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 2 Sep 2019 11:14:57 +0200 Subject: [PATCH 02/20] Structured parse_errors and type_errors --- src/aeso_aci.erl | 4 +- src/aeso_ast_infer_types.erl | 507 +++++++++++++++++++---------------- src/aeso_compiler.erl | 64 ++--- src/aeso_errors.erl | 71 +++++ src/aeso_parse_lib.erl | 8 +- src/aeso_parse_lib.hrl | 2 +- src/aeso_parser.erl | 45 +++- test/aeso_abi_tests.erl | 6 +- test/aeso_aci_tests.erl | 4 +- test/aeso_compiler_tests.erl | 19 +- test/aeso_parser_tests.erl | 7 +- 11 files changed, 427 insertions(+), 310 deletions(-) create mode 100644 src/aeso_errors.erl diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 433c17d..8bd329b 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -74,15 +74,13 @@ do_contract_interface(Type, ContractString, Options) -> string -> do_render_aci_json(JArray) end catch + throw:{type_errors, Errors} -> {error, Errors}; %% The compiler errors. error:{parse_errors, Errors} -> {error, join_errors("Parse errors", Errors, fun(E) -> E end)}; - error:{type_errors, Errors} -> - {error, join_errors("Type errors", Errors, fun(E) -> E end)}; error:{code_errors, Errors} -> {error, join_errors("Code errors", Errors, fun (E) -> io_lib:format("~p", [E]) end)} - %% General programming errors in the compiler just signal error. end. join_errors(Prefix, Errors, Pfun) -> diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0bf16e4..a8a7181 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -36,6 +36,8 @@ -type why_record() :: aeso_syntax:field(aeso_syntax:expr()) | {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}. +-type pos() :: aeso_errors:pos(). + -record(named_argument_constraint, {args :: named_args_t(), name :: aeso_syntax:id(), @@ -535,7 +537,7 @@ global_env() -> option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. --spec infer(aeso_syntax:ast()) -> aeso_syntax:ast(). +-spec infer(aeso_syntax:ast()) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. infer(Contracts) -> infer(Contracts, []). @@ -544,7 +546,8 @@ infer(Contracts) -> -spec init_env(list(option())) -> env(). init_env(_Options) -> global_env(). --spec infer(aeso_syntax:ast(), list(option())) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. +-spec infer(aeso_syntax:ast(), list(option())) -> + aeso_syntax:ast() | {env(), aeso_syntax:ast()}. infer(Contracts, Options) -> ets_init(), %% Init the ETS table state try @@ -2059,12 +2062,11 @@ create_type_errors() -> ets_new(type_errors, [bag]). destroy_and_report_type_errors(Env) -> - Errors = lists:reverse(ets_tab2list(type_errors)), - %% io:format("Type errors now: ~p\n", [Errors]), - PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ], + Errors0 = lists:reverse(ets_tab2list(type_errors)), + %% io:format("Type errors now: ~p\n", [Errors0]), ets_delete(type_errors), - Errors /= [] andalso - error({type_errors, [lists:flatten(Err) || Err <- PPErrors]}). + Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ], + Errors == [] orelse throw({type_errors, Errors}). %% Strip current namespace from error message for nicer printing. unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) -> @@ -2083,153 +2085,197 @@ unqualify1(NS, Xs) -> catch _:_ -> Xs end. -pp_error({cannot_unify, A, B, When}) -> - io_lib:format("Cannot unify ~s\n" - " and ~s\n" - "~s", [pp(instantiate(A)), pp(instantiate(B)), pp_when(When)]); -pp_error({unbound_variable, Id}) -> - io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]); -pp_error({undefined_field, Id}) -> - io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]); -pp_error({not_a_record_type, Type, Why}) -> - io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]); -pp_error({not_a_contract_type, Type, Lit}) -> - io_lib:format("The type ~s is not a contract type\n" - "when checking that the contract literal at ~s\n~s\n" - "has the type\n~s\n", - [pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]); -pp_error({non_linear_pattern, Pattern, Nonlinear}) -> - Plural = [ $s || length(Nonlinear) > 1 ], - io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n", - [Plural, string:join(Nonlinear, ", "), pp_expr(" ", Pattern), pp_loc(Pattern)]); -pp_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) -> - S = [ "s" || length(Fields) > 1 ], - io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s", - [S, string:join([ pp(F) || {_, F} <- Fields ], ", "), - pp_loc(First), - [ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]); -pp_error({missing_field, Field, Rec}) -> - io_lib:format("Record type ~s does not have field ~s (at ~s)\n", [pp(Rec), pp(Field), pp_loc(Field)]); -pp_error({missing_fields, Ann, RecType, Fields}) -> - Many = length(Fields) > 1, - S = [ "s" || Many ], - Are = if Many -> "are"; true -> "is" end, - io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n", - [S, string:join(Fields, ", "), Are, pp(RecType), pp_loc(Ann)]); -pp_error({no_records_with_all_fields, Fields = [{_, First} | _]}) -> - S = [ "s" || length(Fields) > 1 ], - io_lib:format("No record type with field~s ~s (at ~s)\n", - [S, string:join([ pp(F) || {_, F} <- Fields ], ", "), - pp_loc(First)]); -pp_error({recursive_types_not_implemented, Types}) -> - S = if length(Types) > 1 -> "s are mutually"; - true -> " is" end, - io_lib:format("The following type~s recursive, which is not yet supported:\n~s", - [S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]); -pp_error({event_must_be_variant_type, Where}) -> - io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]); -pp_error({indexed_type_must_be_word, Type, Type}) -> - io_lib:format("The indexed type ~s (at ~s) is not a word type\n", - [pp_type("", Type), pp_loc(Type)]); -pp_error({indexed_type_must_be_word, Type, Type1}) -> - io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n", - [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]); -pp_error({payload_type_must_be_string, Type, Type}) -> - io_lib:format("The payload type ~s (at ~s) should be string\n", - [pp_type("", Type), pp_loc(Type)]); -pp_error({payload_type_must_be_string, Type, Type1}) -> - io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n", - [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]); -pp_error({event_0_to_3_indexed_values, Constr}) -> - io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n", - [name(Constr), pp_loc(Constr)]); -pp_error({event_0_to_1_string_values, Constr}) -> - io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n", - [name(Constr), pp_loc(Constr)]); -pp_error({repeated_constructor, Cs}) -> - io_lib:format("Variant types must have distinct constructor names\n~s", - [[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]); -pp_error({bad_named_argument, [], Name}) -> - io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n", - [pp(Name), pp_loc(Name)]); -pp_error({bad_named_argument, Args, Name}) -> - io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s", - [pp(Name), pp_loc(Name), - [ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)]) - || {named_arg_t, _, Arg, Type, _} <- Args ]]); -pp_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) -> - io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n", - [pp_typed("", Name, Type), pp_loc(Name)]); -pp_error({reserved_entrypoint, Name, Def}) -> - io_lib:format("The name '~s' is reserved and cannot be used for a\ntop-level contract function (at ~s).\n", - [Name, pp_loc(Def)]); -pp_error({duplicate_definition, Name, Locs}) -> - io_lib:format("Duplicate definitions of ~s at\n~s", - [Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]); -pp_error({duplicate_scope, Kind, Name, OtherKind, L}) -> - io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", - [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]); -pp_error({include, _, {string, Pos, Name}}) -> - io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", - [binary_to_list(Name), pp_loc(Pos)]); -pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> - io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", - [Name, pp_loc(Pos)]); -pp_error({repeated_arg, Fun, Arg}) -> - io_lib:format("Repeated argument ~s to function ~s (at ~s).\n", - [Arg, pp(Fun), pp_loc(Fun)]); -pp_error({stateful_not_allowed, Id, Fun}) -> - io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", - [pp(Id), pp_loc(Id), pp(Fun)]); -pp_error({value_arg_not_allowed, Value, Fun}) -> - io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n", - [pp_expr("", Value), pp_loc(Value), pp(Fun)]); -pp_error({init_depends_on_state, Which, [_Init | Chain]}) -> +mk_t_err(Pos, Msg) -> + aeso_errors:new(type_error, Pos, lists:flatten(Msg)). +mk_t_err(Pos, Msg, Ctxt) -> + aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)). + +mk_error({cannot_unify, A, B, When}) -> + Msg = io_lib:format("Cannot unify ~s\n and ~s\n", + [pp(instantiate(A)), pp(instantiate(B))]), + {Pos, Ctxt} = pp_when(When), + mk_t_err(Pos, Msg, Ctxt); +mk_error({unbound_variable, Id}) -> + Msg = io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]), + mk_t_err(pos(Id), Msg); +mk_error({undefined_field, Id}) -> + Msg = io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]), + mk_t_err(pos(Id), Msg); +mk_error({not_a_record_type, Type, Why}) -> + Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]), + {Pos, Ctxt} = pp_why_record(Why), + mk_t_err(Pos, Msg, Ctxt); +mk_error({not_a_contract_type, Type, Lit}) -> + Msg = io_lib:format("The type ~s is not a contract type\n" + "when checking that the contract literal at ~s\n~s\n" + "has the type\n~s\n", + [pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]), + mk_t_err(pos(Lit), Msg); +mk_error({non_linear_pattern, Pattern, Nonlinear}) -> + Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n", + [plural("", "s", Nonlinear), string:join(Nonlinear, ", "), + pp_expr(" ", Pattern), pp_loc(Pattern)]), + mk_t_err(pos(Pattern), Msg); +mk_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) -> + Msg = io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s", + [plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "), + pp_loc(First), [ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]), + mk_t_err(pos(First), Msg); +mk_error({missing_field, Field, Rec}) -> + Msg = io_lib:format("Record type ~s does not have field ~s (at ~s)\n", + [pp(Rec), pp(Field), pp_loc(Field)]), + mk_t_err(pos(Field), Msg); +mk_error({missing_fields, Ann, RecType, Fields}) -> + Msg = io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n", + [plural("", "s", Fields), string:join(Fields, ", "), + plural("is", "are", Fields), pp(RecType), pp_loc(Ann)]), + mk_t_err(pos(Ann), Msg); +mk_error({no_records_with_all_fields, Fields = [{_, First} | _]}) -> + Msg = io_lib:format("No record type with field~s ~s (at ~s)\n", + [plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "), + pp_loc(First)]), + mk_t_err(pos(First), Msg); +mk_error({recursive_types_not_implemented, Types}) -> + S = plural(" is", "s are mutually", Types), + Msg = io_lib:format("The following type~s recursive, which is not yet supported:\n~s", + [S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]), + mk_t_err(pos(hd(Types)), Msg); +mk_error({event_must_be_variant_type, Where}) -> + Msg = io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]), + mk_t_err(pos(Where), Msg); +mk_error({indexed_type_must_be_word, Type, Type}) -> + Msg = io_lib:format("The indexed type ~s (at ~s) is not a word type\n", + [pp_type("", Type), pp_loc(Type)]), + mk_t_err(pos(Type), Msg); +mk_error({indexed_type_must_be_word, Type, Type1}) -> + Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n", + [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]), + mk_t_err(pos(Type), Msg); +mk_error({payload_type_must_be_string, Type, Type}) -> + Msg = io_lib:format("The payload type ~s (at ~s) should be string\n", + [pp_type("", Type), pp_loc(Type)]), + mk_t_err(pos(Type), Msg); +mk_error({payload_type_must_be_string, Type, Type1}) -> + Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n", + [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]), + mk_t_err(pos(Type), Msg); +mk_error({event_0_to_3_indexed_values, Constr}) -> + Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n", + [name(Constr), pp_loc(Constr)]), + mk_t_err(pos(Constr), Msg); +mk_error({event_0_to_1_string_values, Constr}) -> + Msg = io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n", + [name(Constr), pp_loc(Constr)]), + mk_t_err(pos(Constr), Msg); +mk_error({repeated_constructor, Cs}) -> + Msg = io_lib:format("Variant types must have distinct constructor names\n~s", + [[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]), + mk_t_err(pos(element(1, hd(Cs))), Msg); +mk_error({bad_named_argument, [], Name}) -> + Msg = io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n", + [pp(Name), pp_loc(Name)]), + mk_t_err(pos(Name), Msg); +mk_error({bad_named_argument, Args, Name}) -> + Msg = io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s", + [pp(Name), pp_loc(Name), + [ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)]) + || {named_arg_t, _, Arg, Type, _} <- Args ]]), + mk_t_err(pos(Name), Msg); +mk_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) -> + Msg = io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n", + [pp_typed("", Name, Type), pp_loc(Name)]), + mk_t_err(pos(Name), Msg); +mk_error({reserved_entrypoint, Name, Def}) -> + Msg = io_lib:format("The name '~s' is reserved and cannot be used for a\n" + "top-level contract function (at ~s).\n", [Name, pp_loc(Def)]), + mk_t_err(pos(Def), Msg); +mk_error({duplicate_definition, Name, Locs}) -> + Msg = io_lib:format("Duplicate definitions of ~s at\n~s", + [Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]), + mk_t_err(pos(hd(Locs)), Msg); +mk_error({duplicate_scope, Kind, Name, OtherKind, L}) -> + Msg = io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", + [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]), + mk_t_err(pos(Name), Msg); +mk_error({include, _, {string, Pos, Name}}) -> + Msg = io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", + [binary_to_list(Name), pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> + Msg = io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({repeated_arg, Fun, Arg}) -> + Msg = io_lib:format("Repeated argument ~s to function ~s (at ~s).\n", + [Arg, pp(Fun), pp_loc(Fun)]), + mk_t_err(pos(Fun), Msg); +mk_error({stateful_not_allowed, Id, Fun}) -> + Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", + [pp(Id), pp_loc(Id), pp(Fun)]), + mk_t_err(pos(Id), Msg); +mk_error({value_arg_not_allowed, Value, Fun}) -> + Msg = io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n", + [pp_expr("", Value), pp_loc(Value), pp(Fun)]), + mk_t_err(pos(Value), Msg); +mk_error({init_depends_on_state, Which, [_Init | Chain]}) -> WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end, - io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s", - [if Which == put -> "write"; true -> "read" end, - [ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)]) - || {[_, Fun], Ann} <- Chain]]); -pp_error({missing_body_for_let, Ann}) -> - io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]); -pp_error({public_modifier_in_contract, Decl}) -> + Msg = io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s", + [if Which == put -> "write"; true -> "read" end, + [ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)]) + || {[_, Fun], Ann} <- Chain]]), + mk_t_err(pos(element(2, hd(Chain))), Msg); +mk_error({missing_body_for_let, Ann}) -> + Msg = io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]), + mk_t_err(pos(Ann), Msg); +mk_error({public_modifier_in_contract, Decl}) -> Decl1 = mk_entrypoint(Decl), - io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n", - [pp_expr("", element(3, Decl)), pp_loc(Decl), - prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); -pp_error({init_must_be_an_entrypoint, Decl}) -> + Msg = io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl), + prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]), + mk_t_err(pos(Decl), Msg); +mk_error({init_must_be_an_entrypoint, Decl}) -> Decl1 = mk_entrypoint(Decl), - io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n", - [pp_loc(Decl), - prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); -pp_error({proto_must_be_entrypoint, Decl}) -> + Msg = io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n", + [pp_loc(Decl), + prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]), + mk_t_err(pos(Decl), Msg); +mk_error({proto_must_be_entrypoint, Decl}) -> Decl1 = mk_entrypoint(Decl), - io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n", - [pp_expr("", element(3, Decl)), pp_loc(Decl), - prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); -pp_error({proto_in_namespace, Decl}) -> - io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n", - [pp_loc(Decl)]); -pp_error({entrypoint_in_namespace, Decl}) -> - io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n", - [pp_loc(Decl)]); -pp_error({private_entrypoint, Decl}) -> - io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n", - [pp_expr("", element(3, Decl)), pp_loc(Decl)]); -pp_error({private_and_public, Decl}) -> - io_lib:format("The function ~s (at ~s) cannot be both public and private.\n", - [pp_expr("", element(3, Decl)), pp_loc(Decl)]); -pp_error({contract_has_no_entrypoints, Con}) -> - io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n" - "contract functions must be declared with the 'entrypoint' keyword instead of\n" - "'function'.\n", [pp_expr("", Con), pp_loc(Con)]); -pp_error({unbound_type, Type}) -> - io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]); -pp_error({new_tuple_syntax, Ann, Ts}) -> - 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_error(Err) -> - io_lib:format("Unknown error: ~p\n", [Err]). + Msg = io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl), + prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]), + mk_t_err(pos(Decl), Msg); +mk_error({proto_in_namespace, Decl}) -> + Msg = io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n", + [pp_loc(Decl)]), + mk_t_err(pos(Decl), Msg); +mk_error({entrypoint_in_namespace, Decl}) -> + Msg = io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n", + [pp_loc(Decl)]), + mk_t_err(pos(Decl), Msg); +mk_error({private_entrypoint, Decl}) -> + Msg = io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl)]), + mk_t_err(pos(Decl), Msg); +mk_error({private_and_public, Decl}) -> + Msg = io_lib:format("The function ~s (at ~s) cannot be both public and private.\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl)]), + mk_t_err(pos(Decl), Msg); +mk_error({contract_has_no_entrypoints, Con}) -> + Msg = io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n" + "contract functions must be declared with the 'entrypoint' keyword instead of\n" + "'function'.\n", [pp_expr("", Con), pp_loc(Con)]), + mk_t_err(pos(Con), Msg); +mk_error({unbound_type, Type}) -> + Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]), + mk_t_err(pos(Type), Msg); +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(Err) -> + Msg = io_lib:format("Unknown error: ~p\n", [Err]), + mk_t_err(pos(0, 0), Msg). mk_entrypoint(Decl) -> Ann = [entrypoint | lists:keydelete(public, 1, @@ -2237,110 +2283,114 @@ mk_entrypoint(Decl) -> aeso_syntax:get_ann(Decl))) -- [public, private]], aeso_syntax:set_ann(Ann, Decl). -pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]); -pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]); +pp_when({todo, What}) -> {pos(0, 0), io_lib:format("[TODO] ~p\n", [What])}; +pp_when({at, Ann}) -> {pos(Ann), io_lib:format("at ~s\n", [pp_loc(Ann)])}; pp_when({check_typesig, Name, Inferred, Given}) -> - io_lib:format("when checking the definition of ~s\n" - " inferred type: ~s\n" - " given type: ~s\n", - [Name, pp(instantiate(Inferred)), pp(instantiate(Given))]); + {pos(Given), + io_lib:format("when checking the definition of ~s (at ~s)\n" + " 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}) -> Inferred = instantiate(Inferred0), ArgTypes = instantiate(ArgTypes0), - io_lib:format("when checking the application at ~s of\n" - "~s\n" - "to arguments\n~s", - [pp_loc(Fun), - pp_typed(" ", Fun, Inferred), - [ [pp_typed(" ", Arg, ArgT), "\n"] - || {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ]); + {pos(Fun), + io_lib:format("when checking the application at ~s of\n" + "~s\n" + "to arguments\n~s", + [pp_loc(Fun), + pp_typed(" ", Fun, Inferred), + [ [pp_typed(" ", Arg, ArgT), "\n"] + || {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])}; pp_when({field_constraint, FieldType0, InferredType0, Fld}) -> FieldType = instantiate(FieldType0), InferredType = instantiate(InferredType0), - case Fld of - {field, _Ann, LV, Id, E} -> - io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n", - [pp_typed(" ", {lvalue, [], LV}, FieldType), - pp_loc(Fld), - pp(Id), - pp_typed(" ", E, InferredType)]); - {field, _Ann, LV, E} -> - io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the value\n~s\n", - [pp_typed(" ", {lvalue, [], LV}, FieldType), - pp_loc(Fld), - pp_typed(" ", E, InferredType)]); - {proj, _Ann, _Rec, _Fld} -> - io_lib:format("when checking the record projection at ~s\n~s\nagainst the expected type\n~s\n", - [pp_loc(Fld), - pp_typed(" ", Fld, FieldType), - pp_type(" ", InferredType)]) - end; + {pos(Fld), + case Fld of + {field, _Ann, LV, Id, E} -> + io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n", + [pp_typed(" ", {lvalue, [], LV}, FieldType), + pp_loc(Fld), + pp(Id), + pp_typed(" ", E, InferredType)]); + {field, _Ann, LV, E} -> + io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the value\n~s\n", + [pp_typed(" ", {lvalue, [], LV}, FieldType), + pp_loc(Fld), + pp_typed(" ", E, InferredType)]); + {proj, _Ann, _Rec, _Fld} -> + io_lib:format("when checking the record projection at ~s\n~s\nagainst the expected type\n~s\n", + [pp_loc(Fld), + pp_typed(" ", Fld, FieldType), + pp_type(" ", InferredType)]) + end}; pp_when({record_constraint, RecType0, InferredType0, Fld}) -> RecType = instantiate(RecType0), InferredType = instantiate(InferredType0), + {Pos, WhyRec} = pp_why_record(Fld), case Fld of {field, _Ann, _LV, _Id, _E} -> - io_lib:format("when checking that the record type\n~s\n~s\n" - "matches the expected type\n~s\n", - [pp_type(" ", RecType), - pp_why_record(Fld), - pp_type(" ", InferredType)]); + {Pos, + io_lib:format("when checking that the record type\n~s\n~s\n" + "matches the expected type\n~s\n", + [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])}; {field, _Ann, _LV, _E} -> - io_lib:format("when checking that the record type\n~s\n~s\n" - "matches the expected type\n~s\n", - [pp_type(" ", RecType), - pp_why_record(Fld), - pp_type(" ", InferredType)]); + {Pos, + io_lib:format("when checking that the record type\n~s\n~s\n" + "matches the expected type\n~s\n", + [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])}; {proj, _Ann, Rec, _FldName} -> - io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s\n", - [pp_typed(" ", Rec, InferredType), - pp_loc(Rec), - pp_type(" ", RecType), - pp_why_record(Fld)]) + {pos(Rec), + io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s\n", + [pp_typed(" ", Rec, InferredType), pp_loc(Rec), + pp_type(" ", RecType), WhyRec])} end; pp_when({if_branches, Then, ThenType0, Else, ElseType0}) -> {ThenType, ElseType} = instantiate({ThenType0, ElseType0}), Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ], - io_lib:format("when comparing the types of the if-branches\n" - "~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)]) - || {B, BType} <- Branches ] ]); + {pos(element(1, hd(Branches))), + io_lib:format("when comparing the types of the if-branches\n" + "~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)]) + || {B, BType} <- Branches ] ])}; pp_when({case_pat, Pat, PatType0, ExprType0}) -> {PatType, ExprType} = instantiate({PatType0, ExprType0}), - io_lib:format("when checking the type of the pattern at ~s\n~s\n" - "against the expected type\n~s\n", - [pp_loc(Pat), pp_typed(" ", Pat, PatType), - pp_type(" ", ExprType)]); + {pos(Pat), + io_lib:format("when checking the type of the pattern at ~s\n~s\n" + "against the expected type\n~s\n", + [pp_loc(Pat), pp_typed(" ", Pat, PatType), + pp_type(" ", ExprType)])}; pp_when({check_expr, Expr, Inferred0, Expected0}) -> {Inferred, Expected} = instantiate({Inferred0, Expected0}), - io_lib:format("when checking the type of the expression at ~s\n~s\n" - "against the expected type\n~s\n", - [pp_loc(Expr), pp_typed(" ", Expr, Inferred), - pp_type(" ", Expected)]); + {pos(Expr), + io_lib:format("when checking the type of the expression at ~s\n~s\n" + "against the expected type\n~s\n", + [pp_loc(Expr), pp_typed(" ", Expr, Inferred), + pp_type(" ", Expected)])}; pp_when({checking_init_type, Ann}) -> - io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n", - [pp_loc(Ann)]); + {pos(Ann), + io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n", + [pp_loc(Ann)])}; pp_when({list_comp, BindExpr, Inferred0, Expected0}) -> {Inferred, Expected} = instantiate({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)] - ); + {pos(BindExpr), + 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(unknown) -> {pos(0,0), ""}. -pp_when(unknown) -> "". - --spec pp_why_record(why_record()) -> iolist(). +-spec pp_why_record(why_record()) -> {pos(), iolist()}. pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) -> - io_lib:format("arising from an assignment of the field ~s (at ~s)", - [pp_expr("", {lvalue, [], LV}), - pp_loc(Fld)]); + {pos(Fld), + io_lib:format("arising from an assignment of the field ~s (at ~s)", + [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; pp_why_record(Fld = {field, _Ann, LV, _E}) -> - io_lib:format("arising from an assignment of the field ~s (at ~s)", - [pp_expr("", {lvalue, [], LV}), - pp_loc(Fld)]); + {pos(Fld), + io_lib:format("arising from an assignment of the field ~s (at ~s)", + [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; pp_why_record({proj, _Ann, Rec, FldName}) -> - io_lib:format("arising from the projection of the field ~s (at ~s)", - [pp(FldName), - pp_loc(Rec)]). + {pos(Rec), + io_lib:format("arising from the projection of the field ~s (at ~s)", + [pp(FldName), pp_loc(Rec)])}. if_branches(If = {'if', Ann, _, Then, Else}) -> @@ -2362,9 +2412,13 @@ pp_expr(Label, Expr) -> pp_type(Label, Type) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))). +src_file(T) -> aeso_syntax:get_ann(src_file, T, no_file). line_number(T) -> aeso_syntax:get_ann(line, T, 0). column_number(T) -> aeso_syntax:get_ann(col, T, 0). +pos(T) -> aeso_errors:pos(src_file(T), line_number(T), column_number(T)). +pos(L, C) -> aeso_errors:pos(L, C). + loc(T) -> {line_number(T), column_number(T)}. @@ -2375,6 +2429,9 @@ pp_loc(T) -> _ -> io_lib:format("line ~p, column ~p", [Line, Col]) end. +plural(No, _Yes, [_]) -> No; +plural(_No, Yes, _) -> Yes. + pp(T = {type_sig, _, _, _, _}) -> pp(typesig_to_fun_t(T)); pp([]) -> diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 014d17b..0939c75 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -99,10 +99,10 @@ from_string(Backend, ContractString, Options) -> from_string1(Backend, ContractString, Options) catch %% The compiler errors. - error:{parse_errors, Errors} -> - {error, join_errors("Parse errors", Errors, fun(E) -> E end)}; - error:{type_errors, Errors} -> - {error, join_errors("Type errors", Errors, fun(E) -> E end)}; + throw:{parse_errors, Errors} -> + {error, Errors}; + throw:{type_errors, Errors} -> + {error, Errors}; error:{code_errors, Errors} -> {error, join_errors("Code errors", Errors, fun (E) -> io_lib:format("~p", [E]) end)} @@ -230,10 +230,10 @@ check_call1(ContractString0, FunName, Args, Options) -> {ok, FunName, CallArgs} end catch - error:{parse_errors, Errors} -> - {error, join_errors("Parse errors", Errors, fun (E) -> E end)}; - error:{type_errors, Errors} -> - {error, join_errors("Type errors", Errors, fun (E) -> E end)}; + throw:{parse_errors, Errors} -> + {error, Errors}; + throw:{type_errors, Errors} -> + {error, Errors}; error:{badmatch, {error, missing_call_function}} -> {error, join_errors("Type errors", ["missing __call function"], fun (E) -> E end)}; @@ -345,10 +345,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> end end catch - error:{parse_errors, Errors} -> - {error, join_errors("Parse errors", Errors, fun (E) -> E end)}; - error:{type_errors, Errors} -> - {error, join_errors("Type errors", Errors, fun (E) -> E end)}; + throw:{parse_errors, Errors} -> + {error, Errors}; + throw:{type_errors, Errors} -> + {error, Errors}; error:{badmatch, {error, missing_function}} -> {error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"], fun (E) -> E end)}; @@ -444,10 +444,10 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> end end catch - error:{parse_errors, Errors} -> - {error, join_errors("Parse errors", Errors, fun (E) -> E end)}; - error:{type_errors, Errors} -> - {error, join_errors("Type errors", Errors, fun (E) -> E end)}; + throw:{parse_errors, Errors} -> + {error, Errors}; + throw:{type_errors, Errors} -> + {error, Errors}; error:{badmatch, {error, missing_function}} -> {error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"], fun (E) -> E end)}; @@ -582,37 +582,7 @@ parse(Text, Options) -> -spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast(). parse(Text, Included, Options) -> - %% Try and return something sensible here! - case aeso_parser:string(Text, Included, Options) of - %% Yay, it worked! - {ok, Contract} -> Contract; - %% Scan errors. - {error, {Pos, scan_error}} -> - parse_error(Pos, "scan error"); - {error, {Pos, scan_error_no_state}} -> - parse_error(Pos, "scan error"); - %% Parse errors. - {error, {Pos, parse_error, Error}} -> - parse_error(Pos, Error); - {error, {Pos, ambiguous_parse, As}} -> - ErrorString = io_lib:format("Ambiguous ~p", [As]), - parse_error(Pos, ErrorString); - %% Include error - {error, {Pos, include_error, File}} -> - parse_error(Pos, io_lib:format("could not find include file '~s'", [File])) - end. - --spec parse_error(aeso_parse_lib:pos(), string()) -> none(). -parse_error(Pos, ErrorString) -> - Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]), - error({parse_errors, [Error]}). + aeso_parser:string(Text, Included, Options). read_contract(Name) -> file:read_file(Name). - -pos_error({Line, Pos}) -> - io_lib:format("line ~p, column ~p", [Line, Pos]); -pos_error({no_file, Line, Pos}) -> - pos_error({Line, Pos}); -pos_error({File, Line, Pos}) -> - io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]). diff --git a/src/aeso_errors.erl b/src/aeso_errors.erl new file mode 100644 index 0000000..a9e6419 --- /dev/null +++ b/src/aeso_errors.erl @@ -0,0 +1,71 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc ADT for structured error messages + formatting. +%%% +%%% @end +%%%------------------------------------------------------------------- + +-module(aeso_errors). + +-type src_file() :: no_file | iolist(). + +-record(pos, { file = no_file :: src_file() + , line = 0 :: non_neg_integer() + , col = 0 :: non_neg_integer() + }). + +-type pos() :: #pos{}. +-type error_type() :: type_error | parse_error | code_error | internal_error. + +-record(err, { pos = #pos{} :: pos() + , type :: error_type() + , message :: iolist() + , context = none :: none | iolist() + }). + +-opaque error() :: #err{}. + +-export_type([error/0, pos/0]). + +-export([ err_msg/1 + , msg/1 + , new/3 + , new/4 + , pos/2 + , pos/3 + , pp/1 + , type/1 + ]). + +new(Type, Pos, Msg) -> + #err{ type = Type, pos = Pos, message = Msg }. + +new(Type, Pos, Msg, Ctxt) -> + #err{ type = Type, pos = Pos, message = Msg, context = Ctxt }. + +pos(Line, Col) -> + #pos{ line = Line, col = Col }. + +pos(File, Line, Col) -> + #pos{ file = File, line = Line, col = Col }. + +msg(#err{ message = Msg, context = none }) -> Msg; +msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt. + +err_msg(#err{ pos = Pos } = Err) -> + lists:flatten(io_lib:format("~s~s", [str_pos(Pos), msg(Err)])). + +str_pos(#pos{file = no_file, line = L, col = C}) -> + io_lib:format("~p:~p:", [L, C]); +str_pos(#pos{file = F, line = L, col = C}) -> + io_lib:format("~s:~p:~p:", [F, L, C]). + +type(#err{ type = Type }) -> Type. + +pp(#err{ pos = Pos } = Err) -> + lists:flatten(io_lib:format("~s\n~s", [pp_pos(Pos), msg(Err)])). + +pp_pos(#pos{file = no_file, line = L, col = C}) -> + io_lib:format("At line ~p, col ~p:", [L, C]); +pp_pos(#pos{file = F, line = L, col = C}) -> + io_lib:format("In '~s' at line ~p, col~p:", [F, L, C]). diff --git a/src/aeso_parse_lib.erl b/src/aeso_parse_lib.erl index 21f3df5..de1b575 100644 --- a/src/aeso_parse_lib.erl +++ b/src/aeso_parse_lib.erl @@ -9,7 +9,7 @@ -module(aeso_parse_lib). -export([parse/2, - return/1, fail/0, fail/1, map/2, bind/2, + return/1, fail/0, fail/1, fail/2, map/2, bind/2, lazy/1, choice/1, choice/2, tok/1, layout/0, left/2, right/2, between/3, optional/1, many/1, many1/1, sep/2, sep1/2, @@ -98,6 +98,10 @@ apply_p(X, K) -> K(X). -spec lazy(fun(() -> parser(A))) -> parser(A). lazy(Delayed) -> ?lazy(Delayed). +%% @doc A parser that always fails at a known location. +-spec fail(pos(), term()) -> parser(none()). +fail(Pos, Err) -> ?fail({Pos, Err}). + %% @doc A parser that always fails. -spec fail(term()) -> parser(none()). fail(Err) -> ?fail(Err). @@ -322,6 +326,8 @@ current_pos(#ts{ tokens = [T | _] }) -> pos(T); current_pos(#ts{ last = T }) -> end_pos(pos(T)). -spec mk_error(#ts{}, term()) -> error(). +mk_error(_Ts, {Pos, Err}) -> + {Pos, Err}; mk_error(Ts, Err) -> {current_pos(Ts), Err}. diff --git a/src/aeso_parse_lib.hrl b/src/aeso_parse_lib.hrl index 6930d12..1b85252 100644 --- a/src/aeso_parse_lib.hrl +++ b/src/aeso_parse_lib.hrl @@ -19,7 +19,7 @@ -import(aeso_parse_lib, [tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2, infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0, - fail/0, fail/1, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2, + fail/0, fail/1, fail/2, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2, left/2, right/2, optional/1]). diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 38e6d64..97c5ae2 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -12,9 +12,7 @@ -include("aeso_parse_lib.hrl"). --type parse_result() :: {ok, aeso_syntax:ast()} - | {error, {aeso_parse_lib:pos(), atom(), term()}} - | {error, {aeso_parse_lib:pos(), atom()}}. +-type parse_result() :: aeso_syntax:ast() | none(). -type include_hash() :: {string(), binary()}. @@ -33,13 +31,19 @@ string(String, Opts) -> string(String, Included, Opts) -> case parse_and_scan(file(), String, Opts) of {ok, AST} -> - expand_includes(AST, Included, Opts); - Err = {error, _} -> - Err + case expand_includes(AST, Included, Opts) of + {ok, AST1} -> AST1; + {error, Err} -> throw({parse_errors, [mk_error(Err)]}) + end; + {error, Err} -> + throw({parse_errors, [mk_error(Err)]}) end. type(String) -> - parse_and_scan(type(), String, []). + case parse_and_scan(type(), String, []) of + {ok, AST} -> {ok, AST}; + {error, Err} -> {error, [mk_error(Err)]} + end. parse_and_scan(P, S, Opts) -> set_current_file(proplists:get_value(src_file, Opts, no_file)), @@ -48,6 +52,24 @@ parse_and_scan(P, S, Opts) -> Error -> Error end. +mk_p_err(Pos, Msg) -> + aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)). + +mk_error({Pos, ScanE}) when ScanE == scan_error; ScanE == scan_error_no_state -> + mk_p_err(Pos, "Scan error\n"); +mk_error({Pos, parse_error, Err}) -> + Msg = io_lib:format("~s\n", [Err]), + mk_p_err(Pos, Msg); +mk_error({Pos, ambiguous_parse, As}) -> + Msg = io_lib:format("Ambiguous parse result: ~p\n", [As]), + mk_p_err(Pos, Msg); +mk_error({Pos, include_error, File}) -> + Msg = io_lib:format("Couldn't find include file '~s'\n", [File]), + mk_p_err(Pos, Msg). + +mk_pos({Line, Col}) -> aeso_errors:pos(Line, Col); +mk_pos({File, Line, Col}) -> aeso_errors:pos(File, Line, Col). + %% -- Parsing rules ---------------------------------------------------------- file() -> choice([], block(decl())). @@ -533,14 +555,9 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E). parse_field_pattern({field, Ann, F, E}) -> {field, Ann, F, parse_pattern(E)}. -return_error({no_file, L, C}, Err) -> - fail(io_lib:format("~p:~p:\n~s", [L, C, Err])); -return_error({F, L, C}, Err) -> - fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])). - -spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()). ret_doc_err(Ann, Doc) -> - return_error(ann_pos(Ann), prettypr:format(Doc)). + fail(ann_pos(Ann), prettypr:format(Doc)). -spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). bad_expr_err(Reason, E) -> @@ -600,7 +617,7 @@ stdlib_options() -> get_include_code(File, Ann, Opts) -> case {read_file(File, Opts), read_file(File, stdlib_options())} of {{ok, _}, {ok,_ }} -> - return_error(ann_pos(Ann), "Illegal redefinition of standard library " ++ File); + fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File); {_, {ok, Bin}} -> {ok, binary_to_list(Bin)}; {{ok, Bin}, _} -> diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 3042f92..481c8c7 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -161,8 +161,10 @@ permissive_literals_fail_test() -> "contract OracleTest =\n" " stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n" " Chain.spend(o, 1000000)\n", - {error, <<"Type errors\nCannot unify", _/binary>>} = - aeso_compiler:check_call(Contract, "haxx", ["#123"], []), + {error, [Err]} = + aeso_compiler:check_call(Contract, "haxx", ["#123"], []), + ?assertMatch("Cannot unify" ++ _, aeso_errors:pp(Err)), + ?assertEqual(type_error, aeso_errors:type(Err)), ok. encode_decode_calldata(FunName, Types, Args) -> diff --git a/test/aeso_aci_tests.erl b/test/aeso_aci_tests.erl index eee30ae..ec6737c 100644 --- a/test/aeso_aci_tests.erl +++ b/test/aeso_aci_tests.erl @@ -107,11 +107,11 @@ aci_test_contract(Name) -> check_stub(Stub, Options) -> case aeso_parser:string(binary_to_list(Stub), Options) of - {ok, Ast} -> + Ast -> try %% io:format("AST: ~120p\n", [Ast]), aeso_ast_infer_types:infer(Ast, []) - catch _:{type_errors, TE} -> + catch throw:{type_errors, TE} -> io:format("Type error:\n~s\n", [TE]), error(TE); _:R -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 6ba37e1..39efe4c 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -35,10 +35,10 @@ simple_compile_test_() -> [ {"Testing error messages of " ++ ContractName, fun() -> case compile(aevm, ContractName) of - <<"Type errors\n", ErrorString/binary>> -> - check_errors(lists:sort(ExpectedErrors), ErrorString); <<"Parse errors\n", ErrorString/binary>> -> - check_errors(lists:sort(ExpectedErrors), ErrorString) + check_errors(lists:sort(ExpectedErrors), ErrorString); + Errors -> + check_errors(lists:sort(ExpectedErrors), Errors) end end} || {ContractName, ExpectedErrors} <- failing_contracts() ] ++ @@ -65,9 +65,8 @@ simple_compile_test_() -> ok end} || Backend <- [aevm, fate] ]. -check_errors(Expect, ErrorString) -> - %% This removes the final single \n as well. - Actual = binary:split(<>, <<"\n\n">>, [global,trim]), +check_errors(Expect, Actual0) -> + Actual = [ list_to_binary(string:trim(aeso_errors:msg(Err))) || Err <- Actual0 ], case {Expect -- Actual, Actual -- Expect} of {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); @@ -81,8 +80,9 @@ compile(Backend, Name) -> compile(Backend, Name, Options) -> String = aeso_test_utils:read_contract(Name), case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of - {ok, Map} -> Map; - {error, ErrorString} -> ErrorString + {ok, Map} -> Map; + {error, ErrorString} when is_binary(ErrorString) -> ErrorString; + {error, Errors} -> Errors end. %% compilable_contracts() -> [ContractName]. @@ -353,8 +353,7 @@ failing_contracts() -> "but it calls\n" " - state (at line 13, column 13)">>]} , {"field_parse_error", - [<<"line 6, column 1: In field_parse_error at 5:26:\n" - "Cannot use nested fields or keys in record construction: p.x\n">>]} + [<<"Cannot use nested fields or keys in record construction: p.x">>]} , {"modifier_checks", [<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>, <<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>, diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index ea6c517..7197bd3 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -39,7 +39,7 @@ simple_contracts_test_() -> RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end, NonAssoc = fun(Op) -> OpAtom = list_to_atom(Op), - ?assertError({error, {_, parse_error, _}}, + ?assertThrow({parse_errors, [_]}, parse_expr(NoPar({a, Op, {b, Op, c}}))) end, Stronger = fun(Op1, Op2) -> CheckParens({{a, Op1, b}, Op2, c}), @@ -74,10 +74,7 @@ roundtrip_contract(Name) -> parse_string(Text) -> parse_string(Text, []). parse_string(Text, Opts) -> - case aeso_parser:string(Text, Opts) of - {ok, Contract} -> Contract; - Err -> error(Err) - end. + aeso_parser:string(Text, Opts). parse_expr(Text) -> [{letval, _, _, _, Expr}] = From e37ac44726910934c4389eedcc459b9c8da7b504 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 10:17:10 +0200 Subject: [PATCH 03/20] Ensure that init is not payable --- src/aeso_ast_infer_types.erl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index a8a7181..c064a59 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -717,12 +717,15 @@ check_modifiers1(What, Decls) when is_list(Decls) -> check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) == fun_decl -> Public = aeso_syntax:get_ann(public, Decl, false), Private = aeso_syntax:get_ann(private, Decl, false), + Payable = aeso_syntax:get_ann(payable, Decl, false), Entrypoint = aeso_syntax:get_ann(entrypoint, Decl, false), FunDecl = element(1, Decl) == fun_decl, {id, _, Name} = element(3, Decl), + IsInit = Name == "init" andalso What == contract, _ = [ type_error({proto_must_be_entrypoint, Decl}) || FunDecl, Private orelse not Entrypoint, What == contract ], _ = [ type_error({proto_in_namespace, Decl}) || FunDecl, What == namespace ], - _ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, Name == "init", What == contract ], + _ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, IsInit ], + _ = [ type_error({init_must_not_be_payable, Decl}) || Payable, IsInit ], _ = [ type_error({public_modifier_in_contract, Decl}) || Public, not Private, not Entrypoint, What == contract ], _ = [ type_error({entrypoint_in_namespace, Decl}) || Entrypoint, What == namespace ], _ = [ type_error({private_entrypoint, Decl}) || Private, Entrypoint ], @@ -2239,6 +2242,12 @@ mk_error({init_must_be_an_entrypoint, Decl}) -> [pp_loc(Decl), prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]), mk_t_err(pos(Decl), Msg); +mk_error({init_must_not_be_payable, Decl}) -> + Msg = io_lib:format("The init function (at ~s) cannot be payable.\n" + "You don't need the 'payable' annotation to be able to attach\n" + "funds to the create contract transaction.", + [pp_loc(Decl)]), + mk_t_err(pos(Decl), Msg); mk_error({proto_must_be_entrypoint, Decl}) -> Decl1 = mk_entrypoint(Decl), Msg = io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n", From db7bf7a730ad39772d3f794c01679f6065cbcac2 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 10:18:22 +0200 Subject: [PATCH 04/20] Set error msg position to last occurrence of duplicate definition --- 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 c064a59..11ecae1 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2195,7 +2195,7 @@ mk_error({reserved_entrypoint, Name, Def}) -> mk_error({duplicate_definition, Name, Locs}) -> Msg = io_lib:format("Duplicate definitions of ~s at\n~s", [Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]), - mk_t_err(pos(hd(Locs)), Msg); + mk_t_err(pos(lists:last(Locs)), Msg); mk_error({duplicate_scope, Kind, Name, OtherKind, L}) -> Msg = io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]), From f2469a676da1b392a1bc3ed4aa9c07725d7020ac Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 10:20:26 +0200 Subject: [PATCH 05/20] Refactor builtin compilation in icode - Eta expand instead of failing on unapplied builtins --- src/aeso_ast_to_icode.erl | 563 +++++++++++++++++++++----------------- 1 file changed, 305 insertions(+), 258 deletions(-) diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 38bba87..728d60b 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -140,20 +140,7 @@ ast_type(T, Icode) -> -define(option_t(A), {app_t, _, {id, _, "option"}, [A]}). -define(map_t(K, V), {app_t, _, {id, _, "map"}, [K, V]}). -ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) -> - prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []}); - -ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_name := Con }) -> - aeso_builtins:check_event_type(Icode), - builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]); - %% Chain environment -ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) -> - #prim_balance{ address = ast_body(Address, Icode) }; -ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) -> - builtin_call(block_hash, [ast_body(Height, Icode)]); -ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) -> - prim_gas_left; ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address; ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator; ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address }; @@ -166,133 +153,19 @@ ast_body({qid, _, ["Chain", "timestamp"]}, _Icode) -> prim_timestamp; ast_body({qid, _, ["Chain", "block_height"]}, _Icode) -> prim_block_height; ast_body({qid, _, ["Chain", "difficulty"]}, _Icode) -> prim_difficulty; ast_body({qid, _, ["Chain", "gas_limit"]}, _Icode) -> prim_gas_limit; -%% TODO: eta expand! -ast_body({qid, _, ["Chain", "balance"]}, _Icode) -> - gen_error({underapplied_primitive, 'Chain.balance'}); -ast_body({qid, _, ["Chain", "block_hash"]}, _Icode) -> - gen_error({underapplied_primitive, 'Chain.block_hash'}); -ast_body({qid, _, ["Chain", "spend"]}, _Icode) -> - gen_error({underapplied_primitive, 'Chain.spend'}); %% State ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state; ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) -> #prim_put{ state = ast_body(NewState, Icode) }; -ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) -> - gen_error({underapplied_primitive, put}); %% TODO: eta - -%% Abort -ast_body(?id_app("abort", [String], _, _), Icode) -> - builtin_call(abort, [ast_body(String, Icode)]); -ast_body(?id_app("require", [Bool, String], _, _), Icode) -> - builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]); +ast_body({typed, _, Id = {qid, _, [Con, "put"]}, Type}, Icode = #{ contract_name := Con }) -> + eta_expand(Id, Type, Icode); %% Authentication ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) -> prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0}, [], [], aeso_icode:option_typerep(word)); -%% Oracles -ast_body(?qid_app(["Oracle", "register"], Args, _, ?oracle_t(QType, RType)), Icode) -> - {Sign, [Acct, QFee, TTL]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0}, - [ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode), - ast_type_value(QType, Icode), ast_type_value(RType, Icode)], - [word, sign_t(), word, ttl_t(Icode), typerep, typerep], word); - -ast_body(?qid_app(["Oracle", "query_fee"], [Oracle], _, _), Icode) -> - prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0}, - [ast_body(Oracle, Icode)], [word], word); - -ast_body(?qid_app(["Oracle", "query"], [Oracle, Q, QFee, QTTL, RTTL], [_, QType, _, _, _], _), Icode) -> - prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode), - [ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)], - [word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word); - -ast_body(?qid_app(["Oracle", "extend"], Args, _, _), Icode) -> - {Sign, [Oracle, TTL]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0}, - [ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)], - [word, sign_t(), ttl_t(Icode)], {tuple, []}); - -ast_body(?qid_app(["Oracle", "respond"], Args, [_, _, RType], _), Icode) -> - {Sign, [Oracle, Query, R]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0}, - [ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)], - [word, word, sign_t(), ast_type(RType, Icode)], {tuple, []}); - -ast_body(?qid_app(["Oracle", "get_question"], [Oracle, Q], [_, ?query_t(QType, _)], _), Icode) -> - prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0}, - [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode)); - -ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)], _), Icode) -> - prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0}, - [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode))); - -ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) -> - prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0}, - [ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)], - [word, typerep, typerep], word); - -ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) -> - prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0}, - [ast_body(Oracle, Icode), ast_body(Query, Icode), - ast_type_value(Q, Icode), ast_type_value(R, Icode)], - [word, typerep, typerep], word); - -ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'}); -ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'}); -ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'}); -ast_body({qid, _, ["Oracle", "respond"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.respond'}); -ast_body({qid, _, ["Oracle", "query_fee"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query_fee'}); -ast_body({qid, _, ["Oracle", "get_answer"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.get_answer'}); -ast_body({qid, _, ["Oracle", "get_question"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.get_question'}); - -%% Name service -ast_body(?qid_app(["AENS", "resolve"], [Name, Key], _, ?option_t(Type)), Icode) -> - case is_monomorphic(Type) of - true -> - case ast_type(Type, Icode) of - T when T == word; T == string -> ok; - _ -> gen_error({invalid_result_type, 'AENS.resolve', Type}) - end, - prim_call(?PRIM_CALL_AENS_RESOLVE, #integer{value = 0}, - [ast_body(Name, Icode), ast_body(Key, Icode), ast_type_value(Type, Icode)], - [string, string, typerep], aeso_icode:option_typerep(ast_type(Type, Icode))); - false -> - gen_error({unresolved_result_type, 'AENS.resolve', Type}) - end; - -ast_body(?qid_app(["AENS", "preclaim"], Args, _, _), Icode) -> - {Sign, [Addr, CHash]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_AENS_PRECLAIM, #integer{value = 0}, - [ast_body(Addr, Icode), ast_body(CHash, Icode), ast_body(Sign, Icode)], - [word, word, sign_t()], {tuple, []}); - -ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) -> - {Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0}, - [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)], - [word, string, word, word, sign_t()], {tuple, []}); - -ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) -> - {Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0}, - [ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], - [word, word, word, sign_t()], {tuple, []}); - -ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) -> - {Sign, [Addr, Name]} = get_signature_arg(Args), - prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0}, - [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], - [word, word, sign_t()], {tuple, []}); - -ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'}); -ast_body({qid, _, ["AENS", "preclaim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.preclaim'}); -ast_body({qid, _, ["AENS", "claim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.claim'}); -ast_body({qid, _, ["AENS", "transfer"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.transfer'}); -ast_body({qid, _, ["AENS", "revoke"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.revoke'}); - %% Maps %% -- map lookup m[k] @@ -306,35 +179,6 @@ ast_body({map_get, _, Map, Key, Val}, Icode) -> Fun = {map_lookup_default, ast_typerep(ValType, Icode)}, builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]); -%% -- lookup functions -ast_body(?qid_app(["Map", "lookup"], [Key, Map], _, _), Icode) -> - map_get(Key, Map, Icode); -ast_body(?qid_app(["Map", "lookup_default"], [Key, Map, Val], _, _), Icode) -> - {_, ValType} = check_monomorphic_map(Map, Icode), - Fun = {map_lookup_default, ast_typerep(ValType, Icode)}, - builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]); -ast_body(?qid_app(["Map", "member"], [Key, Map], _, _), Icode) -> - builtin_call(map_member, [ast_body(Map, Icode), ast_body(Key, Icode)]); -ast_body(?qid_app(["Map", "size"], [Map], _, _), Icode) -> - builtin_call(map_size, [ast_body(Map, Icode)]); -ast_body(?qid_app(["Map", "delete"], [Key, Map], _, _), Icode) -> - map_del(Key, Map, Icode); - -%% -- map conversion to/from list -ast_body(App = ?qid_app(["Map", "from_list"], [List], _, MapType), Icode) -> - Ann = aeso_syntax:get_ann(App), - {KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), - builtin_call(map_from_list, [ast_body(List, Icode), map_empty(KeyType, ValType, Icode)]); - -ast_body(?qid_app(["Map", "to_list"], [Map], _, _), Icode) -> - map_tolist(Map, Icode); - -ast_body({qid, _, ["Map", "from_list"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.from_list'}); -%% ast_body({qid, _, ["Map", "to_list"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.to_list'}); -ast_body({qid, _, ["Map", "lookup"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.lookup'}); -ast_body({qid, _, ["Map", "lookup_default"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.lookup_default'}); -ast_body({qid, _, ["Map", "member"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.member'}); - %% -- map construction { k1 = v1, k2 = v2 } ast_body({typed, Ann, {map, _, KVs}, MapType}, Icode) -> {KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), @@ -356,104 +200,22 @@ ast_body({map, _, Map, [Upd]}, Icode) -> ast_body({map, Ann, Map, [Upd | Upds]}, Icode) -> ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode); -%% Crypto -ast_body(?qid_app(["Crypto", "verify_sig"], [Msg, PK, Sig], _, _), Icode) -> - prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0}, - [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], - [word, word, sign_t()], word); - -ast_body(?qid_app(["Crypto", "verify_sig_secp256k1"], [Msg, PK, Sig], _, _), Icode) -> - prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0}, - [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], - [bytes_t(32), bytes_t(64), bytes_t(64)], word); - -ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, Addr, Sig], _, _), Icode) -> - prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0}, - [ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)], - [word, bytes_t(20), bytes_t(65)], word); - -ast_body(?qid_app(["Crypto", "ecrecover_secp256k1"], [Msg, Sig], _, _), Icode) -> - prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0}, - [ast_body(Msg, Icode), ast_body(Sig, Icode)], - [word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20))); - -ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) -> - generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode); -ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) -> - generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode); -ast_body(?qid_app(["Crypto", "blake2b"], [Term], [Type], _), Icode) -> - generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode); -ast_body(?qid_app(["String", "sha256"], [String], _, _), Icode) -> - string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode); -ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) -> - string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode); - -%% Strings -%% -- String length -ast_body(?qid_app(["String", "length"], [String], _, _), Icode) -> - builtin_call(string_length, [ast_body(String, Icode)]); - -%% -- String concat -ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) -> - builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]); - -%% -- String hash (sha3) -ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> - #unop{ op = 'sha3', rand = ast_body(String, Icode) }; - %% -- Bits -ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode) - when Fun == "test"; Fun == "set"; Fun == "clear"; - Fun == "union"; Fun == "intersection"; Fun == "difference" -> - C = fun(N) when is_integer(N) -> #integer{ value = N }; - (X) -> X end, - Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end, - And = Bin('band'), - Or = Bin('bor'), - Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments - Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end, - Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end, - case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of - ["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1); - ["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix)); - ["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix))); - ["union", A, B] -> Or(A, B); - ["intersection", A, B] -> And(A, B); - ["difference", A, B] -> And(A, Neg(And(A, B))) - end; ast_body({qid, _, ["Bits", "none"]}, _Icode) -> #integer{ value = 0 }; ast_body({qid, _, ["Bits", "all"]}, _Icode) -> #integer{ value = 1 bsl 256 - 1 }; -ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) -> - builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]); %% -- Conversion -ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) -> - builtin_call(int_to_str, [ast_body(Int, Icode)]); - -ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) -> - builtin_call(addr_to_str, [ast_body(Addr, Icode)]); -ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) -> - prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0}, - [ast_body(Addr, Icode)], [word], word); -ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) -> - prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0}, - [ast_body(Addr, Icode)], [word], word); -ast_body(?qid_app(["Address", "is_payable"], [Addr], _, _), Icode) -> - prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0}, - [ast_body(Addr, Icode)], [word], word); - -ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) -> - {typed, _, _, {bytes_t, _, N}} = Bytes, - builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]); -ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) -> - {typed, _, _, {bytes_t, _, N}} = Bytes, - builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]); %% Other terms ast_body({id, _, Name}, _Icode) -> #var_ref{name = Name}; +ast_body({typed, _, Id = {qid, _, Name}, Type}, Icode) -> + case is_builtin_fun(Name, Icode) of + true -> eta_expand(Id, Type, Icode); + false -> ast_body(Id, Icode) + end; ast_body({qid, _, Name}, _Icode) -> #var_ref{name = Name}; ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints @@ -482,16 +244,12 @@ ast_body({list,_,Args}, Icode) -> %% Typed contract calls ast_body({proj, _, {typed, _, Addr, {con, _, _}}, {id, _, "address"}}, Icode) -> ast_body(Addr, Icode); %% Values of contract types _are_ addresses. -ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id, _, FunName}}, +ast_body({app, _, {typed, _, {proj, _, Addr, {id, _, FunName}}, {fun_t, _, NamedT, ArgsT, OutT}}, Args0}, Icode) -> NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0], Args = Args0 -- NamedArgs, ArgOpts = [ {Name, ast_body(Value, Icode)} || {named_arg, _, {id, _, Name}, Value} <- NamedArgs ], Defaults = [ {Name, ast_body(Default, Icode)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedT ], - %% TODO: eta expand - length(Args) /= length(ArgsT) andalso - gen_error({underapplied_contract_call, - string:join([Contract, FunName], ".")}), ArgsI = [ ast_body(Arg, Icode) || Arg <- Args ], ArgType = ast_typerep({tuple_t, [], ArgsT}), Gas = proplists:get_value("gas", ArgOpts ++ Defaults), @@ -509,9 +267,8 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id %% entrypoint on the callee side. type_hash= #integer{value = 0} }; -ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode) -> - gen_error({underapplied_contract_call, - string:join([Contract, FunName], ".")}); +ast_body(Call = {proj, _, {typed, _, _, {con, _, _Contract}}, {id, _, _FunName}}, _Icode) -> + gen_error({unapplied_contract_call, Call}); ast_body({con, _, Name}, Icode) -> Tag = aeso_icode:get_constructor_tag([Name], Icode), @@ -529,7 +286,7 @@ ast_body({app, _, {'..', _}, [A, B]}, Icode) -> #funcall { function = #var_ref{ name = ["ListInternal", "from_to"] } , args = [ast_body(A, Icode), ast_body(B, Icode)] }; -ast_body({app,As,Fun,Args}, Icode) -> +ast_body({app, As, Fun, Args}, Icode) -> case aeso_syntax:get_ann(format, As) of infix -> {Op, _} = Fun, @@ -540,8 +297,13 @@ ast_body({app,As,Fun,Args}, Icode) -> [A] = Args, #unop{op = Op, rand = ast_body(A, Icode)}; _ -> - #funcall{function=ast_body(Fun, Icode), - args=[ast_body(A, Icode) || A <- Args]} + {typed, _, Fun1, {fun_t, _, _, ArgsT, RetT}} = Fun, + case is_builtin_fun(Fun1, Icode) of + true -> builtin_code(As, Fun1, Args, ArgsT, RetT, Icode); + false -> + #funcall{function=ast_body(Fun, Icode), + args=[ast_body(A, Icode) || A <- Args]} + end end; ast_body({list_comp, _, Yield, []}, Icode) -> #list{elems = [ast_body(Yield, Icode)]}; @@ -600,8 +362,6 @@ ast_body({typed,_,{record,Attrs,Fields},{record_t,DefFields}}, Icode) -> ast_body(E, Icode) end || {field_t,_,{id,_,Name},_} <- DefFields]}; -ast_body({typed,_,{record,Attrs,_Fields},T}, _Icode) -> - gen_error({record_has_bad_type,Attrs,T}); ast_body({proj,_,{typed,_,Record,{record_t,Fields}},{id,_,FieldName}}, Icode) -> [Index] = [I || {I,{field_t,_,{id,_,Name},_}} <- @@ -670,6 +430,293 @@ ast_binop('++', _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. +is_builtin_fun({qid, _, ["Chain","spend"]}, _Icode) -> true; +is_builtin_fun({qid, _, [Con, "Chain", "event"]}, #{ contract_name := Con }) -> true; +is_builtin_fun({qid, _, ["Chain", "balance"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Chain", "block_hash"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Call", "gas_left"]}, _Icode) -> true; +is_builtin_fun({id, _, "abort"}, _Icode) -> true; +is_builtin_fun({id, _, "require"}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "register"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "query_fee"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "query"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "extend"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "respond"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "get_question"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "get_answer"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "check"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Oracle", "check_query"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["AENS", "resolve"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["AENS", "preclaim"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["AENS", "claim"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["AENS", "transfer"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["AENS", "revoke"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "lookup"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "lookup_default"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "member"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "size"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "delete"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "from_list"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Map", "to_list"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "verify_sig"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "verify_sig_secp256k1"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "ecverify_secp256k1"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "ecrecover_secp256k1"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "sha3"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "sha256"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Crypto", "blake2b"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["String", "sha256"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["String", "blake2b"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["String", "length"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["String", "concat"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["String", "sha3"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "test"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "set"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "clear"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "union"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "intersection"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "difference"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Bits", "sum"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Int", "to_str"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Address", "to_str"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Address", "is_oracle"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["Address", "is_contract"]}, _Icode) -> true; +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(_, _) -> false. + +%% -- Code generation for builtin functions -- + +%% Chain operations +builtin_code(_, {qid, _, ["Chain","spend"]}, [To, Amount], _, _, Icode) -> + prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []}); + +builtin_code(_, {qid, _, [Con, "Chain", "event"]}, [Event], _, _, Icode = #{ contract_name := Con }) -> + aeso_builtins:check_event_type(Icode), + builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]); + +builtin_code(_, {qid, _, ["Chain", "balance"]}, [Address], _, _, Icode) -> + #prim_balance{ address = ast_body(Address, Icode) }; +builtin_code(_, {qid, _, ["Chain", "block_hash"]}, [Height], _, _, Icode) -> + builtin_call(block_hash, [ast_body(Height, Icode)]); +builtin_code(_, {qid, _, ["Call", "gas_left"]}, [], _, _, _Icode) -> + prim_gas_left; + +%% Abort +builtin_code(_, {id, _, "abort"}, [String], _, _, Icode) -> + builtin_call(abort, [ast_body(String, Icode)]); +builtin_code(_, {id, _, "require"}, [Bool, String], _, _, Icode) -> + builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]); + +%% Oracles +builtin_code(_, {qid, _, ["Oracle", "register"]}, Args, _, ?oracle_t(QType, RType), Icode) -> + {Sign, [Acct, QFee, TTL]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0}, + [ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode), + ast_type_value(QType, Icode), ast_type_value(RType, Icode)], + [word, sign_t(), word, ttl_t(Icode), typerep, typerep], word); + +builtin_code(_, {qid, _, ["Oracle", "query_fee"]}, [Oracle], _, _, Icode) -> + prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0}, + [ast_body(Oracle, Icode)], [word], word); + +builtin_code(_, {qid, _, ["Oracle", "query"]}, [Oracle, Q, QFee, QTTL, RTTL], [_, QType, _, _, _], _, Icode) -> + prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode), + [ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)], + [word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word); + +builtin_code(_, {qid, _, ["Oracle", "extend"]}, Args, _, _, Icode) -> + {Sign, [Oracle, TTL]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0}, + [ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)], + [word, sign_t(), ttl_t(Icode)], {tuple, []}); + +builtin_code(_, {qid, _, ["Oracle", "respond"]}, Args, [_, _, RType], _, Icode) -> + {Sign, [Oracle, Query, R]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0}, + [ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)], + [word, word, sign_t(), ast_type(RType, Icode)], {tuple, []}); + +builtin_code(_, {qid, _, ["Oracle", "get_question"]}, [Oracle, Q], [_, ?query_t(QType, _)], _, Icode) -> + prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0}, + [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode)); + +builtin_code(_, {qid, _, ["Oracle", "get_answer"]}, [Oracle, Q], [_, ?query_t(_, RType)], _, Icode) -> + prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0}, + [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode))); + +builtin_code(_, {qid, _, ["Oracle", "check"]}, [Oracle], [?oracle_t(Q, R)], _, Icode) -> + prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0}, + [ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)], + [word, typerep, typerep], word); + +builtin_code(_, {qid, _, ["Oracle", "check_query"]}, [Oracle, Query], [_, ?query_t(Q, R)], _, Icode) -> + prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0}, + [ast_body(Oracle, Icode), ast_body(Query, Icode), + ast_type_value(Q, Icode), ast_type_value(R, Icode)], + [word, typerep, typerep], word); + +%% Name service +builtin_code(_, {qid, Ann, ["AENS", "resolve"]}, [Name, Key], _, ?option_t(Type), Icode) -> + case is_monomorphic(Type) of + true -> + case ast_type(Type, Icode) of + T when T == word; T == string -> ok; + _ -> gen_error({invalid_aens_resolve_type, Ann, Type}) + end, + prim_call(?PRIM_CALL_AENS_RESOLVE, #integer{value = 0}, + [ast_body(Name, Icode), ast_body(Key, Icode), ast_type_value(Type, Icode)], + [string, string, typerep], aeso_icode:option_typerep(ast_type(Type, Icode))); + false -> + gen_error({invalid_aens_resolve_type, Ann, Type}) + end; + +builtin_code(_, {qid, _, ["AENS", "preclaim"]}, Args, _, _, Icode) -> + {Sign, [Addr, CHash]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_AENS_PRECLAIM, #integer{value = 0}, + [ast_body(Addr, Icode), ast_body(CHash, Icode), ast_body(Sign, Icode)], + [word, word, sign_t()], {tuple, []}); + +builtin_code(_, {qid, _, ["AENS", "claim"]}, Args, _, _, Icode) -> + {Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0}, + [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)], + [word, string, word, word, sign_t()], {tuple, []}); + +builtin_code(_, {qid, _, ["AENS", "transfer"]}, Args, _, _, Icode) -> + {Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0}, + [ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], + [word, word, word, sign_t()], {tuple, []}); + +builtin_code(_, {qid, _, ["AENS", "revoke"]}, Args, _, _, Icode) -> + {Sign, [Addr, Name]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0}, + [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], + [word, word, sign_t()], {tuple, []}); + +%% -- Maps +%% -- lookup functions +builtin_code(_, {qid, _, ["Map", "lookup"]}, [Key, Map], _, _, Icode) -> + map_get(Key, Map, Icode); +builtin_code(_, {qid, _, ["Map", "lookup_default"]}, [Key, Map, Val], _, _, Icode) -> + {_, ValType} = check_monomorphic_map(Map, Icode), + Fun = {map_lookup_default, ast_typerep(ValType, Icode)}, + builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]); +builtin_code(_, {qid, _, ["Map", "member"]}, [Key, Map], _, _, Icode) -> + builtin_call(map_member, [ast_body(Map, Icode), ast_body(Key, Icode)]); +builtin_code(_, {qid, _, ["Map", "size"]}, [Map], _, _, Icode) -> + builtin_call(map_size, [ast_body(Map, Icode)]); +builtin_code(_, {qid, _, ["Map", "delete"]}, [Key, Map], _, _, Icode) -> + map_del(Key, Map, Icode); + +%% -- map conversion to/from list +builtin_code(_, {qid, Ann, ["Map", "from_list"]}, [List], _, MapType, Icode) -> + {KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), + builtin_call(map_from_list, [ast_body(List, Icode), map_empty(KeyType, ValType, Icode)]); + +builtin_code(_, {qid, _, ["Map", "to_list"]}, [Map], _, _, Icode) -> + map_tolist(Map, Icode); + +%% Crypto +builtin_code(_, {qid, _, ["Crypto", "verify_sig"]}, [Msg, PK, Sig], _, _, Icode) -> + prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0}, + [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], + [word, word, sign_t()], word); + +builtin_code(_, {qid, _, ["Crypto", "verify_sig_secp256k1"]}, [Msg, PK, Sig], _, _, Icode) -> + prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0}, + [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], + [bytes_t(32), bytes_t(64), bytes_t(64)], word); + +builtin_code(_, {qid, _, ["Crypto", "ecverify_secp256k1"]}, [Msg, Addr, Sig], _, _, Icode) -> + prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0}, + [ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)], + [word, bytes_t(20), bytes_t(65)], word); + +builtin_code(_, {qid, _, ["Crypto", "ecrecover_secp256k1"]}, [Msg, Sig], _, _, Icode) -> + prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0}, + [ast_body(Msg, Icode), ast_body(Sig, Icode)], + [word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20))); + +builtin_code(_, {qid, _, ["Crypto", "sha3"]}, [Term], [Type], _, Icode) -> + generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode); +builtin_code(_, {qid, _, ["Crypto", "sha256"]}, [Term], [Type], _, Icode) -> + generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode); +builtin_code(_, {qid, _, ["Crypto", "blake2b"]}, [Term], [Type], _, Icode) -> + generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode); +builtin_code(_, {qid, _, ["String", "sha256"]}, [String], _, _, Icode) -> + string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode); +builtin_code(_, {qid, _, ["String", "blake2b"]}, [String], _, _, Icode) -> + string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode); + +%% Strings +%% -- String length +builtin_code(_, {qid, _, ["String", "length"]}, [String], _, _, Icode) -> + builtin_call(string_length, [ast_body(String, Icode)]); + +%% -- String concat +builtin_code(_, {qid, _, ["String", "concat"]}, [String1, String2], _, _, Icode) -> + builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]); + +%% -- String hash (sha3) +builtin_code(_, {qid, _, ["String", "sha3"]}, [String], _, _, Icode) -> + #unop{ op = 'sha3', rand = ast_body(String, Icode) }; + +builtin_code(_, {qid, _, ["Bits", Fun]}, Args, _, _, Icode) + when Fun == "test"; Fun == "set"; Fun == "clear"; + Fun == "union"; Fun == "intersection"; Fun == "difference" -> + C = fun(N) when is_integer(N) -> #integer{ value = N }; + (X) -> X end, + Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end, + And = Bin('band'), + Or = Bin('bor'), + Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments + Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end, + Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end, + case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of + ["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1); + ["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix)); + ["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix))); + ["union", A, B] -> Or(A, B); + ["intersection", A, B] -> And(A, B); + ["difference", A, B] -> And(A, Neg(And(A, B))) + end; +builtin_code(_, {qid, _, ["Bits", "sum"]}, [Bits], _, _, Icode) -> + builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]); + +builtin_code(_, {qid, _, ["Int", "to_str"]}, [Int], _, _, Icode) -> + builtin_call(int_to_str, [ast_body(Int, Icode)]); + +builtin_code(_, {qid, _, ["Address", "to_str"]}, [Addr], _, _, Icode) -> + builtin_call(addr_to_str, [ast_body(Addr, Icode)]); +builtin_code(_, {qid, _, ["Address", "is_oracle"]}, [Addr], _, _, Icode) -> + prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0}, + [ast_body(Addr, Icode)], [word], word); +builtin_code(_, {qid, _, ["Address", "is_contract"]}, [Addr], _, _, Icode) -> + prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0}, + [ast_body(Addr, Icode)], [word], word); +builtin_code(_, {qid, _, ["Address", "is_payable"]}, [Addr], _, _, Icode) -> + prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0}, + [ast_body(Addr, Icode)], [word], word); + +builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) -> + {typed, _, _, {bytes_t, _, N}} = Bytes, + builtin_call({bytes_to_int, N}, [ast_body(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(_As, Fun, _Args, _ArgsT, _RetT, _Icode) -> + gen_error({missing_code_for, Fun}). + +eta_expand(Id = {_, Ann0, _}, {fun_t, _, _, ArgsT, _}, Icode) -> + Ann = [{origin, system} | Ann0], + Xs = [ {arg, Ann, {id, Ann, "%" ++ integer_to_list(I)}, T} || + {I, T} <- lists:zip(lists:seq(1, length(ArgsT)), ArgsT) ], + Args = [ X || {arg, _, X, _} <- Xs ], + ast_body({lam, Ann, Xs, {app, Ann, Id, Args}}, Icode). + check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map(Ann, MapType, Icode). From 510935d945d577d27e9f5e2fdf38409a7541edf4 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 10:21:37 +0200 Subject: [PATCH 06/20] Framework and tests for code generation (icode/fcode) errors --- src/aeso_aci.erl | 12 +- src/aeso_ast_infer_types.erl | 2 +- src/aeso_ast_to_fcode.erl | 56 ++- src/aeso_ast_to_icode.erl | 39 +- src/aeso_code_errors.erl | 75 ++++ src/aeso_compiler.erl | 39 +- src/aeso_errors.erl | 9 +- src/aeso_parser.erl | 4 +- test/aeso_abi_tests.erl | 2 +- test/aeso_compiler_tests.erl | 402 ++++++++++++------ test/aeso_parser_tests.erl | 2 +- .../code_errors/bad_aens_resolve.aes | 9 + .../code_errors/higher_order_compare.aes | 8 + .../last_declaration_must_be_contract.aes | 2 + .../code_errors/missing_init_function.aes | 3 + .../code_errors/parameterised_event.aes | 4 + .../code_errors/parameterised_state.aes | 4 + .../code_errors/polymorphic_aens_resolve.aes | 7 + .../code_errors/polymorphic_compare.aes | 7 + .../code_errors/polymorphic_entrypoint.aes | 3 + 20 files changed, 481 insertions(+), 208 deletions(-) create mode 100644 src/aeso_code_errors.erl create mode 100644 test/contracts/code_errors/bad_aens_resolve.aes create mode 100644 test/contracts/code_errors/higher_order_compare.aes create mode 100644 test/contracts/code_errors/last_declaration_must_be_contract.aes create mode 100644 test/contracts/code_errors/missing_init_function.aes create mode 100644 test/contracts/code_errors/parameterised_event.aes create mode 100644 test/contracts/code_errors/parameterised_state.aes create mode 100644 test/contracts/code_errors/polymorphic_aens_resolve.aes create mode 100644 test/contracts/code_errors/polymorphic_compare.aes create mode 100644 test/contracts/code_errors/polymorphic_entrypoint.aes diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 8bd329b..7f9f7a9 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -74,19 +74,9 @@ do_contract_interface(Type, ContractString, Options) -> string -> do_render_aci_json(JArray) end catch - throw:{type_errors, Errors} -> {error, Errors}; - %% The compiler errors. - error:{parse_errors, Errors} -> - {error, join_errors("Parse errors", Errors, fun(E) -> E end)}; - error:{code_errors, Errors} -> - {error, join_errors("Code errors", Errors, - fun (E) -> io_lib:format("~p", [E]) end)} + throw:{error, Errors} -> {error, Errors} end. -join_errors(Prefix, Errors, Pfun) -> - Ess = [ Pfun(E) || E <- Errors ], - list_to_binary(string:join([Prefix|Ess], "\n")). - encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> C0 = #{name => encode_name(Name)}, diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 11ecae1..88fdc12 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2069,7 +2069,7 @@ destroy_and_report_type_errors(Env) -> %% io:format("Type errors now: ~p\n", [Errors0]), ets_delete(type_errors), Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ], - Errors == [] orelse throw({type_errors, Errors}). + aeso_errors:throw(Errors). %% No-op if Errors == [] %% Strip current namespace from error message for nicer printing. unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 82c276b..f6ca217 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -232,7 +232,7 @@ is_no_code(Env) -> %% -- Compilation ------------------------------------------------------------ -spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) -> +to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) -> #{ builtins := Builtins } = Env, MainEnv = Env#{ context => {main_contract, Main}, builtins => Builtins#{[Main, "state"] => {get_state, none}, @@ -247,8 +247,10 @@ to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) -> state_type => StateType, event_type => EventType, payable => Payable, - functions => add_init_function(Env1, StateType, + functions => add_init_function(Env1, MainCon, StateType, add_event_function(Env1, EventType, Funs)) }; +to_fcode(_Env, [NotContract]) -> + fcode_error({last_declaration_must_be_contract, NotContract}); to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), to_fcode(Env1, Code); @@ -294,9 +296,10 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R Env#{ functions := NewFuns }. -spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env(). -typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> +typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) -> + check_state_and_event_types(Env, Id, Xs), Q = qname(Env, Name), - FDef = fun(Args) -> + FDef = fun(Args) when length(Args) == length(Xs) -> Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)), case Def of {record_t, Fields} -> {todo, Xs, Args, record_t, Fields}; @@ -307,7 +310,9 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> end || Con <- Cons ], {variant, FCons}; {alias_t, Type} -> {todo, Xs, Args, alias_t, Type} - end end, + end; + (Args) -> internal_error({type_arity_mismatch, Name, length(Args), length(Xs)}) + end, Constructors = case Def of {variant_t, Cons} -> @@ -328,6 +333,14 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> end, bind_type(Env2, Q, FDef). +check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) -> + case Id of + {id, _, "state"} -> fcode_error({parameterized_state, Id}); + {id, _, "event"} -> fcode_error({parameterized_event, Id}); + _ -> ok + end; +check_state_and_event_types(_, _, _) -> ok. + -spec type_to_fcode(env(), aeso_syntax:type()) -> ftype(). type_to_fcode(Env, Type) -> type_to_fcode(Env, #{}, Type). @@ -533,7 +546,7 @@ expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, {builtin_u, B, _} when B =:= aens_resolve -> %% Get the type we are assuming the name resolves to AensType = type_to_fcode(Env, Type), - validate_aens_resolve_type(aeso_syntax:get_ann(Fun), AensType), + validate_aens_resolve_type(aeso_syntax:get_ann(Fun), Type, AensType), TypeArgs = [{lit, {typerep, AensType}}], builtin_to_fcode(B, FArgs ++ TypeArgs); {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs); @@ -616,10 +629,15 @@ validate_oracle_type(Ann, QType, RType) -> ensure_first_order(RType, {higher_order_response_type, Ann, RType}), ok. -validate_aens_resolve_type(Ann, {variant, [[], [Type]]}) -> - ensure_monomorphic(Type, {polymorphic_aens_resolve, Ann, Type}), - ensure_first_order(Type, {higher_order_aens_resolve, Ann, Type}), - ok. +validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) -> + case FType of + string -> ok; + address -> ok; + contract -> ok; + {oracle, _, _} -> ok; + oracle_query -> ok; + _ -> fcode_error({invalid_aens_resolve_type, Ann, Type}) + end. ensure_first_order_entrypoint(Ann, Args, Ret) -> [ ensure_first_order(T, {higher_order_entrypoint_argument, Ann, X, T}) @@ -904,18 +922,18 @@ builtin_to_fcode(Builtin, Args) -> %% -- Init function -- -add_init_function(Env, StateType, Funs0) -> +add_init_function(Env, Main, StateType, Funs0) -> case is_no_code(Env) of true -> Funs0; false -> - Funs = add_default_init_function(Env, StateType, Funs0), + Funs = add_default_init_function(Env, Main, StateType, Funs0), InitName = {entrypoint, <<"init">>}, InitFun = #{ body := InitBody} = maps:get(InitName, Funs), Funs#{ InitName => InitFun#{ return => {tuple, []}, body => {builtin, set_state, [InitBody]} } } end. -add_default_init_function(_Env, StateType, Funs) -> +add_default_init_function(_Env, Main, StateType, Funs) -> InitName = {entrypoint, <<"init">>}, case maps:get(InitName, Funs, none) of %% Only add default init function if state is unit. @@ -924,7 +942,7 @@ add_default_init_function(_Env, StateType, Funs) -> args => [], return => {tuple, []}, body => {tuple, []}} }; - none -> fcode_error(missing_init_function); + none -> fcode_error({missing_init_function, Main}); _ -> Funs end. @@ -1115,7 +1133,7 @@ lookup_type(Env, {qid, _, Name}, Args) -> lookup_type(Env, Name, Args); lookup_type(Env, Name, Args) -> case lookup_type(Env, Name, Args, not_found) of - not_found -> error({unknown_type, Name}); + not_found -> internal_error({unknown_type, Name}); Type -> Type end. @@ -1440,8 +1458,12 @@ get_attributes(Ann) -> indexed(Xs) -> lists:zip(lists:seq(1, length(Xs)), Xs). -fcode_error(Err) -> - error(Err). +fcode_error(Error) -> + aeso_errors:throw(aeso_code_errors:format(Error)). + +internal_error(Error) -> + Msg = lists:flatten(io_lib:format("~p\n", [Error])), + aeso_errors:throw(aeso_errors:new(internal_error, aeso_errors:pos(0, 0), Msg)). %% -- Pretty printing -------------------------------------------------------- diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 728d60b..9918a3b 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -21,8 +21,8 @@ convert_typed(TypedTree, Options) -> case lists:last(TypedTree) of {contract, Attrs, {con, _, Con}, _} -> {proplists:get_value(payable, Attrs, false), Con}; - _ -> - gen_error(last_declaration_must_be_contract) + Decl -> + gen_error({last_declaration_must_be_contract, Decl}) end, NewIcode = aeso_icode:set_payable(Payable, aeso_icode:set_name(Name, aeso_icode:new(Options))), @@ -42,17 +42,17 @@ code([], Icode, Options) -> %% Generate error on correct format. gen_error(Error) -> - error({code_errors, [Error]}). + aeso_errors:throw(aeso_code_errors:format(Error)). %% Create default init function (only if state is unit). -add_default_init_function(Icode = #{functions := Funs, state_type := State}, Options) -> +add_default_init_function(Icode = #{namespace := NS, functions := Funs, state_type := State}, Options) -> NoCode = proplists:get_value(no_code, Options, false), {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode), case lists:keymember(QInit, 1, Funs) of true -> Icode; false when NoCode -> Icode; false when State /= {tuple, []} -> - gen_error(missing_init_function); + gen_error({missing_init_function, NS}); false -> Type = {tuple, [typerep, {tuple, []}]}, Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] }, @@ -83,9 +83,9 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], constructors := maps:merge(Constructors, NewConstructors) }, Icode2 = case Name of "state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) }; - "state" -> gen_error(state_type_cannot_be_parameterized); + "state" -> gen_error({parameterized_state, Id}); "event" when Args == [] -> Icode1#{ event_type => Def }; - "event" -> gen_error(event_type_cannot_be_parameterized); + "event" -> gen_error({parameterized_event, Id}); _ -> Icode1 end, contract_to_icode(Rest, Icode2); @@ -398,10 +398,8 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode) when Op == '=='; Op == '!='; Op == '<'; Op == '>'; Op == '<='; Op == '=<'; Op == '>=' -> - Monomorphic = is_monomorphic(Type), + [ gen_error({cant_compare_type_aevm, Ann, Op, Type}) || not is_simple_type(Type) ], case ast_typerep(Type, Icode) of - _ when not Monomorphic -> - gen_error({cant_compare_polymorphic_type, Ann, Op, Type}); word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}; OtherType -> Neg = case Op of @@ -767,14 +765,22 @@ map_upd(Key, Default, ValFun, Map = {typed, Ann, _, MapType}, Icode) -> builtin_call(FunName, Args). check_entrypoint_type(Ann, Name, Args, Ret) -> - Check = fun(T, Err) -> - case is_simple_type(T) of + CheckFirstOrder = fun(T, Err) -> + case is_first_order_type(T) of false -> gen_error(Err); true -> ok end end, - [ Check(T, {entrypoint_argument_must_have_simple_type, Ann1, Name, X, T}) + CheckMonomorphic = fun(T, Err) -> + case is_monomorphic(T) of + false -> gen_error(Err); + true -> ok + end end, + [ CheckFirstOrder(T, {entrypoint_argument_must_have_first_order_type, Ann1, Name, X, T}) || {arg, Ann1, X, T} <- Args ], - Check(Ret, {entrypoint_must_have_simple_return_type, Ann, Name, Ret}). + CheckFirstOrder(Ret, {entrypoint_must_have_first_order_return_type, Ann, Name, Ret}), + [ CheckMonomorphic(T, {entrypoint_argument_must_have_monomorphic_type, Ann1, Name, X, T}) + || {arg, Ann1, X, T} <- Args ], + CheckMonomorphic(Ret, {entrypoint_must_have_monomorphic_return_type, Ann, Name, Ret}). is_simple_type({tvar, _, _}) -> false; is_simple_type({fun_t, _, _, _, _}) -> false; @@ -782,6 +788,11 @@ is_simple_type(Ts) when is_list(Ts) -> lists:all(fun is_simple_type/1, Ts); is_simple_type(T) when is_tuple(T) -> is_simple_type(tuple_to_list(T)); is_simple_type(_) -> true. +is_first_order_type({fun_t, _, _, _, _}) -> false; +is_first_order_type(Ts) when is_list(Ts) -> lists:all(fun is_first_order_type/1, Ts); +is_first_order_type(T) when is_tuple(T) -> is_first_order_type(tuple_to_list(T)); +is_first_order_type(_) -> true. + is_monomorphic({tvar, _, _}) -> false; is_monomorphic([H|T]) -> is_monomorphic(H) andalso is_monomorphic(T); diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl new file mode 100644 index 0000000..22b31c6 --- /dev/null +++ b/src/aeso_code_errors.erl @@ -0,0 +1,75 @@ +%%%------------------------------------------------------------------- +%%% @author Ulf Norell +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% Formatting of code generation errors. +%%% @end +%%% +%%%------------------------------------------------------------------- +-module(aeso_code_errors). + +-export([format/1]). + +format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) -> + Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n", + [C]), + mk_err(pos(Decl), Msg); +format({missing_init_function, Con}) -> + Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]), + Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n", + mk_err(pos(Con), Msg, Cxt); +format({parameterized_state, Decl}) -> + Msg = "The state type cannot be parameterized.\n", + mk_err(pos(Decl), Msg); +format({parameterized_event, Decl}) -> + Msg = "The event type cannot be parameterized.\n", + mk_err(pos(Decl), Msg); +format({entrypoint_argument_must_have_monomorphic_type, Ann, {id, _, Name}, X, T}) -> + Msg = io_lib:format("The argument\n~s\nof entrypoint '~s' does not have a monomorphic type.\n", + [pp_typed(X, T), Name]), + Cxt = "Use the FATE backend if you want polymorphic entrypoints.", + mk_err(pos(Ann), Msg, Cxt); +format({cant_compare_type_aevm, Ann, Op, Type}) -> + StringAndTuple = [ "- type string\n" + "- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ], + Msg = io_lib:format("Cannot compare values of type\n" + "~s\n" + "The AEVM only supports '~s' on values of\n" + "- word type (int, bool, bits, address, oracle(_, _), etc)\n" + "~s" + "Use FATE if you need to compare arbitrary types.\n", + [pp_type(2, Type), Op, StringAndTuple]), + mk_err(pos(Ann), Msg); +format({invalid_aens_resolve_type, Ann, T}) -> + Msg = io_lib:format("Invalid return type of AENS.resolve:\n" + "~s\n" + "It must be a string or a pubkey type (address, oracle, etc).\n", + [pp_type(2, T)]), + mk_err(pos(Ann), Msg); +format(Err) -> + mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])). + +pos(Ann) -> + File = aeso_syntax:get_ann(file, Ann, no_file), + Line = aeso_syntax:get_ann(line, Ann, 0), + Col = aeso_syntax:get_ann(col, Ann, 0), + aeso_errors:pos(File, Line, Col). + +pp_typed(E, T) -> + prettypr:format(prettypr:nest(2, + lists:foldr(fun prettypr:beside/2, prettypr:empty(), + [aeso_pretty:expr(E), prettypr:text(" : "), + aeso_pretty:type(T)]))). + +pp_expr(E) -> + prettypr:format(aeso_pretty:expr(E)). + +pp_type(N, T) -> + prettypr:format(prettypr:nest(N, aeso_pretty:type(T))). + +mk_err(Pos, Msg) -> + aeso_errors:new(code_error, Pos, lists:flatten(Msg)). + +mk_err(Pos, Msg, Cxt) -> + aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)). + diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 0939c75..3ee0d48 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -98,15 +98,7 @@ from_string(Backend, ContractString, Options) -> try from_string1(Backend, ContractString, Options) catch - %% The compiler errors. - throw:{parse_errors, Errors} -> - {error, Errors}; - throw:{type_errors, Errors} -> - {error, Errors}; - error:{code_errors, Errors} -> - {error, join_errors("Code errors", Errors, - fun (E) -> io_lib:format("~p", [E]) end)} - %% General programming errors in the compiler just signal error. + throw:{error, Errors} -> {error, Errors} end. from_string1(aevm, ContractString, Options) -> @@ -230,16 +222,10 @@ check_call1(ContractString0, FunName, Args, Options) -> {ok, FunName, CallArgs} end catch - throw:{parse_errors, Errors} -> - {error, Errors}; - throw:{type_errors, Errors} -> - {error, Errors}; + throw:{error, Errors} -> {error, Errors}; error:{badmatch, {error, missing_call_function}} -> {error, join_errors("Type errors", ["missing __call function"], - fun (E) -> E end)}; - throw:Error -> %Don't ask - {error, join_errors("Code errors", [Error], - fun (E) -> io_lib:format("~p", [E]) end)} + fun (E) -> E end)} end. arguments_of_body(CallName, _FunName, Fcode) -> @@ -345,16 +331,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> end end catch - throw:{parse_errors, Errors} -> - {error, Errors}; - throw:{type_errors, Errors} -> - {error, Errors}; + throw:{error, Errors} -> {error, Errors}; error:{badmatch, {error, missing_function}} -> {error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"], - fun (E) -> E end)}; - throw:Error -> %Don't ask - {error, join_errors("Code errors", [Error], - fun (E) -> io_lib:format("~p", [E]) end)} + fun (E) -> E end)} end. @@ -444,16 +424,11 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> end end catch - throw:{parse_errors, Errors} -> - {error, Errors}; - throw:{type_errors, Errors} -> + throw:{error, Errors} -> {error, Errors}; error:{badmatch, {error, missing_function}} -> {error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"], - fun (E) -> E end)}; - throw:Error -> %Don't ask - {error, join_errors("Code errors", [Error], - fun (E) -> io_lib:format("~p", [E]) end)} + fun (E) -> E end)} end. get_arg_icode(Funs) -> diff --git a/src/aeso_errors.erl b/src/aeso_errors.erl index a9e6419..b96794c 100644 --- a/src/aeso_errors.erl +++ b/src/aeso_errors.erl @@ -34,6 +34,7 @@ , pos/2 , pos/3 , pp/1 + , throw/1 , type/1 ]). @@ -49,6 +50,12 @@ pos(Line, Col) -> pos(File, Line, Col) -> #pos{ file = File, line = Line, col = Col }. +throw([]) -> ok; +throw(Errs) when is_list(Errs) -> + erlang:throw({error, Errs}); +throw(#err{} = Err) -> + erlang:throw({error, [Err]}). + msg(#err{ message = Msg, context = none }) -> Msg; msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt. @@ -68,4 +75,4 @@ pp(#err{ pos = Pos } = Err) -> pp_pos(#pos{file = no_file, line = L, col = C}) -> io_lib:format("At line ~p, col ~p:", [L, C]); pp_pos(#pos{file = F, line = L, col = C}) -> - io_lib:format("In '~s' at line ~p, col~p:", [F, L, C]). + io_lib:format("In '~s' at line ~p, col ~p:", [F, L, C]). diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 97c5ae2..6eecca7 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -33,10 +33,10 @@ string(String, Included, Opts) -> {ok, AST} -> case expand_includes(AST, Included, Opts) of {ok, AST1} -> AST1; - {error, Err} -> throw({parse_errors, [mk_error(Err)]}) + {error, Err} -> aeso_errors:throw(mk_error(Err)) end; {error, Err} -> - throw({parse_errors, [mk_error(Err)]}) + aeso_errors:throw(mk_error(Err)) end. type(String) -> diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 481c8c7..8c585e0 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -163,7 +163,7 @@ permissive_literals_fail_test() -> " Chain.spend(o, 1000000)\n", {error, [Err]} = aeso_compiler:check_call(Contract, "haxx", ["#123"], []), - ?assertMatch("Cannot unify" ++ _, aeso_errors:pp(Err)), + ?assertMatch("At line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)), ?assertEqual(type_error, aeso_errors:type(Err)), ok. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 39efe4c..68efabf 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -16,57 +16,74 @@ %% are made on the output, just that it is a binary which indicates %% that the compilation worked. simple_compile_test_() -> - [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", - fun() -> - case compile(Backend, ContractName) of - #{byte_code := ByteCode, - contract_source := _, - type_info := _} when Backend == aevm -> - ?assertMatch(Code when is_binary(Code), ByteCode); - #{fate_code := Code} when Backend == fate -> - Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), - ?assertMatch({X, X}, {Code1, Code}); - ErrBin -> - io:format("\n~s", [ErrBin]), - error(ErrBin) - end - end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], - not lists:member(ContractName, not_yet_compilable(Backend))] ++ - [ {"Testing error messages of " ++ ContractName, - fun() -> - case compile(aevm, ContractName) of - <<"Parse errors\n", ErrorString/binary>> -> - check_errors(lists:sort(ExpectedErrors), ErrorString); - Errors -> - check_errors(lists:sort(ExpectedErrors), Errors) - end - end} || - {ContractName, ExpectedErrors} <- failing_contracts() ] ++ - [ {"Testing include with explicit files", - fun() -> - FileSystem = maps:from_list( - [ begin - {ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), - {File, Bin} - end || File <- ["included.aes", "../contracts/included2.aes"] ]), - #{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), - #{byte_code := Code2} = compile(aevm, "include"), - ?assertMatch(true, Code1 == Code2) - end} ] ++ - [ {"Testing deadcode elimination for " ++ atom_to_list(Backend), - fun() -> - #{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"), - #{ byte_code := DeadCode } = compile(Backend, "deadcode"), - SizeNoDeadCode = byte_size(NoDeadCode), - SizeDeadCode = byte_size(DeadCode), - Delta = if Backend == aevm -> 40; - Backend == fate -> 20 end, - ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), - ok - end} || Backend <- [aevm, fate] ]. + [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", + fun() -> + case compile(Backend, ContractName) of + #{byte_code := ByteCode, + contract_source := _, + type_info := _} when Backend == aevm -> + ?assertMatch(Code when is_binary(Code), ByteCode); + #{fate_code := Code} when Backend == fate -> + Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), + ?assertMatch({X, X}, {Code1, Code}); + ErrBin -> + io:format("\n~s", [ErrBin]), + error(ErrBin) + end + end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], + not lists:member(ContractName, not_yet_compilable(Backend))] ++ + [ {"Testing error messages of " ++ ContractName, + fun() -> + Errors = compile(aevm, ContractName), + check_errors(ExpectedErrors, Errors) + end} || + {ContractName, ExpectedErrors} <- failing_contracts() ] ++ + [ {"Testing " ++ atom_to_list(Backend) ++ " code generation error messages of " ++ ContractName, + fun() -> + Errors = compile(Backend, ContractName), + Expect = + case is_binary(ExpectedError) of + true -> [ExpectedError]; + false -> + case proplists:get_value(Backend, ExpectedError, no_error) of + no_error -> no_error; + Err -> [Err] + end + end, + check_errors(Expect, Errors) + end} || + {ContractName, ExpectedError} <- failing_code_gen_contracts(), + Backend <- [aevm, fate] ] ++ + [ {"Testing include with explicit files", + fun() -> + FileSystem = maps:from_list( + [ begin + {ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), + {File, Bin} + end || File <- ["included.aes", "../contracts/included2.aes"] ]), + #{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), + #{byte_code := Code2} = compile(aevm, "include"), + ?assertMatch(true, Code1 == Code2) + end} ] ++ + [ {"Testing deadcode elimination for " ++ atom_to_list(Backend), + fun() -> + #{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"), + #{ byte_code := DeadCode } = compile(Backend, "deadcode"), + SizeNoDeadCode = byte_size(NoDeadCode), + SizeDeadCode = byte_size(DeadCode), + Delta = if Backend == aevm -> 40; + Backend == fate -> 20 end, + ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), + ok + end} || Backend <- [aevm, fate] ] ++ + []. -check_errors(Expect, Actual0) -> - Actual = [ list_to_binary(string:trim(aeso_errors:msg(Err))) || Err <- Actual0 ], +check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual); +check_errors(Expect, #{}) -> + ?assertEqual({error, Expect}, ok); +check_errors(Expect0, Actual0) -> + Expect = lists:sort(Expect0), + Actual = [ list_to_binary(string:trim(aeso_errors:pp(Err))) || Err <- Actual0 ], case {Expect -- Actual, Actual -- Expect} of {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); @@ -79,7 +96,7 @@ compile(Backend, Name) -> compile(Backend, Name, Options) -> String = aeso_test_utils:read_contract(Name), - case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of + case aeso_compiler:from_string(String, [{src_file, Name ++ ".aes"}, {backend, Backend} | Options]) of {ok, Map} -> Map; {error, ErrorString} when is_binary(ErrorString) -> ErrorString; {error, Errors} -> Errors @@ -135,241 +152,370 @@ not_yet_compilable(aevm) -> []. %% Contracts that should produce type errors +-define(Pos(Line, Col), "At line " ??Line ", col " ??Col ":\n"). +-define(Pos(File, Line, Col), "In '" File ".aes' at line " ??Line ", col " ??Col ":\n"). + failing_contracts() -> [ {"name_clash", - [<<"Duplicate definitions of abort at\n" + [<>, - <<"Duplicate definitions of require at\n" + <>, - <<"Duplicate definitions of double_def at\n" + <>, - <<"Duplicate definitions of double_proto at\n" + <>, - <<"Duplicate definitions of proto_and_def at\n" + <>, - <<"Duplicate definitions of put at\n" + <>, - <<"Duplicate definitions of state at\n" + <>]} , {"type_errors", - [<<"Unbound variable zz at line 17, column 23">>, - <<"Cannot unify int\n" + [<>, + < list(int)\n" "to arguments\n" " x : int\n" " x : int">>, - <<"Cannot unify string\n" + <>, - <<"Cannot unify int\n" + <>, - <<"Cannot unify string\n" + <>, - <<"Cannot unify string\n" + <>, - <<"Cannot unify string\n" + <>, - <<"Cannot unify int\n" + <>, - <<"Not a record type: string\n" + <>, - <<"Not a record type: string\n" + <>, - <<"Not a record type: string\n" + <>, - <<"Not a record type: string\n" + <>, - <<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" + <>, - <<"Repeated name x in pattern\n" + <>, - <<"Repeated argument x to function repeated_arg (at line 44, column 14).">>, - <<"Repeated argument y to function repeated_arg (at line 44, column 14).">>, - <<"No record type with fields y, z (at line 14, column 24)">>, - <<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>, - <<"Record type r2 does not have field y (at line 15, column 24)">>, - <<"Let binding at line 47, column 5 must be followed by an expression">>, - <<"Let binding at line 50, column 5 must be followed by an expression">>, - <<"Let binding at line 54, column 5 must be followed by an expression">>, - <<"Let binding at line 58, column 5 must be followed by an expression">>]} + <>, + <>, + <>, + <>, + <>, + <>, + <>, + <>, + <>]} , {"init_type_error", - [<<"Cannot unify string\n" + [<>]} , {"missing_state_type", - [<<"Cannot unify string\n" + [<>]} , {"missing_fields_in_record_expression", - [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, - <<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, - <<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} + [<>, + <>, + <>]} , {"namespace_clash", - [<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} + [<>]} , {"bad_events", - [<<"The indexed type string (at line 9, column 25) is not a word type">>, - <<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} + [<>, + <>]} , {"bad_events2", - [<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, - <<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} + [<>, + <>]} , {"type_clash", - [<<"Cannot unify int\n" + [< Remote.themap\n" "against the expected type\n" " (gas : int, value : int) => map(string, int)">>]} , {"bad_include_and_ns", - [<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>, - <<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]} + [<>, + <>]} , {"bad_address_literals", - [<<"The type bytes(32) is not a contract type\n" + [<>, - <<"The type oracle(int, bool) is not a contract type\n" + <>, - <<"The type address is not a contract type\n" + <>, - <<"Cannot unify oracle_query('a, 'b)\n" + <>, - <<"Cannot unify oracle_query('c, 'd)\n" + <>, - <<"Cannot unify oracle_query('e, 'f)\n" + <>, - <<"Cannot unify oracle('g, 'h)\n" + <>, - <<"Cannot unify oracle('i, 'j)\n" + <>, - <<"Cannot unify oracle('k, 'l)\n" + <>, - <<"Cannot unify address\n" + <>, - <<"Cannot unify address\n" + <>, - <<"Cannot unify address\n" + <>]} , {"stateful", - [<<"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>, - <<"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>, - <<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>, - <<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>, - <<"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>, - <<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>, - <<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>, - <<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]} + [<>, + <>, + <>, + <>, + <>, + <>, + <>, + <>]} , {"bad_init_state_access", - [<<"The init function should return the initial state as its result and cannot write the state,\n" + [<>, - <<"The init function should return the initial state as its result and cannot read the state,\n" + <>, - <<"The init function should return the initial state as its result and cannot read the state,\n" + <>]} , {"field_parse_error", - [<<"Cannot use nested fields or keys in record construction: p.x">>]} + [<>]} , {"modifier_checks", - [<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>, - <<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>, - <<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>, - <<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>, - <<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>, - <<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>, - <<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]} + [<>, + <>, + <>, + <>, + < unit">>, + <>, + < unit">>]} , {"list_comp_not_a_list", - [<<"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">> + [<> ]} , {"list_comp_if_not_bool", - [<<"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">> + [<> ]} , {"list_comp_bad_shadow", - [<<"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">> + [<> ]} ]. +-define(Path(File), "code_errors/" ??File). +-define(Msg(File, Line, Col, Err), <>). + +-define(SAME(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}). +-define(AEVM(File, Line, Col, Err), {?Path(File), [{aevm, ?Msg(File, Line, Col, Err)}]}). +-define(FATE(File, Line, Col, Err), {?Path(File), [{fate, ?Msg(File, Line, Col, Err)}]}). +-define(BOTH(File, Line, Col, ErrAEVM, ErrFATE), + {?Path(File), [{aevm, ?Msg(File, Line, Col, ErrAEVM)}, + {fate, ?Msg(File, Line, Col, ErrFATE)}]}). + +failing_code_gen_contracts() -> + [ ?SAME(last_declaration_must_be_contract, 1, 1, + "Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'") + , ?AEVM(polymorphic_entrypoint, 2, 17, + "The argument\n" + " x : 'a\n" + "of entrypoint 'id' does not have a monomorphic type.\n" + "Use the FATE backend if you want polymorphic entrypoints.") + , ?SAME(missing_init_function, 1, 10, + "Missing init function for the contract 'MissingInitFunction'.\n" + "The 'init' function can only be omitted if the state type is 'unit'.") + , ?SAME(parameterised_state, 3, 8, + "The state type cannot be parameterized.") + , ?SAME(parameterised_event, 3, 12, + "The event type cannot be parameterized.") + , ?SAME(polymorphic_aens_resolve, 4, 5, + "Invalid return type of AENS.resolve:\n" + " 'a\n" + "It must be a string or a pubkey type (address, oracle, etc).") + , ?SAME(bad_aens_resolve, 6, 5, + "Invalid return type of AENS.resolve:\n" + " list(int)\n" + "It must be a string or a pubkey type (address, oracle, etc).") + , ?AEVM(polymorphic_compare, 4, 5, + "Cannot compare values of type\n" + " 'a\n" + "The AEVM only supports '==' on values of\n" + "- word type (int, bool, bits, address, oracle(_, _), etc)\n" + "- type string\n" + "- tuple or record of word type\n" + "Use FATE if you need to compare arbitrary types.") + , ?AEVM(higher_order_compare, 4, 5, + "Cannot compare values of type\n" + " (int) => int\n" + "The AEVM only supports '<' on values of\n" + "- word type (int, bool, bits, address, oracle(_, _), etc)\n" + "Use FATE if you need to compare arbitrary types.") + ]. + diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index 7197bd3..6b2fc5c 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -39,7 +39,7 @@ simple_contracts_test_() -> RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end, NonAssoc = fun(Op) -> OpAtom = list_to_atom(Op), - ?assertThrow({parse_errors, [_]}, + ?assertThrow({error, [_]}, parse_expr(NoPar({a, Op, {b, Op, c}}))) end, Stronger = fun(Op1, Op2) -> CheckParens({{a, Op1, b}, Op2, c}), diff --git a/test/contracts/code_errors/bad_aens_resolve.aes b/test/contracts/code_errors/bad_aens_resolve.aes new file mode 100644 index 0000000..38d932b --- /dev/null +++ b/test/contracts/code_errors/bad_aens_resolve.aes @@ -0,0 +1,9 @@ +contract BadAENSresolve = + + type t('a) = option(list('a)) + + function fail() : t(int) = + AENS.resolve("foo.aet", "whatever") + + entrypoint main() = () + diff --git a/test/contracts/code_errors/higher_order_compare.aes b/test/contracts/code_errors/higher_order_compare.aes new file mode 100644 index 0000000..c60325d --- /dev/null +++ b/test/contracts/code_errors/higher_order_compare.aes @@ -0,0 +1,8 @@ +contract HigherOrderCompare = + + function cmp(x : int => int, y) : bool = + x < y + + entrypoint test() = + let f(x) = (y) => x + y + cmp(f(1), f(2)) diff --git a/test/contracts/code_errors/last_declaration_must_be_contract.aes b/test/contracts/code_errors/last_declaration_must_be_contract.aes new file mode 100644 index 0000000..1c72d81 --- /dev/null +++ b/test/contracts/code_errors/last_declaration_must_be_contract.aes @@ -0,0 +1,2 @@ +namespace LastDeclarationIsNotAContract = + function add(x, y) = x + y diff --git a/test/contracts/code_errors/missing_init_function.aes b/test/contracts/code_errors/missing_init_function.aes new file mode 100644 index 0000000..49372fb --- /dev/null +++ b/test/contracts/code_errors/missing_init_function.aes @@ -0,0 +1,3 @@ +contract MissingInitFunction = + type state = int * int + diff --git a/test/contracts/code_errors/parameterised_event.aes b/test/contracts/code_errors/parameterised_event.aes new file mode 100644 index 0000000..422f67b --- /dev/null +++ b/test/contracts/code_errors/parameterised_event.aes @@ -0,0 +1,4 @@ +contract ParameterisedEvent = + + datatype event('a) = Event(int) + diff --git a/test/contracts/code_errors/parameterised_state.aes b/test/contracts/code_errors/parameterised_state.aes new file mode 100644 index 0000000..fed262e --- /dev/null +++ b/test/contracts/code_errors/parameterised_state.aes @@ -0,0 +1,4 @@ +contract ParameterisedState = + + type state('a) = list('a) + diff --git a/test/contracts/code_errors/polymorphic_aens_resolve.aes b/test/contracts/code_errors/polymorphic_aens_resolve.aes new file mode 100644 index 0000000..6301743 --- /dev/null +++ b/test/contracts/code_errors/polymorphic_aens_resolve.aes @@ -0,0 +1,7 @@ +contract PolymorphicAENSresolve = + + function fail() : option('a) = + AENS.resolve("foo.aet", "whatever") + + entrypoint main() = () + diff --git a/test/contracts/code_errors/polymorphic_compare.aes b/test/contracts/code_errors/polymorphic_compare.aes new file mode 100644 index 0000000..91ba62e --- /dev/null +++ b/test/contracts/code_errors/polymorphic_compare.aes @@ -0,0 +1,7 @@ +contract PolymorphicCompare = + + function cmp(x : 'a, y : 'a) : bool = + x == y + + entrypoint test() = + cmp(4, 6) && cmp(true, false) diff --git a/test/contracts/code_errors/polymorphic_entrypoint.aes b/test/contracts/code_errors/polymorphic_entrypoint.aes new file mode 100644 index 0000000..d994a95 --- /dev/null +++ b/test/contracts/code_errors/polymorphic_entrypoint.aes @@ -0,0 +1,3 @@ +contract PolymorphicEntrypoint = + entrypoint id(x : 'a) : 'a = x + From 0533ab27e1f9751538ce0764818bb50e5387a8db Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 12:04:22 +0200 Subject: [PATCH 07/20] Check that there are no maps in map keys already in type checker --- src/aeso_ast_infer_types.erl | 17 ++++++++++++++++- src/aeso_ast_to_icode.erl | 22 +++++----------------- test/aeso_compiler_tests.erl | 9 +++++++++ test/contracts/map_as_map_key.aes | 6 ++++++ 4 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 test/contracts/map_as_map_key.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 88fdc12..ba8abf5 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1708,7 +1708,7 @@ solve_known_record_types(Env, Constraints) -> C end; _ -> - type_error({not_a_record_type, RecId, When}), + type_error({not_a_record_type, RecType, When}), not_solved end end @@ -1823,6 +1823,10 @@ unfold_types(_Env, X, _Options) -> unfold_types_in_type(Env, T) -> 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) ], + {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), UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false), @@ -1870,6 +1874,13 @@ unfold_types_in_type(Env, [H|T], Options) -> unfold_types_in_type(_Env, X, _Options) -> X. +has_maps({app_t, _, {id, _, "map"}, _}) -> + true; +has_maps(L) when is_list(L) -> + lists:any(fun has_maps/1, L); +has_maps(T) when is_tuple(T) -> + has_maps(tuple_to_list(T)); +has_maps(_) -> false. subst_tvars(Env, Type) -> subst_tvars1([{V, T} || {{tvar, _, V}, T} <- Env], Type). @@ -2282,6 +2293,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}) -> + 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_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 9918a3b..aadf843 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -718,15 +718,11 @@ eta_expand(Id = {_, Ann0, _}, {fun_t, _, _, ArgsT, _}, Icode) -> check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map(Ann, MapType, Icode). -check_monomorphic_map(Ann, Type = ?map_t(KeyType, ValType), Icode) -> - case is_monomorphic(KeyType) of - true -> - case has_maps(ast_type(KeyType, Icode)) of - false -> {KeyType, ValType}; - true -> gen_error({cant_use_map_as_map_keys, Ann, Type}) - end; - false -> gen_error({cant_compile_map_with_polymorphic_keys, Ann, Type}) - end. +check_monomorphic_map(Ann, ?map_t(KeyType, ValType), _Icode) -> + Err = fun(Why) -> gen_error({invalid_map_key_type, Why, Ann, KeyType}) end, + [ Err(polymorphic) || not is_monomorphic(KeyType) ], + [ Err(function) || not is_first_order_type(KeyType) ], + {KeyType, ValType}. map_empty(KeyType, ValType, Icode) -> prim_call(?PRIM_CALL_MAP_EMPTY, #integer{value = 0}, @@ -928,14 +924,6 @@ ast_fun_to_icode(Name, Attrs, Args, Body, TypeRep, #{functions := Funs} = Icode) NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs], aeso_icode:set_functions(NewFuns, Icode). -has_maps({map, _, _}) -> true; -has_maps(word) -> false; -has_maps(string) -> false; -has_maps(typerep) -> false; -has_maps({list, T}) -> has_maps(T); -has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); -has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). - %% A function is private if not an 'entrypoint', or if it's not defined in the %% main contract name space. (NOTE: changes when we introduce inheritance). is_private(Ann, #{ contract_name := MainContract } = Icode) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 68efabf..94d9a0b 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -468,6 +468,15 @@ failing_contracts() -> [<> ]} + , {"map_as_map_key", + [<>, + <>]} ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/map_as_map_key.aes b/test/contracts/map_as_map_key.aes new file mode 100644 index 0000000..8b2caf4 --- /dev/null +++ b/test/contracts/map_as_map_key.aes @@ -0,0 +1,6 @@ +contract MapAsMapKey = + type t('key) = map('key, int) + type lm = list(map(int, int)) + + entrypoint foo(m) : t(map(int, int)) = {[m] = 0} + entrypoint bar(m) : t(lm) = Map.delete(m, {}) From adfa325f48a76661866b437f6b751c135a086657 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 14:11:30 +0200 Subject: [PATCH 08/20] Don't use `_main` for the AEVM top entrypoint --- src/aeso_icode_to_asm.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeso_icode_to_asm.erl b/src/aeso_icode_to_asm.erl index b9e6042..422d36f 100644 --- a/src/aeso_icode_to_asm.erl +++ b/src/aeso_icode_to_asm.erl @@ -27,7 +27,7 @@ convert(#{ contract_name := _ContractName }, _Options) -> %% Create a function dispatcher - DispatchFun = {"_main", [], [{"arg", "_"}], + DispatchFun = {"%main", [], [{"arg", "_"}], {switch, {var_ref, "arg"}, [{{tuple, [fun_hash(Fun), {tuple, make_args(Args)}]}, @@ -44,7 +44,7 @@ convert(#{ contract_name := _ContractName %% taken from the stack StopLabel = make_ref(), StatefulStopLabel = make_ref(), - MainFunction = lookup_fun(Funs, "_main"), + MainFunction = lookup_fun(Funs, "%main"), StateTypeValue = aeso_ast_to_icode:type_value(StateType), From 30de1db163011f558d992d97d34cb60f233726cf Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 14:11:42 +0200 Subject: [PATCH 09/20] More code errors --- src/aeso_ast_to_fcode.erl | 33 +++++----- src/aeso_ast_to_icode.erl | 64 ++++++++++++------- src/aeso_code_errors.erl | 55 +++++++++++++--- src/aeso_icode.erl | 5 ++ test/aeso_compiler_tests.erl | 59 ++++++++++++++++- .../contracts/code_errors/complex_compare.aes | 4 ++ .../code_errors/complex_compare_leq.aes | 4 ++ .../code_errors/higher_order_entrypoint.aes | 2 + .../higher_order_entrypoint_return.aes | 2 + .../code_errors/higher_order_map_keys.aes | 6 ++ .../code_errors/higher_order_query_type.aes | 5 ++ .../higher_order_response_type.aes | 5 ++ .../code_errors/missing_definition.aes | 3 + .../polymorphic_entrypoint_return.aes | 3 + .../code_errors/polymorphic_map_keys.aes | 6 ++ .../code_errors/polymorphic_query_type.aes | 5 ++ .../code_errors/polymorphic_response_type.aes | 5 ++ .../code_errors/unapplied_contract_call.aes | 9 +++ 18 files changed, 227 insertions(+), 48 deletions(-) create mode 100644 test/contracts/code_errors/complex_compare.aes create mode 100644 test/contracts/code_errors/complex_compare_leq.aes create mode 100644 test/contracts/code_errors/higher_order_entrypoint.aes create mode 100644 test/contracts/code_errors/higher_order_entrypoint_return.aes create mode 100644 test/contracts/code_errors/higher_order_map_keys.aes create mode 100644 test/contracts/code_errors/higher_order_query_type.aes create mode 100644 test/contracts/code_errors/higher_order_response_type.aes create mode 100644 test/contracts/code_errors/missing_definition.aes create mode 100644 test/contracts/code_errors/polymorphic_entrypoint_return.aes create mode 100644 test/contracts/code_errors/polymorphic_map_keys.aes create mode 100644 test/contracts/code_errors/polymorphic_query_type.aes create mode 100644 test/contracts/code_errors/polymorphic_response_type.aes create mode 100644 test/contracts/code_errors/unapplied_contract_call.aes diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index f6ca217..f112230 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -140,6 +140,7 @@ functions := #{ fun_name() => fun_def() } }. -define(HASH_BYTES, 32). + %% -- Entrypoint ------------------------------------------------------------- %% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2 @@ -272,21 +273,21 @@ decls_to_fcode(Env, Decls) -> -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; -decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, Ann, {id, _, Name}, _}) -> +decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) -> case is_no_code(Env) of - false -> fcode_error({missing_definition, Name, lists:keydelete(entrypoint, 1, Ann)}); + false -> fcode_error({missing_definition, Id}); true -> Env end; decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) -> typedef_to_fcode(Env, Name, Args, Def); -decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, Body}) -> +decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) -> Attrs = get_attributes(Ann), FName = lookup_fun(Env, qname(Env, Name)), FArgs = args_to_fcode(Env, Args), FRet = type_to_fcode(Env, Ret), FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body), - [ ensure_first_order_entrypoint(Ann, FArgs, FRet) + [ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet) || aeso_syntax:get_ann(entrypoint, Ann, false) ], Def = #{ attrs => Attrs, args => FArgs, @@ -415,7 +416,7 @@ expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Args}) when C Arity = lists:nth(I + 1, Arities), case length(Args) == Arity of true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; - false -> fcode_error({constructor_arity_mismatch, Con, length(Args), Arity}) + false -> internal_error({constructor_arity_mismatch, Con, length(Args), Arity}) end; %% Tuples @@ -540,7 +541,7 @@ expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, %% Get the type of the oracle from the args or the expression itself OType = get_oracle_type(B, Type, Args1), {oracle, QType, RType} = type_to_fcode(Env, OType), - validate_oracle_type(aeso_syntax:get_ann(Fun), QType, RType), + validate_oracle_type(aeso_syntax:get_ann(Fun), OType, QType, RType), TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}], builtin_to_fcode(B, FArgs ++ TypeArgs); {builtin_u, B, _} when B =:= aens_resolve -> @@ -622,11 +623,11 @@ get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) -> get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType. -validate_oracle_type(Ann, QType, RType) -> - ensure_monomorphic(QType, {polymorphic_query_type, Ann, QType}), - ensure_monomorphic(RType, {polymorphic_response_type, Ann, RType}), - ensure_first_order(QType, {higher_order_query_type, Ann, QType}), - ensure_first_order(RType, {higher_order_response_type, Ann, RType}), +validate_oracle_type(Ann, Type, QType, RType) -> + ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}), + ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}), + ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}), + ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}), ok. validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) -> @@ -639,10 +640,10 @@ validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) _ -> fcode_error({invalid_aens_resolve_type, Ann, Type}) end. -ensure_first_order_entrypoint(Ann, Args, Ret) -> - [ ensure_first_order(T, {higher_order_entrypoint_argument, Ann, X, T}) - || {X, T} <- Args ], - ensure_first_order(Ret, {higher_order_entrypoint_return, Ann, Ret}), +ensure_first_order_entrypoint(Ann, Name, Args, Ret, FArgs, FRet) -> + [ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Name, {argument, X, T}}) + || {{arg, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ], + ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Name, {result, Ret}}), ok. ensure_monomorphic(Type, Err) -> @@ -1218,7 +1219,7 @@ resolve_var(Env, Q) -> resolve_fun(Env, Q). resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) -> case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of - {not_found, not_found} -> fcode_error({unbound_variable, Q}); + {not_found, not_found} -> internal_error({unbound_variable, Q}); {_, {B, none}} -> {builtin, B, []}; {_, {B, Ar}} -> {builtin_u, B, Ar}; {{Fun, Ar}, _} -> {def_u, Fun, Ar} diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index aadf843..cde1ce7 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -113,8 +113,12 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest] NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode), contract_to_icode(Rest, NewIcode); contract_to_icode([], Icode) -> Icode; -contract_to_icode([{fun_decl, _, _, _} | Code], Icode) -> - contract_to_icode(Code, Icode); +contract_to_icode([{fun_decl, _, Id, _} | Code], Icode = #{ options := Options }) -> + NoCode = proplists:get_value(no_code, Options, false), + case aeso_icode:in_main_contract(Icode) andalso not NoCode of + true -> gen_error({missing_definition, Id}); + false -> contract_to_icode(Code, Icode) + end; contract_to_icode([Decl | Code], Icode) -> io:format("Unhandled declaration: ~p\n", [Decl]), contract_to_icode(Code, Icode). @@ -267,8 +271,8 @@ ast_body({app, _, {typed, _, {proj, _, Addr, {id, _, FunName}}, %% entrypoint on the callee side. type_hash= #integer{value = 0} }; -ast_body(Call = {proj, _, {typed, _, _, {con, _, _Contract}}, {id, _, _FunName}}, _Icode) -> - gen_error({unapplied_contract_call, Call}); +ast_body({proj, _, Con = {typed, _, _, {con, _, _}}, _Fun}, _Icode) -> + gen_error({unapplied_contract_call, Con}); ast_body({con, _, Name}, Icode) -> Tag = aeso_icode:get_constructor_tag([Name], Icode), @@ -405,7 +409,7 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode) Neg = case Op of '==' -> fun(X) -> X end; '!=' -> fun(X) -> #unop{ op = '!', rand = X } end; - _ -> gen_error({cant_compare, Ann, Op, Type}) + _ -> gen_error({cant_compare_type_aevm, Ann, Op, Type}) end, Args = [ast_body(A, Icode), ast_body(B, Icode)], Builtin = @@ -416,10 +420,10 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode) case lists:usort(Types) of [word] -> builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]); - _ -> gen_error({cant_compare, Ann, Op, Type}) + _ -> gen_error({cant_compare_type_aevm, Ann, Op, Type}) end; _ -> - gen_error({cant_compare, Ann, Op, Type}) + gen_error({cant_compare_type_aevm, Ann, Op, Type}) end, Neg(Builtin) end; @@ -508,48 +512,57 @@ builtin_code(_, {id, _, "require"}, [Bool, String], _, _, Icode) -> builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]); %% Oracles -builtin_code(_, {qid, _, ["Oracle", "register"]}, Args, _, ?oracle_t(QType, RType), Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "register"]}, Args, _, OracleType = ?oracle_t(QType, RType), Icode) -> + check_oracle_type(Ann, OracleType), {Sign, [Acct, QFee, TTL]} = get_signature_arg(Args), prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0}, [ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode), ast_type_value(QType, Icode), ast_type_value(RType, Icode)], [word, sign_t(), word, ttl_t(Icode), typerep, typerep], word); -builtin_code(_, {qid, _, ["Oracle", "query_fee"]}, [Oracle], _, _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "query_fee"]}, [Oracle], [OracleType], _, Icode) -> + check_oracle_type(Ann, OracleType), prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0}, [ast_body(Oracle, Icode)], [word], word); -builtin_code(_, {qid, _, ["Oracle", "query"]}, [Oracle, Q, QFee, QTTL, RTTL], [_, QType, _, _, _], _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "query"]}, [Oracle, Q, QFee, QTTL, RTTL], [OracleType, QType, _, _, _], _, Icode) -> + check_oracle_type(Ann, OracleType), prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode), [ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)], [word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word); -builtin_code(_, {qid, _, ["Oracle", "extend"]}, Args, _, _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "extend"]}, Args, [OracleType, _], _, Icode) -> + check_oracle_type(Ann, OracleType), {Sign, [Oracle, TTL]} = get_signature_arg(Args), prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0}, [ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)], [word, sign_t(), ttl_t(Icode)], {tuple, []}); -builtin_code(_, {qid, _, ["Oracle", "respond"]}, Args, [_, _, RType], _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "respond"]}, Args, [OracleType, _, RType], _, Icode) -> + check_oracle_type(Ann, OracleType), {Sign, [Oracle, Query, R]} = get_signature_arg(Args), prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0}, [ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)], [word, word, sign_t(), ast_type(RType, Icode)], {tuple, []}); -builtin_code(_, {qid, _, ["Oracle", "get_question"]}, [Oracle, Q], [_, ?query_t(QType, _)], _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "get_question"]}, [Oracle, Q], [OracleType, ?query_t(QType, _)], _, Icode) -> + check_oracle_type(Ann, OracleType), prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0}, [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode)); -builtin_code(_, {qid, _, ["Oracle", "get_answer"]}, [Oracle, Q], [_, ?query_t(_, RType)], _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "get_answer"]}, [Oracle, Q], [OracleType, ?query_t(_, RType)], _, Icode) -> + check_oracle_type(Ann, OracleType), prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0}, [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode))); -builtin_code(_, {qid, _, ["Oracle", "check"]}, [Oracle], [?oracle_t(Q, R)], _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "check"]}, [Oracle], [OracleType = ?oracle_t(Q, R)], _, Icode) -> + check_oracle_type(Ann, OracleType), prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0}, [ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)], [word, typerep, typerep], word); -builtin_code(_, {qid, _, ["Oracle", "check_query"]}, [Oracle, Query], [_, ?query_t(Q, R)], _, Icode) -> +builtin_code(_, {qid, Ann, ["Oracle", "check_query"]}, [Oracle, Query], [OracleType, ?query_t(Q, R)], _, Icode) -> + check_oracle_type(Ann, OracleType), prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0}, [ast_body(Oracle, Icode), ast_body(Query, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)], @@ -730,8 +743,8 @@ map_empty(KeyType, ValType, Icode) -> ast_type_value(ValType, Icode)], [typerep, typerep], word). -map_get(Key, Map = {typed, Ann, _, MapType}, Icode) -> - {_KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), +map_get(Key, Map = {typed, _Ann, _, MapType}, Icode) -> + {_KeyType, ValType} = check_monomorphic_map(aeso_syntax:get_ann(Key), MapType, Icode), builtin_call({map_lookup, ast_type(ValType, Icode)}, [ast_body(Map, Icode), ast_body(Key, Icode)]). map_put(Key, Val, Map, Icode) -> @@ -771,12 +784,19 @@ check_entrypoint_type(Ann, Name, Args, Ret) -> false -> gen_error(Err); true -> ok end end, - [ CheckFirstOrder(T, {entrypoint_argument_must_have_first_order_type, Ann1, Name, X, T}) + [ CheckFirstOrder(T, {invalid_entrypoint, higher_order, Ann1, Name, {argument, X, T}}) || {arg, Ann1, X, T} <- Args ], - CheckFirstOrder(Ret, {entrypoint_must_have_first_order_return_type, Ann, Name, Ret}), - [ CheckMonomorphic(T, {entrypoint_argument_must_have_monomorphic_type, Ann1, Name, X, T}) + CheckFirstOrder(Ret, {invalid_entrypoint, higher_order, Ann, Name, {result, Ret}}), + [ CheckMonomorphic(T, {invalid_entrypoint, polymorphic, Ann1, Name, {argument, X, T}}) || {arg, Ann1, X, T} <- Args ], - CheckMonomorphic(Ret, {entrypoint_must_have_monomorphic_return_type, Ann, Name, Ret}). + CheckMonomorphic(Ret, {invalid_entrypoint, polymorphic, Ann, Name, {result, Ret}}). + +check_oracle_type(Ann, Type = ?oracle_t(QType, RType)) -> + [ gen_error({invalid_oracle_type, Why, Which, Ann, Type}) + || {Why, Check} <- [{polymorphic, fun is_monomorphic/1}, + {higher_order, fun is_first_order_type/1}], + {Which, T} <- [{query, QType}, {response, RType}], + not Check(T) ]. is_simple_type({tvar, _, _}) -> false; is_simple_type({fun_t, _, _, _, _}) -> false; diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index 22b31c6..d51fff6 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -18,17 +18,32 @@ format({missing_init_function, Con}) -> Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]), Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n", mk_err(pos(Con), Msg, Cxt); +format({missing_definition, Id}) -> + Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]), + mk_err(pos(Id), Msg); format({parameterized_state, Decl}) -> Msg = "The state type cannot be parameterized.\n", mk_err(pos(Decl), Msg); format({parameterized_event, Decl}) -> Msg = "The event type cannot be parameterized.\n", mk_err(pos(Decl), Msg); -format({entrypoint_argument_must_have_monomorphic_type, Ann, {id, _, Name}, X, T}) -> - Msg = io_lib:format("The argument\n~s\nof entrypoint '~s' does not have a monomorphic type.\n", - [pp_typed(X, T), Name]), - Cxt = "Use the FATE backend if you want polymorphic entrypoints.", - mk_err(pos(Ann), Msg, Cxt); +format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) -> + What = case Why of higher_order -> "higher-order (contains function types)"; + polymorphic -> "polymorphic (contains type variables)" end, + ThingS = case Thing of + {argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]); + {result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)]) + end, + Bad = case Thing of + {argument, _, _} -> io_lib:format("has a ~s type", [What]); + {result, _} -> io_lib:format("is ~s", [What]) + end, + Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n", + [ThingS, Name, Bad]), + case Why of + polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n"); + higher_order -> mk_err(pos(Ann), Msg) + end; format({cant_compare_type_aevm, Ann, Op, Type}) -> StringAndTuple = [ "- type string\n" "- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ], @@ -36,16 +51,35 @@ format({cant_compare_type_aevm, Ann, Op, Type}) -> "~s\n" "The AEVM only supports '~s' on values of\n" "- word type (int, bool, bits, address, oracle(_, _), etc)\n" - "~s" - "Use FATE if you need to compare arbitrary types.\n", + "~s", [pp_type(2, Type), Op, StringAndTuple]), - mk_err(pos(Ann), Msg); + Cxt = "Use FATE if you need to compare arbitrary types.\n", + mk_err(pos(Ann), Msg, Cxt); format({invalid_aens_resolve_type, Ann, T}) -> Msg = io_lib:format("Invalid return type of AENS.resolve:\n" "~s\n" "It must be a string or a pubkey type (address, oracle, etc).\n", [pp_type(2, T)]), mk_err(pos(Ann), Msg); +format({unapplied_contract_call, Contract}) -> + Msg = io_lib:format("The AEVM does not support unapplied contract call to\n" + "~s\n", [pp_expr(2, Contract)]), + Cxt = "Use FATE if you need this.\n", + mk_err(pos(Contract), Msg, Cxt); +format({invalid_map_key_type, Why, Ann, Type}) -> + Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]), + Cxt = case Why of + polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n"; + function -> "Map keys cannot be higher-order.\n" + end, + mk_err(pos(Ann), Msg, Cxt); +format({invalid_oracle_type, Why, What, Ann, Type}) -> + WhyS = case Why of higher_order -> "higher-order (contain function types)"; + polymorphic -> "polymorphic (contain type variables)" end, + Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]), + Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]), + mk_err(pos(Ann), Msg, Cxt); + format(Err) -> mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])). @@ -62,7 +96,10 @@ pp_typed(E, T) -> aeso_pretty:type(T)]))). pp_expr(E) -> - prettypr:format(aeso_pretty:expr(E)). + pp_expr(0, E). + +pp_expr(N, E) -> + prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))). pp_type(N, T) -> prettypr:format(prettypr:nest(N, aeso_pretty:type(T))). diff --git a/src/aeso_icode.erl b/src/aeso_icode.erl index df8721b..902a870 100644 --- a/src/aeso_icode.erl +++ b/src/aeso_icode.erl @@ -16,6 +16,7 @@ set_payable/2, enter_namespace/2, get_namespace/1, + in_main_contract/1, qualify/2, set_functions/2, map_typerep/2, @@ -120,6 +121,10 @@ enter_namespace(NS, Icode = #{ namespace := NS1 }) -> enter_namespace(NS, Icode) -> Icode#{ namespace => NS }. +-spec in_main_contract(icode()) -> boolean(). +in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true; +in_main_contract(_Icode) -> false. + -spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon(). get_namespace(Icode) -> maps:get(namespace, Icode, false). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 94d9a0b..c2af334 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -492,11 +492,26 @@ failing_contracts() -> failing_code_gen_contracts() -> [ ?SAME(last_declaration_must_be_contract, 1, 1, "Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'") + , ?SAME(missing_definition, 2, 14, + "Missing definition of function 'foo'.") , ?AEVM(polymorphic_entrypoint, 2, 17, "The argument\n" " x : 'a\n" - "of entrypoint 'id' does not have a monomorphic type.\n" + "of entrypoint 'id' has a polymorphic (contains type variables) type.\n" "Use the FATE backend if you want polymorphic entrypoints.") + , ?AEVM(polymorphic_entrypoint_return, 2, 3, + "The return type\n" + " 'a\n" + "of entrypoint 'fail' is polymorphic (contains type variables).\n" + "Use the FATE backend if you want polymorphic entrypoints.") + , ?SAME(higher_order_entrypoint, 2, 20, + "The argument\n" + " f : (int) => int\n" + "of entrypoint 'apply' has a higher-order (contains function types) type.") + , ?SAME(higher_order_entrypoint_return, 2, 3, + "The return type\n" + " (int) => int\n" + "of entrypoint 'add' is higher-order (contains function types).") , ?SAME(missing_init_function, 1, 10, "Missing init function for the contract 'MissingInitFunction'.\n" "The 'init' function can only be omitted if the state type is 'unit'.") @@ -520,11 +535,53 @@ failing_code_gen_contracts() -> "- type string\n" "- tuple or record of word type\n" "Use FATE if you need to compare arbitrary types.") + , ?AEVM(complex_compare, 4, 5, + "Cannot compare values of type\n" + " (string * int)\n" + "The AEVM only supports '!=' on values of\n" + "- word type (int, bool, bits, address, oracle(_, _), etc)\n" + "- type string\n" + "- tuple or record of word type\n" + "Use FATE if you need to compare arbitrary types.") + , ?AEVM(complex_compare_leq, 4, 5, + "Cannot compare values of type\n" + " (int * int)\n" + "The AEVM only supports '=<' on values of\n" + "- word type (int, bool, bits, address, oracle(_, _), etc)\n" + "Use FATE if you need to compare arbitrary types.") , ?AEVM(higher_order_compare, 4, 5, "Cannot compare values of type\n" " (int) => int\n" "The AEVM only supports '<' on values of\n" "- word type (int, bool, bits, address, oracle(_, _), etc)\n" "Use FATE if you need to compare arbitrary types.") + , ?AEVM(unapplied_contract_call, 6, 19, + "The AEVM does not support unapplied contract call to\n" + " r : Remote\n" + "Use FATE if you need this.") + , ?AEVM(polymorphic_map_keys, 4, 34, + "Invalid map key type\n" + " 'a\n" + "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.") + , ?AEVM(higher_order_map_keys, 4, 42, + "Invalid map key type\n" + " (int) => int\n" + "Map keys cannot be higher-order.") + , ?SAME(polymorphic_query_type, 3, 5, + "Invalid oracle type\n" + " oracle('a, 'b)\n" + "The query type must not be polymorphic (contain type variables).") + , ?SAME(polymorphic_response_type, 3, 5, + "Invalid oracle type\n" + " oracle(string, 'r)\n" + "The response type must not be polymorphic (contain type variables).") + , ?SAME(higher_order_query_type, 3, 5, + "Invalid oracle type\n" + " oracle((int) => int, string)\n" + "The query type must not be higher-order (contain function types).") + , ?SAME(higher_order_response_type, 3, 5, + "Invalid oracle type\n" + " oracle(string, (int) => int)\n" + "The response type must not be higher-order (contain function types).") ]. diff --git a/test/contracts/code_errors/complex_compare.aes b/test/contracts/code_errors/complex_compare.aes new file mode 100644 index 0000000..aa8d314 --- /dev/null +++ b/test/contracts/code_errors/complex_compare.aes @@ -0,0 +1,4 @@ +contract ComplexCompare = + + entrypoint test(x : string * int) = + ("foo", 1) != x diff --git a/test/contracts/code_errors/complex_compare_leq.aes b/test/contracts/code_errors/complex_compare_leq.aes new file mode 100644 index 0000000..0bbae5c --- /dev/null +++ b/test/contracts/code_errors/complex_compare_leq.aes @@ -0,0 +1,4 @@ +contract ComplexCompare = + + entrypoint test(x : int) = + (1, 2) =< (x, x + 1) diff --git a/test/contracts/code_errors/higher_order_entrypoint.aes b/test/contracts/code_errors/higher_order_entrypoint.aes new file mode 100644 index 0000000..6cdbdd3 --- /dev/null +++ b/test/contracts/code_errors/higher_order_entrypoint.aes @@ -0,0 +1,2 @@ +contract HigherOrderEntrypoint = + entrypoint apply(f : int => int, x : int) = f(x) diff --git a/test/contracts/code_errors/higher_order_entrypoint_return.aes b/test/contracts/code_errors/higher_order_entrypoint_return.aes new file mode 100644 index 0000000..b605055 --- /dev/null +++ b/test/contracts/code_errors/higher_order_entrypoint_return.aes @@ -0,0 +1,2 @@ +contract HigherOrderEntrypoint = + entrypoint add(x : int) = (y) => x + y diff --git a/test/contracts/code_errors/higher_order_map_keys.aes b/test/contracts/code_errors/higher_order_map_keys.aes new file mode 100644 index 0000000..a5fa742 --- /dev/null +++ b/test/contracts/code_errors/higher_order_map_keys.aes @@ -0,0 +1,6 @@ +contract MapAsMapKey = + type t('key) = map('key, int) + + function foo(m) : t(int => int) = {[m] = 0} + + entrypoint main() = () diff --git a/test/contracts/code_errors/higher_order_query_type.aes b/test/contracts/code_errors/higher_order_query_type.aes new file mode 100644 index 0000000..39d533f --- /dev/null +++ b/test/contracts/code_errors/higher_order_query_type.aes @@ -0,0 +1,5 @@ +contract HigherOrderQueryType = + stateful function foo(o) : oracle_query(_, string ) = + Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100)) + + entrypoint main() = () diff --git a/test/contracts/code_errors/higher_order_response_type.aes b/test/contracts/code_errors/higher_order_response_type.aes new file mode 100644 index 0000000..ae4ec93 --- /dev/null +++ b/test/contracts/code_errors/higher_order_response_type.aes @@ -0,0 +1,5 @@ +contract HigherOrderResponseType = + stateful function foo(o, q : oracle_query(string, _)) = + Oracle.respond(o, q, (x) => x + 1) + + entrypoint main() = () diff --git a/test/contracts/code_errors/missing_definition.aes b/test/contracts/code_errors/missing_definition.aes new file mode 100644 index 0000000..6f7b919 --- /dev/null +++ b/test/contracts/code_errors/missing_definition.aes @@ -0,0 +1,3 @@ +contract MissingDefinition = + entrypoint foo : int => int + entrypoint main() = foo(0) diff --git a/test/contracts/code_errors/polymorphic_entrypoint_return.aes b/test/contracts/code_errors/polymorphic_entrypoint_return.aes new file mode 100644 index 0000000..b1f469d --- /dev/null +++ b/test/contracts/code_errors/polymorphic_entrypoint_return.aes @@ -0,0 +1,3 @@ +contract PolymorphicEntrypoint = + entrypoint fail() : 'a = abort("fail") + diff --git a/test/contracts/code_errors/polymorphic_map_keys.aes b/test/contracts/code_errors/polymorphic_map_keys.aes new file mode 100644 index 0000000..eb64237 --- /dev/null +++ b/test/contracts/code_errors/polymorphic_map_keys.aes @@ -0,0 +1,6 @@ +contract MapAsMapKey = + type t('key) = map('key, int) + + function foo(m) : t('a) = {[m] = 0} + + entrypoint main() = () diff --git a/test/contracts/code_errors/polymorphic_query_type.aes b/test/contracts/code_errors/polymorphic_query_type.aes new file mode 100644 index 0000000..4fba99b --- /dev/null +++ b/test/contracts/code_errors/polymorphic_query_type.aes @@ -0,0 +1,5 @@ +contract PolymorphicQueryType = + stateful function is_oracle(o) = + Oracle.check(o) + + entrypoint main() = () diff --git a/test/contracts/code_errors/polymorphic_response_type.aes b/test/contracts/code_errors/polymorphic_response_type.aes new file mode 100644 index 0000000..9b33971 --- /dev/null +++ b/test/contracts/code_errors/polymorphic_response_type.aes @@ -0,0 +1,5 @@ +contract PolymorphicResponseType = + function is_oracle(o : oracle(string, 'r)) = + Oracle.check(o) + + entrypoint main(o : oracle(string, int)) = is_oracle(o) diff --git a/test/contracts/code_errors/unapplied_contract_call.aes b/test/contracts/code_errors/unapplied_contract_call.aes new file mode 100644 index 0000000..0ef0248 --- /dev/null +++ b/test/contracts/code_errors/unapplied_contract_call.aes @@ -0,0 +1,9 @@ +contract Remote = + entrypoint foo : int => int + +contract UnappliedContractCall = + + function f(r) = r.foo + + entrypoint test(r) = f(r)(0) + From 0b56691533afb458264fe4eff884849488a22177 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 14:30:56 +0200 Subject: [PATCH 10/20] Tell dialyzer to bugger off --- src/aeso_ast_to_fcode.erl | 2 ++ src/aeso_ast_to_icode.erl | 2 ++ src/aeso_errors.erl | 1 + src/aeso_parser.erl | 8 ++++++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index f112230..f69ee86 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -1459,6 +1459,8 @@ get_attributes(Ann) -> indexed(Xs) -> lists:zip(lists:seq(1, length(Xs)), Xs). +-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}). + fcode_error(Error) -> aeso_errors:throw(aeso_code_errors:format(Error)). diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index cde1ce7..2a9ef27 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -41,6 +41,7 @@ code([], Icode, Options) -> %% Generate error on correct format. +-dialyzer({nowarn_function, gen_error/1}). gen_error(Error) -> aeso_errors:throw(aeso_code_errors:format(Error)). @@ -731,6 +732,7 @@ eta_expand(Id = {_, Ann0, _}, {fun_t, _, _, ArgsT, _}, Icode) -> check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map(Ann, MapType, Icode). +-dialyzer({nowarn_function, check_monomorphic_map/3}). check_monomorphic_map(Ann, ?map_t(KeyType, ValType), _Icode) -> Err = fun(Why) -> gen_error({invalid_map_key_type, Why, Ann, KeyType}) end, [ Err(polymorphic) || not is_monomorphic(KeyType) ], diff --git a/src/aeso_errors.erl b/src/aeso_errors.erl index b96794c..d22c56e 100644 --- a/src/aeso_errors.erl +++ b/src/aeso_errors.erl @@ -50,6 +50,7 @@ pos(Line, Col) -> pos(File, Line, Col) -> #pos{ file = File, line = Line, col = Col }. +-spec throw(_) -> ok | no_return(). throw([]) -> ok; throw(Errs) when is_list(Errs) -> erlang:throw({error, Errs}); diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 6eecca7..4c3d50d 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -33,10 +33,10 @@ string(String, Included, Opts) -> {ok, AST} -> case expand_includes(AST, Included, Opts) of {ok, AST1} -> AST1; - {error, Err} -> aeso_errors:throw(mk_error(Err)) + {error, Err} -> parse_error(Err) end; {error, Err} -> - aeso_errors:throw(mk_error(Err)) + parse_error(Err) end. type(String) -> @@ -52,6 +52,10 @@ parse_and_scan(P, S, Opts) -> Error -> Error end. +-dialyzer({nowarn_function, parse_error/1}). +parse_error(Err) -> + aeso_errors:throw(mk_error(Err)). + mk_p_err(Pos, Msg) -> aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)). From 69a4c1365bb141cfb374b44a61e0078d904e08ff Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 14:47:13 +0200 Subject: [PATCH 11/20] Test case for calling init function from inside the contract --- src/aeso_ast_infer_types.erl | 4 ++++ test/aeso_compiler_tests.erl | 4 ++++ test/contracts/calling_init_function.aes | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 test/contracts/calling_init_function.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ba8abf5..97ad461 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2297,6 +2297,10 @@ mk_error({map_in_map_key, 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_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", + 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 c2af334..7a8dd7d 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -477,6 +477,10 @@ failing_contracts() -> "Invalid key type\n" " lm\n" "Map keys cannot contain other maps.">>]} + , {"calling_init_function", + [<>]} ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/calling_init_function.aes b/test/contracts/calling_init_function.aes new file mode 100644 index 0000000..0200b47 --- /dev/null +++ b/test/contracts/calling_init_function.aes @@ -0,0 +1,7 @@ +contract CallingInitFunction = + + type state = int * int + + entrypoint init() = (1, 2) + + entrypoint call_init() = init() From 61faa3e2ddbad73636c3f5dda046dadd6f6a8ac4 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 15:00:56 +0200 Subject: [PATCH 12/20] Fix missing file name from type errors --- src/aeso_ast_infer_types.erl | 2 +- test/aeso_compiler_tests.erl | 90 +++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 97ad461..8c33012 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2440,7 +2440,7 @@ pp_expr(Label, Expr) -> pp_type(Label, Type) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))). -src_file(T) -> aeso_syntax:get_ann(src_file, T, no_file). +src_file(T) -> aeso_syntax:get_ann(file, T, no_file). line_number(T) -> aeso_syntax:get_ann(line, T, 0). column_number(T) -> aeso_syntax:get_ann(col, T, 0). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 7a8dd7d..255757c 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -152,11 +152,17 @@ not_yet_compilable(aevm) -> []. %% Contracts that should produce type errors --define(Pos(Line, Col), "At line " ??Line ", col " ??Col ":\n"). --define(Pos(File, Line, Col), "In '" File ".aes' at line " ??Line ", col " ??Col ":\n"). +-define(Pos(File, Line, Col), "In '", (list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n"). +-define(Pos(Line, Col), ?Pos(__File, Line, Col)). + +-define(TEST(Name, Errs), + (fun() -> + __File = ??Name, + {__File, Errs} + end)()). failing_contracts() -> - [ {"name_clash", + [ ?TEST(name_clash, [< <>]} - , {"type_errors", + " - line 17, column 3">>]) + , ?TEST(type_errors, [<>, < <>, <>]} - , {"init_type_error", + "Let binding at line 58, column 5 must be followed by an expression">>]) + , ?TEST(init_type_error, [<>]} - , {"missing_state_type", + "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]) + , ?TEST(missing_state_type, [<>]} - , {"missing_fields_in_record_expression", + "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]) + , ?TEST(missing_fields_in_record_expression, [<>, <>, <>]} - , {"namespace_clash", + "The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]) + , ?TEST(namespace_clash, [<>]} - , {"bad_events", + "The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]) + , ?TEST(bad_events, [<>, <>]} - , {"bad_events2", + "The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]) + , ?TEST(bad_events2, [<>, <>]} - , {"type_clash", + "The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]) + , ?TEST(type_clash, [< Remote.themap\n" "against the expected type\n" - " (gas : int, value : int) => map(string, int)">>]} - , {"bad_include_and_ns", + " (gas : int, value : int) => map(string, int)">>]) + , ?TEST(bad_include_and_ns, [<>, <>]} - , {"bad_address_literals", + "Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]) + , ?TEST(bad_address_literals, [< "when checking the type of the expression at line 7, column 5\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" "against the expected type\n" - " bytes(32)">>]} - , {"stateful", + " bytes(32)">>]) + , ?TEST(stateful, [<>, < <>, <>]} - , {"bad_init_state_access", + "Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]) + , ?TEST(bad_init_state_access, [< <>]} - , {"field_parse_error", + " - state (at line 13, column 13)">>]) + , ?TEST(field_parse_error, [<>]} - , {"modifier_checks", + "Cannot use nested fields or keys in record construction: p.x">>]) + , ?TEST(modifier_checks, [<>, < <>, < unit">>]} - , {"list_comp_not_a_list", + "Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]) + , ?TEST(list_comp_not_a_list, [<> - ]} - , {"list_comp_if_not_bool", + ]) + , ?TEST(list_comp_if_not_bool, [<> - ]} - , {"list_comp_bad_shadow", + ]) + , ?TEST(list_comp_bad_shadow, [<> - ]} - , {"map_as_map_key", + ]) + , ?TEST(map_as_map_key, [< <>]} - , {"calling_init_function", + "Map keys cannot contain other maps.">>]) + , ?TEST(calling_init_function, [<>]} + "and cannot be called from the contract code.">>]) ]. -define(Path(File), "code_errors/" ??File). From 412b0b8b6d4abfa87b691239658a107456774ad3 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 16:51:04 +0200 Subject: [PATCH 13/20] Improve some parse errors --- src/aeso_parse_lib.erl | 56 ++++++++++++++++++++++++++++++++-- src/aeso_parser.erl | 7 +---- test/aeso_compiler_tests.erl | 14 ++++++--- test/contracts/indent_fail.aes | 3 ++ test/contracts/vclose.aes | 4 +++ test/contracts/vsemi.aes | 3 ++ 6 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 test/contracts/indent_fail.aes create mode 100644 test/contracts/vclose.aes create mode 100644 test/contracts/vsemi.aes diff --git a/src/aeso_parse_lib.erl b/src/aeso_parse_lib.erl index de1b575..04b0a76 100644 --- a/src/aeso_parse_lib.erl +++ b/src/aeso_parse_lib.erl @@ -15,6 +15,8 @@ many/1, many1/1, sep/2, sep1/2, infixl/2, infixr/2]). +-export([current_file/0, set_current_file/1]). + %% -- Types ------------------------------------------------------------------ -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). @@ -159,7 +161,7 @@ layout() -> ?layout. -spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}. parse(P, S) -> case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of - {[], {Pos, Err}} -> {error, {Pos, parse_error, flatten_error(Err)}}; + {[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}}; {[A], _} -> {ok, A}; {As, _} -> {error, {{1, 1}, ambiguous_parse, As}} end. @@ -289,7 +291,7 @@ parse1({tok_bind, Map}, Ts, Acc, Err) -> %% y + y)(4) case maps:get(vclose, Map, '$not_found') of '$not_found' -> - {Acc, unexpected_token_error(Ts, T)}; + {Acc, unexpected_token_error(Ts, maps:keys(Map), T)}; F -> VClose = {vclose, pos(T)}, Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }), @@ -333,7 +335,45 @@ mk_error(Ts, Err) -> -spec unexpected_token_error(#ts{}, token()) -> error(). unexpected_token_error(Ts, T) -> - mk_error(Ts, io_lib:format("Unexpected token ~p", [tag(T)])). + unexpected_token_error(Ts, [], T). + +unexpected_token_error(Ts, Expect, {Tag, _}) when Tag == vclose; Tag == vsemi -> + Braces = [')', ']', '}'], + Fix = case lists:filter(fun(T) -> lists:member(T, Braces) end, Expect) of + [] -> " Probable causes:\n" + " - something is missing in the previous statement, or\n" + " - this line should be indented more."; + [T | _] -> io_lib:format(" Did you forget a ~p?", [T]) + end, + Msg = io_lib:format("Unexpected indentation.~s", [Fix]), + mk_error(Ts, Msg); +unexpected_token_error(Ts, Expect, T) -> + ExpectCon = lists:member(con, Expect), + ExpectId = lists:member(id, Expect), + Fix = case T of + {id, _, X} when ExpectCon, hd(X) /= $_ -> io_lib:format(" Did you mean ~s?", [mk_upper(X)]); + {con, _, X} when ExpectId -> io_lib:format(" Did you mean ~s?", [mk_lower(X)]); + {qcon, _, Xs} when ExpectCon -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]); + {qid, _, Xs} when ExpectId -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]); + _ -> "" + end, + mk_error(Ts, io_lib:format("Unexpected ~s.~s", [describe(T), Fix])). + +mk_upper([C | Rest]) -> string:to_upper([C]) ++ Rest. +mk_lower([C | Rest]) -> string:to_lower([C]) ++ Rest. + + +describe({id, _, X}) -> io_lib:format("identifier ~s", [X]); +describe({con, _, X}) -> io_lib:format("identifier ~s", [X]); +describe({qid, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]); +describe({qcon, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]); +describe({tvar, _, X}) -> io_lib:format("type variable ~s", [X]); +describe({char, _, _}) -> "character literal"; +describe({string, _, _}) -> "string literal"; +describe({hex, _, _}) -> "integer literal"; +describe({int, _, _}) -> "integer literal"; +describe({bytes, _, _}) -> "bytes literal"; +describe(T) -> io_lib:format("token '~s'", [tag(T)]). %% Get the next token from a token stream. Inserts layout tokens if necessary. -spec next_token(#ts{}) -> false | {token(), #ts{}}. @@ -417,3 +457,13 @@ merge_with(Fun, Map1, Map2) -> end, Map2, maps:to_list(Map1)) end. +%% Current source file +current_file() -> + get('$current_file'). + +set_current_file(File) -> + put('$current_file', File). + +add_current_file({L, C}) -> {current_file(), L, C}; +add_current_file(Pos) -> Pos. + diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 4c3d50d..0156db0 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -11,6 +11,7 @@ type/1]). -include("aeso_parse_lib.hrl"). +-import(aeso_parse_lib, [current_file/0, set_current_file/1]). -type parse_result() :: aeso_syntax:ast() | none(). @@ -451,12 +452,6 @@ bracket_list(P) -> brackets(comma_sep(P)). -spec pos_ann(ann_line(), ann_col()) -> ann(). pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}]. -current_file() -> - get('$current_file'). - -set_current_file(File) -> - put('$current_file', File). - ann_pos(Ann) -> {proplists:get_value(file, Ann), proplists:get_value(line, Ann), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 255757c..c63c54d 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -162,7 +162,16 @@ not_yet_compilable(aevm) -> []. end)()). failing_contracts() -> - [ ?TEST(name_clash, + %% Parse errors + [ ?TEST(field_parse_error, + [<>]) + , ?TEST(vsemi, [<>]) + , ?TEST(vclose, [<>]) + , ?TEST(indent_fail, [<>]) + + %% Type errors + , ?TEST(name_clash, [< "The init function should return the initial state as its result and cannot read the state,\n" "but it calls\n" " - state (at line 13, column 13)">>]) - , ?TEST(field_parse_error, - [<>]) , ?TEST(modifier_checks, [<>, diff --git a/test/contracts/indent_fail.aes b/test/contracts/indent_fail.aes new file mode 100644 index 0000000..a1e0f7d --- /dev/null +++ b/test/contracts/indent_fail.aes @@ -0,0 +1,3 @@ +contract IndentFail = + entrypoint twoSpace() = () + entrypoint oneSpace() = () diff --git a/test/contracts/vclose.aes b/test/contracts/vclose.aes new file mode 100644 index 0000000..e4097fe --- /dev/null +++ b/test/contracts/vclose.aes @@ -0,0 +1,4 @@ +contract VClose = + entrypoint missing_bracket() = + let x = [1, 2, 3 + entrypoint bar() = () diff --git a/test/contracts/vsemi.aes b/test/contracts/vsemi.aes new file mode 100644 index 0000000..778b8ae --- /dev/null +++ b/test/contracts/vsemi.aes @@ -0,0 +1,3 @@ +contract VSemi = + record missing_brace = { x : int + entrypoint foo() = () From 325d69e96df119880e7d4074728d47bb7f9b04fa Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 17:24:06 +0200 Subject: [PATCH 14/20] Fail gracefully on bad top-level declaration --- src/aeso_ast_infer_types.erl | 12 +++++++++++- test/aeso_compiler_tests.erl | 2 ++ test/contracts/bad_top_level_decl.aes | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/contracts/bad_top_level_decl.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 8c33012..58c60e3 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -706,7 +706,8 @@ check_modifiers(Env, Contracts) -> {true, []} -> type_error({contract_has_no_entrypoints, Con}); _ -> ok end; - {namespace, _, _, Decls} -> check_modifiers1(namespace, Decls) + {namespace, _, _, Decls} -> check_modifiers1(namespace, Decls); + Decl -> type_error({bad_top_level_decl, Decl}) end || C <- Contracts ], destroy_and_report_type_errors(Env). @@ -2301,6 +2302,15 @@ 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", mk_t_err(pos(Ann), Msg); +mk_error({bad_top_level_decl, Decl}) -> + What = case element(1, Decl) of + letval -> "function or entrypoint"; + _ -> "contract or namespace" + end, + Id = element(3, 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(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 c63c54d..9956ce6 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -493,6 +493,8 @@ failing_contracts() -> [<>]) + , ?TEST(bad_top_level_decl, + [<>]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/bad_top_level_decl.aes b/test/contracts/bad_top_level_decl.aes new file mode 100644 index 0000000..5475fb6 --- /dev/null +++ b/test/contracts/bad_top_level_decl.aes @@ -0,0 +1,3 @@ +function square(x) = x ^ 2 +contract Main = + entrypoint main() = square(10) From 602e99512f6ccba99ed9f256d9e9eacd04b0f1a9 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 17:24:40 +0200 Subject: [PATCH 15/20] Fail gracefully on higher-order state in AEVM and accept it in FATE --- src/aeso_ast_to_fcode.erl | 8 +++++--- src/aeso_ast_to_icode.erl | 8 ++++++-- src/aeso_code_errors.erl | 4 ++++ test/aeso_compiler_tests.erl | 4 ++++ test/contracts/code_errors/higher_order_state.aes | 7 +++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 test/contracts/code_errors/higher_order_state.aes diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index f69ee86..e8ec0c9 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -640,10 +640,12 @@ validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) _ -> fcode_error({invalid_aens_resolve_type, Ann, Type}) end. -ensure_first_order_entrypoint(Ann, Name, Args, Ret, FArgs, FRet) -> - [ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Name, {argument, X, T}}) +ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) -> + [ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}}) || {{arg, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ], - ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Name, {result, Ret}}), + [ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}}) + || Name /= "init" ], %% init can return higher-order values, since they're written to the store + %% rather than being returned. ok. ensure_monomorphic(Type, Err) -> diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 2a9ef27..472c503 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -67,7 +67,7 @@ contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) -> NS = aeso_icode:get_namespace(Icode), Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)), contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1)); -contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], +contract_to_icode([Decl = {type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], Icode = #{ types := Types, constructors := Constructors }) -> TypeDef = make_type_def(Args, Def, Icode), NewConstructors = @@ -83,7 +83,11 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], Icode1 = Icode#{ types := Types#{ TName => TypeDef }, constructors := maps:merge(Constructors, NewConstructors) }, Icode2 = case Name of - "state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) }; + "state" when Args == [] -> + case is_first_order_type(Def) of + true -> Icode1#{ state_type => ast_typerep(Def, Icode) }; + false -> gen_error({higher_order_state, Decl}) + end; "state" -> gen_error({parameterized_state, Id}); "event" when Args == [] -> Icode1#{ event_type => Def }; "event" -> gen_error({parameterized_event, Id}); diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index d51fff6..9a4d8b2 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -79,6 +79,10 @@ format({invalid_oracle_type, Why, What, Ann, Type}) -> Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]), Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]), mk_err(pos(Ann), Msg, Cxt); +format({higher_order_state, {type_def, Ann, _, _, State}}) -> + Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]), + Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n", + mk_err(pos(Ann), Msg, Cxt); format(Err) -> mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 9956ce6..ddad533 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -601,5 +601,9 @@ failing_code_gen_contracts() -> "Invalid oracle type\n" " oracle(string, (int) => int)\n" "The response type must not be higher-order (contain function types).") + , ?AEVM(higher_order_state, 3, 3, + "Invalid state type\n" + " {f : (int) => int}\n" + "The state cannot contain functions in the AEVM. Use FATE if you need this.") ]. diff --git a/test/contracts/code_errors/higher_order_state.aes b/test/contracts/code_errors/higher_order_state.aes new file mode 100644 index 0000000..81d15e7 --- /dev/null +++ b/test/contracts/code_errors/higher_order_state.aes @@ -0,0 +1,7 @@ +contract HigherOrderState = + + record state = {f : int => int} + + entrypoint init() = {f = (x) => x} + entrypoint apply(n) = state.f(n) + stateful entrypoint inc() = put(state{ f = (x) => state.f(x + 1) }) From 157ffbf9e2bb467a010fd86b3bceae8c7d8e21bb Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 4 Sep 2019 10:07:43 +0200 Subject: [PATCH 16/20] Fix bug with unapplied builtins taking typerep arguments (Oracle builtins and AENS.resolve) --- src/aeso_ast_to_fcode.erl | 81 +++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index e8ec0c9..d813348 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -67,6 +67,7 @@ | {def_u, fun_name(), arity()} | {remote_u, [ftype()], ftype(), fexpr(), fun_name()} | {builtin_u, builtin(), arity()} + | {builtin_u, builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args. | {lam, [var_name()], fexpr()}. -type fsplit() :: {split, ftype(), var_name(), [fcase()]} @@ -406,7 +407,28 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}}; %% Variables expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]); -expr_to_fcode(Env, _Type, {qid, _, X}) -> resolve_var(Env, X); +expr_to_fcode(Env, Type, {qid, Ann, X}) -> + case resolve_var(Env, X) of + {builtin_u, B, Ar} when B =:= oracle_query; + B =:= oracle_get_question; + B =:= oracle_get_answer; + B =:= oracle_respond; + B =:= oracle_register; + B =:= oracle_check; + B =:= oracle_check_query -> + OType = get_oracle_type(B, Type), + {oracle, QType, RType} = type_to_fcode(Env, OType), + validate_oracle_type(Ann, OType, QType, RType), + TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}], + {builtin_u, B, Ar, TypeArgs}; + {builtin_u, B = aens_resolve, Ar} -> + {fun_t, _, _, _, ResType} = Type, + AensType = type_to_fcode(Env, ResType), + validate_aens_resolve_type(Ann, ResType, AensType), + TypeArgs = [{lit, {typerep, AensType}}], + {builtin_u, B, Ar, TypeArgs}; + Other -> Other + end; %% Constructors expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon -> @@ -527,31 +549,13 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> end; %% Function calls -expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> +expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of - {builtin_u, B, _} when B =:= oracle_query; - B =:= oracle_get_question; - B =:= oracle_get_answer; - B =:= oracle_respond; - B =:= oracle_register; - B =:= oracle_check; - B =:= oracle_check_query -> - %% Get the type of the oracle from the args or the expression itself - OType = get_oracle_type(B, Type, Args1), - {oracle, QType, RType} = type_to_fcode(Env, OType), - validate_oracle_type(aeso_syntax:get_ann(Fun), OType, QType, RType), - TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}], - builtin_to_fcode(B, FArgs ++ TypeArgs); - {builtin_u, B, _} when B =:= aens_resolve -> - %% Get the type we are assuming the name resolves to - AensType = type_to_fcode(Env, Type), - validate_aens_resolve_type(aeso_syntax:get_ann(Fun), Type, AensType), - TypeArgs = [{lit, {typerep, AensType}}], - builtin_to_fcode(B, FArgs ++ TypeArgs); - {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs); - {def_u, F, _Ar} -> {def, F, FArgs}; + {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(B, FArgs ++ TypeArgs); + {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs); + {def_u, F, _Ar} -> {def, F, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; FFun -> %% FFun is a closure, with first component the function name and @@ -615,13 +619,13 @@ make_if(Cond, Then, Else) -> {'let', X, Cond, make_if({var, X}, Then, Else)}. -get_oracle_type(oracle_register, OType, _Args) -> OType; -get_oracle_type(oracle_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; -get_oracle_type(oracle_get_question, _Type, [{typed, _, _Expr, OType} | _]) -> OType; -get_oracle_type(oracle_get_answer, _Type, [{typed, _, _Expr, OType} | _]) -> OType; -get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) -> OType; -get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; -get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType. +get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType; +get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType; +get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType; +get_oracle_type(oracle_get_answer, {fun_t, _, _, [OType | _], _}) -> OType; +get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType; +get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType; +get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType. validate_oracle_type(Ann, Type, QType, RType) -> ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}), @@ -1027,9 +1031,14 @@ make_closure(FVs, Xs, Body) -> lambda_lift_expr({lam, Xs, Body}) -> FVs = free_vars({lam, Xs, Body}), make_closure(FVs, Xs, lambda_lift_expr(Body)); -lambda_lift_expr({Tag, F, Ar}) when Tag == def_u; Tag == builtin_u -> +lambda_lift_expr(UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u -> + [Tag, F, Ar | _] = tuple_to_list(UExpr), + ExtraArgs = case UExpr of + {builtin_u, _, _, TypeArgs} -> TypeArgs; + _ -> [] + end, Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], - Args = [{var, X} || X <- Xs], + Args = [{var, X} || X <- Xs] ++ ExtraArgs, Body = case Tag of builtin_u -> builtin_to_fcode(F, Args); def_u -> {def, F, Args} @@ -1279,6 +1288,7 @@ free_vars(Expr) -> {remote_u, _, _, Ct, _} -> free_vars(Ct); {builtin, _, As} -> free_vars(As); {builtin_u, _, _} -> []; + {builtin_u, _, _, _} -> []; %% Typereps are always literals {con, _, _, As} -> free_vars(As); {tuple, As} -> free_vars(As); {proj, A, _} -> free_vars(A); @@ -1307,6 +1317,7 @@ used_defs(Expr) -> {remote_u, _, _, Ct, _} -> used_defs(Ct); {builtin, _, As} -> used_defs(As); {builtin_u, _, _} -> []; + {builtin_u, _, _, _} -> []; {con, _, _, As} -> used_defs(As); {tuple, As} -> used_defs(As); {proj, A, _} -> used_defs(A); @@ -1347,6 +1358,7 @@ rename(Ren, Expr) -> {def_u, _, _} -> Expr; {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; {builtin_u, _, _} -> Expr; + {builtin_u, _, _, _} -> Expr; {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F}; {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; @@ -1493,7 +1505,8 @@ pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")). pp_text(<<>>) -> prettypr:text("\"\""); pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)]))); pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S])); -pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)). +pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)); +pp_text(N) when is_integer(N) -> prettypr:text(integer_to_list(N)). pp_int(I) -> prettypr:text(integer_to_list(I)). @@ -1567,6 +1580,8 @@ pp_fexpr({'let', X, A, B}) -> pp_fexpr(B)]); pp_fexpr({builtin_u, B, N}) -> pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); +pp_fexpr({builtin_u, B, N, TypeArgs}) -> + pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, TypeArgs}), pp_text("/"), pp_text(N)]); pp_fexpr({builtin, B, As}) -> pp_call(pp_text(B), As); pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) -> From c37cc93abeedb8d47d93f529c9e945dae0a22d45 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 4 Sep 2019 10:21:23 +0200 Subject: [PATCH 17/20] Don't try to eta expand builtins with named arguments in AEVM --- src/aeso_ast_to_icode.erl | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 472c503..699157b 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -220,8 +220,8 @@ ast_body({qid, _, ["Bits", "all"]}, _Icode) -> %% Other terms ast_body({id, _, Name}, _Icode) -> #var_ref{name = Name}; -ast_body({typed, _, Id = {qid, _, Name}, Type}, Icode) -> - case is_builtin_fun(Name, Icode) of +ast_body({typed, _, Id = {qid, _, _}, Type}, Icode) -> + case is_builtin_fun(Id, Icode) of true -> eta_expand(Id, Type, Icode); false -> ast_body(Id, Icode) end; @@ -342,9 +342,12 @@ ast_body({switch,_,A,Cases}, Icode) -> #switch{expr=ast_body(A, Icode), cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)} || {'case',_,Pat,Body} <- Cases]}; -ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) -> - #switch{expr=ast_body(E, Icode), - cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]}; +ast_body({block, As, [{letval, _, Pat, _, E} | Rest]}, Icode) -> + E1 = ast_body(E, Icode), + Pat1 = ast_body(Pat, Icode), + Rest1 = ast_body({block, As, Rest}, Icode), + #switch{expr = E1, + cases = [{Pat1, Rest1}]}; ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) -> ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode); ast_body({block,_,[]}, _Icode) -> @@ -726,12 +729,14 @@ builtin_code(_, {qid, _, ["Bytes", "to_str"]}, [Bytes], _, _, Icode) -> builtin_code(_As, Fun, _Args, _ArgsT, _RetT, _Icode) -> gen_error({missing_code_for, Fun}). -eta_expand(Id = {_, Ann0, _}, {fun_t, _, _, ArgsT, _}, Icode) -> +eta_expand(Id = {_, Ann0, _}, {fun_t, _, [], ArgsT, _}, Icode) -> Ann = [{origin, system} | Ann0], Xs = [ {arg, Ann, {id, Ann, "%" ++ integer_to_list(I)}, T} || {I, T} <- lists:zip(lists:seq(1, length(ArgsT)), ArgsT) ], Args = [ X || {arg, _, X, _} <- Xs ], - ast_body({lam, Ann, Xs, {app, Ann, Id, Args}}, Icode). + ast_body({lam, Ann, Xs, {app, Ann, Id, Args}}, Icode); +eta_expand(Id, _Type, _Icode) -> + gen_error({unapplied_named_arg_builtin, Id}). check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map(Ann, MapType, Icode). From b9d141e035a16a6ebac75915d9ff9a02c3dce2ad Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 4 Sep 2019 10:45:22 +0200 Subject: [PATCH 18/20] Fix issue with AEVM eta expansion --- src/aeso_ast_to_icode.erl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 699157b..418f2f2 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -528,8 +528,7 @@ builtin_code(_, {qid, Ann, ["Oracle", "register"]}, Args, _, OracleType = ?oracl ast_type_value(QType, Icode), ast_type_value(RType, Icode)], [word, sign_t(), word, ttl_t(Icode), typerep, typerep], word); -builtin_code(_, {qid, Ann, ["Oracle", "query_fee"]}, [Oracle], [OracleType], _, Icode) -> - check_oracle_type(Ann, OracleType), +builtin_code(_, {qid, _, ["Oracle", "query_fee"]}, [Oracle], [_], _, Icode) -> prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0}, [ast_body(Oracle, Icode)], [word], word); @@ -539,8 +538,7 @@ builtin_code(_, {qid, Ann, ["Oracle", "query"]}, [Oracle, Q, QFee, QTTL, RTTL], [ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)], [word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word); -builtin_code(_, {qid, Ann, ["Oracle", "extend"]}, Args, [OracleType, _], _, Icode) -> - check_oracle_type(Ann, OracleType), +builtin_code(_, {qid, _, ["Oracle", "extend"]}, Args, [_, _], _, Icode) -> {Sign, [Oracle, TTL]} = get_signature_arg(Args), prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0}, [ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)], @@ -729,14 +727,14 @@ builtin_code(_, {qid, _, ["Bytes", "to_str"]}, [Bytes], _, _, Icode) -> builtin_code(_As, Fun, _Args, _ArgsT, _RetT, _Icode) -> gen_error({missing_code_for, Fun}). -eta_expand(Id = {_, Ann0, _}, {fun_t, _, [], ArgsT, _}, Icode) -> +eta_expand(Id = {_, Ann0, _}, Type = {fun_t, _, [], ArgsT, _}, Icode) -> Ann = [{origin, system} | Ann0], Xs = [ {arg, Ann, {id, Ann, "%" ++ integer_to_list(I)}, T} || {I, T} <- lists:zip(lists:seq(1, length(ArgsT)), ArgsT) ], - Args = [ X || {arg, _, X, _} <- Xs ], - ast_body({lam, Ann, Xs, {app, Ann, Id, Args}}, Icode); + Args = [ {typed, Ann, X, T} || {arg, _, X, T} <- Xs ], + ast_body({lam, Ann, Xs, {app, Ann, {typed, Ann, Id, Type}, Args}}, Icode); eta_expand(Id, _Type, _Icode) -> - gen_error({unapplied_named_arg_builtin, Id}). + gen_error({unapplied_builtin, Id}). check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map(Ann, MapType, Icode). From d8adfce4658490bb4a556b39e21f0fdd2588837d Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 4 Sep 2019 10:45:43 +0200 Subject: [PATCH 19/20] Tests for unapplied builtins --- src/aeso_code_errors.erl | 4 ++ test/aeso_compiler_tests.erl | 6 +- .../unapplied_named_arg_builtin.aes | 5 ++ test/contracts/unapplied_builtins.aes | 63 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test/contracts/code_errors/unapplied_named_arg_builtin.aes create mode 100644 test/contracts/unapplied_builtins.aes diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index 9a4d8b2..b178637 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -66,6 +66,10 @@ format({unapplied_contract_call, Contract}) -> "~s\n", [pp_expr(2, Contract)]), Cxt = "Use FATE if you need this.\n", mk_err(pos(Contract), Msg, Cxt); +format({unapplied_builtin, Id}) -> + Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]), + Cxt = "Use FATE if you need this.\n", + mk_err(pos(Id), Msg, Cxt); format({invalid_map_key_type, Why, Ann, Type}) -> Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]), Cxt = case Why of diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index ddad533..22b5275 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -144,7 +144,8 @@ compilable_contracts() -> "double_include", "manual_stdlib_include", "list_comp", - "payable" + "payable", + "unapplied_builtins" ]. not_yet_compilable(fate) -> []; @@ -577,6 +578,9 @@ failing_code_gen_contracts() -> "The AEVM does not support unapplied contract call to\n" " r : Remote\n" "Use FATE if you need this.") + , ?AEVM(unapplied_named_arg_builtin, 4, 15, + "The AEVM does not support unapplied use of Oracle.register.\n" + "Use FATE if you need this.") , ?AEVM(polymorphic_map_keys, 4, 34, "Invalid map key type\n" " 'a\n" diff --git a/test/contracts/code_errors/unapplied_named_arg_builtin.aes b/test/contracts/code_errors/unapplied_named_arg_builtin.aes new file mode 100644 index 0000000..9b0a5ce --- /dev/null +++ b/test/contracts/code_errors/unapplied_named_arg_builtin.aes @@ -0,0 +1,5 @@ +contract UnappliedNamedArgBuiltin = + // Allowed in FATE, but not AEVM + stateful entrypoint main(s) = + let reg = Oracle.register + reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int) diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes new file mode 100644 index 0000000..6055a87 --- /dev/null +++ b/test/contracts/unapplied_builtins.aes @@ -0,0 +1,63 @@ +// Builtins without named arguments can appear unapplied in both AEVM and FATE. +// Named argument builtins are: +// Oracle.register +// Oracle.respond +// AENS.preclaim +// AENS.claim +// AENS.transfer +// AENS.revoke +// Oracle.extend +contract UnappliedBuiltins = + entrypoint main() = () + type o = oracle(int, int) + type t = list(int * string) + type m = map(int, int) + datatype event = Event(int) + stateful function chain_spend() = Chain.spend + function chain_event() = Chain.event + function chain_balance() = Chain.balance + function chain_block_hash() = Chain.block_hash + function call_gas_left() = Call.gas_left + function b_abort() = abort + function b_require() = require + function oracle_query_fee() = Oracle.query_fee + stateful function oracle_query() = Oracle.query : (o, _, _, _, _) => _ + function oracle_get_question() = Oracle.get_question : (o, _) => _ + function oracle_get_answer() = Oracle.get_answer : (o, _) => _ + function oracle_check() = Oracle.check : o => _ + function oracle_check_query() = Oracle.check_query : (o, _) => _ + function aens_resolve() = AENS.resolve : (_, _) => option(string) + function map_lookup() = Map.lookup : (_, m) => _ + function map_lookup_default() = Map.lookup_default : (_, m, _) => _ + function map_member() = Map.member : (_, m) => _ + function map_size() = Map.size : m => _ + function map_delete() = Map.delete : (_, m) => _ + function map_from_list() = Map.from_list : _ => m + function map_to_list() = Map.to_list : m => _ + function crypto_verify_sig() = Crypto.verify_sig + function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1 + function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1 + function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1 + function crypto_sha3() = Crypto.sha3 : t => _ + function crypto_sha256() = Crypto.sha256 : t => _ + function crypto_blake2b() = Crypto.blake2b : t => _ + function string_sha256() = String.sha256 + function string_blake2b() = String.blake2b + function string_length() = String.length + function string_concat() = String.concat + function string_sha3() = String.sha3 + function bits_test() = Bits.test + function bits_set() = Bits.set + function bits_clear() = Bits.clear + function bits_union() = Bits.union + function bits_intersection() = Bits.intersection + function bits_difference() = Bits.difference + function bits_sum() = Bits.sum + function int_to_str() = Int.to_str + function address_to_str() = Address.to_str + function address_is_oracle() = Address.is_oracle + function address_is_contract() = Address.is_contract + function address_is_payable() = Address.is_payable + function bytes_to_int() = Bytes.to_int : bytes(10) => int + function bytes_to_str() = Bytes.to_str : bytes(99) => string + From 97d58fcacdbbd78ed0eab6d580b697a383f87d61 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 4 Sep 2019 11:03:33 +0200 Subject: [PATCH 20/20] Nicer error for missing event type --- src/aeso_ast_infer_types.erl | 25 +++++++++++++++---------- test/aeso_compiler_tests.erl | 4 ++++ test/contracts/missing_event_type.aes | 3 +++ 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 test/contracts/missing_event_type.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 58c60e3..42888ee 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -205,18 +205,18 @@ bind_state(Env) -> {S, _} -> {qid, Ann, S}; false -> Unit end, - Event = - case lookup_type(Env, {id, Ann, "event"}) of - {E, _} -> {qid, Ann, E}; - false -> {id, Ann, "event"} %% will cause type error if used(?) - end, Env1 = bind_funs([{"state", State}, {"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env), - %% We bind Chain.event in a local 'Chain' namespace. - pop_scope( - bind_fun("event", {fun_t, Ann, [], [Event], Unit}, - push_scope(namespace, {con, Ann, "Chain"}, Env1))). + case lookup_type(Env, {id, Ann, "event"}) of + {E, _} -> + %% We bind Chain.event in a local 'Chain' namespace. + Event = {qid, Ann, E}, + pop_scope( + bind_fun("event", {fun_t, Ann, [], [Event], Unit}, + push_scope(namespace, {con, Ann, "Chain"}, Env1))); + false -> Env1 + end. -spec bind_field(name(), field_info(), env()) -> env(). bind_field(X, Info, Env = #env{ fields = Fields }) -> @@ -2112,7 +2112,12 @@ mk_error({cannot_unify, A, B, When}) -> mk_t_err(Pos, Msg, Ctxt); mk_error({unbound_variable, Id}) -> Msg = io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]), - mk_t_err(pos(Id), Msg); + case Id of + {qid, _, ["Chain", "event"]} -> + Cxt = "Did you forget to define the event type?", + mk_t_err(pos(Id), Msg, Cxt); + _ -> mk_t_err(pos(Id), Msg) + end; mk_error({undefined_field, Id}) -> Msg = io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]), mk_t_err(pos(Id), Msg); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 22b5275..cb878a4 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -496,6 +496,10 @@ failing_contracts() -> "and cannot be called from the contract code.">>]) , ?TEST(bad_top_level_decl, [<>]) + , ?TEST(missing_event_type, + [<>]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/missing_event_type.aes b/test/contracts/missing_event_type.aes new file mode 100644 index 0000000..8931c5e --- /dev/null +++ b/test/contracts/missing_event_type.aes @@ -0,0 +1,3 @@ +contract MissingEventType = + entrypoint main() = + Chain.event("MAIN")