From 249b61238e92563a5d2c9263f237d60395de22f1 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 2 Sep 2019 11:14:57 +0200 Subject: [PATCH] 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}] =