Structured parse_errors and type_errors

This commit is contained in:
Hans Svensson 2019-09-02 11:14:57 +02:00 committed by Ulf Norell
parent 9e955d5958
commit 249b61238e
11 changed files with 427 additions and 310 deletions

View File

@ -74,15 +74,13 @@ do_contract_interface(Type, ContractString, Options) ->
string -> do_render_aci_json(JArray) string -> do_render_aci_json(JArray)
end end
catch catch
throw:{type_errors, Errors} -> {error, Errors};
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)}; {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:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors, {error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end. end.
join_errors(Prefix, Errors, Pfun) -> join_errors(Prefix, Errors, Pfun) ->

View File

@ -36,6 +36,8 @@
-type why_record() :: aeso_syntax:field(aeso_syntax:expr()) -type why_record() :: aeso_syntax:field(aeso_syntax:expr())
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}. | {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
-type pos() :: aeso_errors:pos().
-record(named_argument_constraint, -record(named_argument_constraint,
{args :: named_args_t(), {args :: named_args_t(),
name :: aeso_syntax:id(), name :: aeso_syntax:id(),
@ -535,7 +537,7 @@ global_env() ->
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. 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) ->
infer(Contracts, []). infer(Contracts, []).
@ -544,7 +546,8 @@ infer(Contracts) ->
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(_Options) -> global_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) -> infer(Contracts, Options) ->
ets_init(), %% Init the ETS table state ets_init(), %% Init the ETS table state
try try
@ -2059,12 +2062,11 @@ create_type_errors() ->
ets_new(type_errors, [bag]). ets_new(type_errors, [bag]).
destroy_and_report_type_errors(Env) -> destroy_and_report_type_errors(Env) ->
Errors = lists:reverse(ets_tab2list(type_errors)), Errors0 = lists:reverse(ets_tab2list(type_errors)),
%% io:format("Type errors now: ~p\n", [Errors]), %% io:format("Type errors now: ~p\n", [Errors0]),
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
ets_delete(type_errors), ets_delete(type_errors),
Errors /= [] andalso Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ],
error({type_errors, [lists:flatten(Err) || Err <- PPErrors]}). Errors == [] orelse throw({type_errors, Errors}).
%% Strip current namespace from error message for nicer printing. %% Strip current namespace from error message for nicer printing.
unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) -> unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) ->
@ -2083,153 +2085,197 @@ unqualify1(NS, Xs) ->
catch _:_ -> Xs catch _:_ -> Xs
end. end.
pp_error({cannot_unify, A, B, When}) -> mk_t_err(Pos, Msg) ->
io_lib:format("Cannot unify ~s\n" aeso_errors:new(type_error, Pos, lists:flatten(Msg)).
" and ~s\n" mk_t_err(Pos, Msg, Ctxt) ->
"~s", [pp(instantiate(A)), pp(instantiate(B)), pp_when(When)]); aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
pp_error({unbound_variable, Id}) ->
io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]); mk_error({cannot_unify, A, B, When}) ->
pp_error({undefined_field, Id}) -> Msg = io_lib:format("Cannot unify ~s\n and ~s\n",
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]); [pp(instantiate(A)), pp(instantiate(B))]),
pp_error({not_a_record_type, Type, Why}) -> {Pos, Ctxt} = pp_when(When),
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]); mk_t_err(Pos, Msg, Ctxt);
pp_error({not_a_contract_type, Type, Lit}) -> mk_error({unbound_variable, Id}) ->
io_lib:format("The type ~s is not a contract type\n" Msg = io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]),
"when checking that the contract literal at ~s\n~s\n" mk_t_err(pos(Id), Msg);
"has the type\n~s\n", mk_error({undefined_field, Id}) ->
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]); Msg = io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]),
pp_error({non_linear_pattern, Pattern, Nonlinear}) -> mk_t_err(pos(Id), Msg);
Plural = [ $s || length(Nonlinear) > 1 ], mk_error({not_a_record_type, Type, Why}) ->
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n", Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]),
[Plural, string:join(Nonlinear, ", "), pp_expr(" ", Pattern), pp_loc(Pattern)]); {Pos, Ctxt} = pp_why_record(Why),
pp_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) -> mk_t_err(Pos, Msg, Ctxt);
S = [ "s" || length(Fields) > 1 ], mk_error({not_a_contract_type, Type, Lit}) ->
io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s", Msg = io_lib:format("The type ~s is not a contract type\n"
[S, string:join([ pp(F) || {_, F} <- Fields ], ", "), "when checking that the contract literal at ~s\n~s\n"
pp_loc(First), "has the type\n~s\n",
[ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]); [pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]),
pp_error({missing_field, Field, Rec}) -> mk_t_err(pos(Lit), Msg);
io_lib:format("Record type ~s does not have field ~s (at ~s)\n", [pp(Rec), pp(Field), pp_loc(Field)]); mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
pp_error({missing_fields, Ann, RecType, Fields}) -> Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
Many = length(Fields) > 1, [plural("", "s", Nonlinear), string:join(Nonlinear, ", "),
S = [ "s" || Many ], pp_expr(" ", Pattern), pp_loc(Pattern)]),
Are = if Many -> "are"; true -> "is" end, mk_t_err(pos(Pattern), Msg);
io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n", mk_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) ->
[S, string:join(Fields, ", "), Are, pp(RecType), pp_loc(Ann)]); Msg = io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s",
pp_error({no_records_with_all_fields, Fields = [{_, First} | _]}) -> [plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "),
S = [ "s" || length(Fields) > 1 ], pp_loc(First), [ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]),
io_lib:format("No record type with field~s ~s (at ~s)\n", mk_t_err(pos(First), Msg);
[S, string:join([ pp(F) || {_, F} <- Fields ], ", "), mk_error({missing_field, Field, Rec}) ->
pp_loc(First)]); Msg = io_lib:format("Record type ~s does not have field ~s (at ~s)\n",
pp_error({recursive_types_not_implemented, Types}) -> [pp(Rec), pp(Field), pp_loc(Field)]),
S = if length(Types) > 1 -> "s are mutually"; mk_t_err(pos(Field), Msg);
true -> " is" end, mk_error({missing_fields, Ann, RecType, Fields}) ->
io_lib:format("The following type~s recursive, which is not yet supported:\n~s", Msg = io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n",
[S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]); [plural("", "s", Fields), string:join(Fields, ", "),
pp_error({event_must_be_variant_type, Where}) -> plural("is", "are", Fields), pp(RecType), pp_loc(Ann)]),
io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]); mk_t_err(pos(Ann), Msg);
pp_error({indexed_type_must_be_word, Type, Type}) -> mk_error({no_records_with_all_fields, Fields = [{_, First} | _]}) ->
io_lib:format("The indexed type ~s (at ~s) is not a word type\n", Msg = io_lib:format("No record type with field~s ~s (at ~s)\n",
[pp_type("", Type), pp_loc(Type)]); [plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "),
pp_error({indexed_type_must_be_word, Type, Type1}) -> pp_loc(First)]),
io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n", mk_t_err(pos(First), Msg);
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]); mk_error({recursive_types_not_implemented, Types}) ->
pp_error({payload_type_must_be_string, Type, Type}) -> S = plural(" is", "s are mutually", Types),
io_lib:format("The payload type ~s (at ~s) should be string\n", Msg = io_lib:format("The following type~s recursive, which is not yet supported:\n~s",
[pp_type("", Type), pp_loc(Type)]); [S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]),
pp_error({payload_type_must_be_string, Type, Type1}) -> mk_t_err(pos(hd(Types)), Msg);
io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n", mk_error({event_must_be_variant_type, Where}) ->
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]); Msg = io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]),
pp_error({event_0_to_3_indexed_values, Constr}) -> mk_t_err(pos(Where), Msg);
io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n", mk_error({indexed_type_must_be_word, Type, Type}) ->
[name(Constr), pp_loc(Constr)]); Msg = io_lib:format("The indexed type ~s (at ~s) is not a word type\n",
pp_error({event_0_to_1_string_values, Constr}) -> [pp_type("", Type), pp_loc(Type)]),
io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n", mk_t_err(pos(Type), Msg);
[name(Constr), pp_loc(Constr)]); mk_error({indexed_type_must_be_word, Type, Type1}) ->
pp_error({repeated_constructor, Cs}) -> Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n",
io_lib:format("Variant types must have distinct constructor names\n~s", [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
[[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]); mk_t_err(pos(Type), Msg);
pp_error({bad_named_argument, [], Name}) -> mk_error({payload_type_must_be_string, Type, Type}) ->
io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n", Msg = io_lib:format("The payload type ~s (at ~s) should be string\n",
[pp(Name), pp_loc(Name)]); [pp_type("", Type), pp_loc(Type)]),
pp_error({bad_named_argument, Args, Name}) -> mk_t_err(pos(Type), Msg);
io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s", mk_error({payload_type_must_be_string, Type, Type1}) ->
[pp(Name), pp_loc(Name), Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n",
[ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)]) [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
|| {named_arg_t, _, Arg, Type, _} <- Args ]]); mk_t_err(pos(Type), Msg);
pp_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) -> mk_error({event_0_to_3_indexed_values, Constr}) ->
io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n", Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n",
[pp_typed("", Name, Type), pp_loc(Name)]); [name(Constr), pp_loc(Constr)]),
pp_error({reserved_entrypoint, Name, Def}) -> mk_t_err(pos(Constr), Msg);
io_lib:format("The name '~s' is reserved and cannot be used for a\ntop-level contract function (at ~s).\n", mk_error({event_0_to_1_string_values, Constr}) ->
[Name, pp_loc(Def)]); Msg = io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n",
pp_error({duplicate_definition, Name, Locs}) -> [name(Constr), pp_loc(Constr)]),
io_lib:format("Duplicate definitions of ~s at\n~s", mk_t_err(pos(Constr), Msg);
[Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]); mk_error({repeated_constructor, Cs}) ->
pp_error({duplicate_scope, Kind, Name, OtherKind, L}) -> Msg = io_lib:format("Variant types must have distinct constructor names\n~s",
io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", [[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]),
[Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]); mk_t_err(pos(element(1, hd(Cs))), Msg);
pp_error({include, _, {string, Pos, Name}}) -> mk_error({bad_named_argument, [], Name}) ->
io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", Msg = io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n",
[binary_to_list(Name), pp_loc(Pos)]); [pp(Name), pp_loc(Name)]),
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> mk_t_err(pos(Name), Msg);
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", mk_error({bad_named_argument, Args, Name}) ->
[Name, pp_loc(Pos)]); Msg = io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s",
pp_error({repeated_arg, Fun, Arg}) -> [pp(Name), pp_loc(Name),
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n", [ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)])
[Arg, pp(Fun), pp_loc(Fun)]); || {named_arg_t, _, Arg, Type, _} <- Args ]]),
pp_error({stateful_not_allowed, Id, Fun}) -> mk_t_err(pos(Name), Msg);
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", mk_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) ->
[pp(Id), pp_loc(Id), pp(Fun)]); Msg = io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n",
pp_error({value_arg_not_allowed, Value, Fun}) -> [pp_typed("", Name, Type), pp_loc(Name)]),
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n", mk_t_err(pos(Name), Msg);
[pp_expr("", Value), pp_loc(Value), pp(Fun)]); mk_error({reserved_entrypoint, Name, Def}) ->
pp_error({init_depends_on_state, Which, [_Init | Chain]}) -> 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, 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", 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, [if Which == put -> "write"; true -> "read" end,
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)]) [ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|| {[_, Fun], Ann} <- Chain]]); || {[_, Fun], Ann} <- Chain]]),
pp_error({missing_body_for_let, Ann}) -> mk_t_err(pos(element(2, hd(Chain))), Msg);
io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]); mk_error({missing_body_for_let, Ann}) ->
pp_error({public_modifier_in_contract, Decl}) -> 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), Decl1 = mk_entrypoint(Decl),
io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n", 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), [pp_expr("", element(3, Decl)), pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
pp_error({init_must_be_an_entrypoint, Decl}) -> mk_t_err(pos(Decl), Msg);
mk_error({init_must_be_an_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl), Decl1 = mk_entrypoint(Decl),
io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n", Msg = io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n",
[pp_loc(Decl), [pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
pp_error({proto_must_be_entrypoint, Decl}) -> mk_t_err(pos(Decl), Msg);
mk_error({proto_must_be_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl), Decl1 = mk_entrypoint(Decl),
io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n", Msg = io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl), [pp_expr("", element(3, Decl)), pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
pp_error({proto_in_namespace, Decl}) -> mk_t_err(pos(Decl), Msg);
io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n", mk_error({proto_in_namespace, Decl}) ->
[pp_loc(Decl)]); Msg = io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n",
pp_error({entrypoint_in_namespace, Decl}) -> [pp_loc(Decl)]),
io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n", mk_t_err(pos(Decl), Msg);
[pp_loc(Decl)]); mk_error({entrypoint_in_namespace, Decl}) ->
pp_error({private_entrypoint, Decl}) -> Msg = io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n",
io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n", [pp_loc(Decl)]),
[pp_expr("", element(3, Decl)), pp_loc(Decl)]); mk_t_err(pos(Decl), Msg);
pp_error({private_and_public, Decl}) -> mk_error({private_entrypoint, Decl}) ->
io_lib:format("The function ~s (at ~s) cannot be both public and private.\n", Msg = io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl)]); [pp_expr("", element(3, Decl)), pp_loc(Decl)]),
pp_error({contract_has_no_entrypoints, Con}) -> mk_t_err(pos(Decl), Msg);
io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n" mk_error({private_and_public, Decl}) ->
"contract functions must be declared with the 'entrypoint' keyword instead of\n" Msg = io_lib:format("The function ~s (at ~s) cannot be both public and private.\n",
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]); [pp_expr("", element(3, Decl)), pp_loc(Decl)]),
pp_error({unbound_type, Type}) -> mk_t_err(pos(Decl), Msg);
io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]); mk_error({contract_has_no_entrypoints, Con}) ->
pp_error({new_tuple_syntax, Ann, Ts}) -> Msg = io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n"
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", "contract functions must be declared with the 'entrypoint' keyword instead of\n"
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]); "'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
pp_error(Err) -> mk_t_err(pos(Con), Msg);
io_lib:format("Unknown error: ~p\n", [Err]). 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) -> mk_entrypoint(Decl) ->
Ann = [entrypoint | lists:keydelete(public, 1, Ann = [entrypoint | lists:keydelete(public, 1,
@ -2237,110 +2283,114 @@ mk_entrypoint(Decl) ->
aeso_syntax:get_ann(Decl))) -- [public, private]], aeso_syntax:get_ann(Decl))) -- [public, private]],
aeso_syntax:set_ann(Ann, Decl). aeso_syntax:set_ann(Ann, Decl).
pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]); pp_when({todo, What}) -> {pos(0, 0), io_lib:format("[TODO] ~p\n", [What])};
pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]); pp_when({at, Ann}) -> {pos(Ann), io_lib:format("at ~s\n", [pp_loc(Ann)])};
pp_when({check_typesig, Name, Inferred, Given}) -> pp_when({check_typesig, Name, Inferred, Given}) ->
io_lib:format("when checking the definition of ~s\n" {pos(Given),
" inferred type: ~s\n" io_lib:format("when checking the definition of ~s (at ~s)\n"
" given type: ~s\n", " inferred type: ~s\n"
[Name, pp(instantiate(Inferred)), pp(instantiate(Given))]); " given type: ~s\n",
[Name, pp_loc(Given), pp(instantiate(Inferred)), pp(instantiate(Given))])};
pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) -> pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) ->
Inferred = instantiate(Inferred0), Inferred = instantiate(Inferred0),
ArgTypes = instantiate(ArgTypes0), ArgTypes = instantiate(ArgTypes0),
io_lib:format("when checking the application at ~s of\n" {pos(Fun),
"~s\n" io_lib:format("when checking the application at ~s of\n"
"to arguments\n~s", "~s\n"
[pp_loc(Fun), "to arguments\n~s",
pp_typed(" ", Fun, Inferred), [pp_loc(Fun),
[ [pp_typed(" ", Arg, ArgT), "\n"] pp_typed(" ", Fun, Inferred),
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ]); [ [pp_typed(" ", Arg, ArgT), "\n"]
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])};
pp_when({field_constraint, FieldType0, InferredType0, Fld}) -> pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
FieldType = instantiate(FieldType0), FieldType = instantiate(FieldType0),
InferredType = instantiate(InferredType0), InferredType = instantiate(InferredType0),
case Fld of {pos(Fld),
{field, _Ann, LV, Id, E} -> case Fld of
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", {field, _Ann, LV, Id, E} ->
[pp_typed(" ", {lvalue, [], LV}, FieldType), 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_loc(Fld), [pp_typed(" ", {lvalue, [], LV}, FieldType),
pp(Id), pp_loc(Fld),
pp_typed(" ", E, InferredType)]); pp(Id),
{field, _Ann, LV, E} -> pp_typed(" ", E, InferredType)]);
io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the value\n~s\n", {field, _Ann, LV, E} ->
[pp_typed(" ", {lvalue, [], LV}, FieldType), io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the value\n~s\n",
pp_loc(Fld), [pp_typed(" ", {lvalue, [], LV}, FieldType),
pp_typed(" ", E, InferredType)]); pp_loc(Fld),
{proj, _Ann, _Rec, _Fld} -> pp_typed(" ", E, InferredType)]);
io_lib:format("when checking the record projection at ~s\n~s\nagainst the expected type\n~s\n", {proj, _Ann, _Rec, _Fld} ->
[pp_loc(Fld), io_lib:format("when checking the record projection at ~s\n~s\nagainst the expected type\n~s\n",
pp_typed(" ", Fld, FieldType), [pp_loc(Fld),
pp_type(" ", InferredType)]) pp_typed(" ", Fld, FieldType),
end; pp_type(" ", InferredType)])
end};
pp_when({record_constraint, RecType0, InferredType0, Fld}) -> pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
RecType = instantiate(RecType0), RecType = instantiate(RecType0),
InferredType = instantiate(InferredType0), InferredType = instantiate(InferredType0),
{Pos, WhyRec} = pp_why_record(Fld),
case Fld of case Fld of
{field, _Ann, _LV, _Id, _E} -> {field, _Ann, _LV, _Id, _E} ->
io_lib:format("when checking that the record type\n~s\n~s\n" {Pos,
"matches the expected type\n~s\n", io_lib:format("when checking that the record type\n~s\n~s\n"
[pp_type(" ", RecType), "matches the expected type\n~s\n",
pp_why_record(Fld), [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
pp_type(" ", InferredType)]);
{field, _Ann, _LV, _E} -> {field, _Ann, _LV, _E} ->
io_lib:format("when checking that the record type\n~s\n~s\n" {Pos,
"matches the expected type\n~s\n", io_lib:format("when checking that the record type\n~s\n~s\n"
[pp_type(" ", RecType), "matches the expected type\n~s\n",
pp_why_record(Fld), [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
pp_type(" ", InferredType)]);
{proj, _Ann, Rec, _FldName} -> {proj, _Ann, Rec, _FldName} ->
io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s\n", {pos(Rec),
[pp_typed(" ", Rec, InferredType), io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s\n",
pp_loc(Rec), [pp_typed(" ", Rec, InferredType), pp_loc(Rec),
pp_type(" ", RecType), pp_type(" ", RecType), WhyRec])}
pp_why_record(Fld)])
end; end;
pp_when({if_branches, Then, ThenType0, Else, ElseType0}) -> pp_when({if_branches, Then, ThenType0, Else, ElseType0}) ->
{ThenType, ElseType} = instantiate({ThenType0, ElseType0}), {ThenType, ElseType} = instantiate({ThenType0, ElseType0}),
Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ], Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ],
io_lib:format("when comparing the types of the if-branches\n" {pos(element(1, hd(Branches))),
"~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)]) io_lib:format("when comparing the types of the if-branches\n"
|| {B, BType} <- Branches ] ]); "~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}) -> pp_when({case_pat, Pat, PatType0, ExprType0}) ->
{PatType, ExprType} = instantiate({PatType0, ExprType0}), {PatType, ExprType} = instantiate({PatType0, ExprType0}),
io_lib:format("when checking the type of the pattern at ~s\n~s\n" {pos(Pat),
"against the expected type\n~s\n", io_lib:format("when checking the type of the pattern at ~s\n~s\n"
[pp_loc(Pat), pp_typed(" ", Pat, PatType), "against the expected type\n~s\n",
pp_type(" ", ExprType)]); [pp_loc(Pat), pp_typed(" ", Pat, PatType),
pp_type(" ", ExprType)])};
pp_when({check_expr, Expr, Inferred0, Expected0}) -> pp_when({check_expr, Expr, Inferred0, Expected0}) ->
{Inferred, Expected} = instantiate({Inferred0, Expected0}), {Inferred, Expected} = instantiate({Inferred0, Expected0}),
io_lib:format("when checking the type of the expression at ~s\n~s\n" {pos(Expr),
"against the expected type\n~s\n", io_lib:format("when checking the type of the expression at ~s\n~s\n"
[pp_loc(Expr), pp_typed(" ", Expr, Inferred), "against the expected type\n~s\n",
pp_type(" ", Expected)]); [pp_loc(Expr), pp_typed(" ", Expr, Inferred),
pp_type(" ", Expected)])};
pp_when({checking_init_type, Ann}) -> pp_when({checking_init_type, Ann}) ->
io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n", {pos(Ann),
[pp_loc(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}) -> pp_when({list_comp, BindExpr, Inferred0, Expected0}) ->
{Inferred, Expected} = instantiate({Inferred0, Expected0}), {Inferred, Expected} = instantiate({Inferred0, Expected0}),
io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n" {pos(BindExpr),
"against type \n~s\n", io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n"
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)] "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()) -> {pos(), iolist()}.
-spec pp_why_record(why_record()) -> iolist().
pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) -> pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) ->
io_lib:format("arising from an assignment of the field ~s (at ~s)", {pos(Fld),
[pp_expr("", {lvalue, [], LV}), io_lib:format("arising from an assignment of the field ~s (at ~s)",
pp_loc(Fld)]); [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
pp_why_record(Fld = {field, _Ann, LV, _E}) -> pp_why_record(Fld = {field, _Ann, LV, _E}) ->
io_lib:format("arising from an assignment of the field ~s (at ~s)", {pos(Fld),
[pp_expr("", {lvalue, [], LV}), io_lib:format("arising from an assignment of the field ~s (at ~s)",
pp_loc(Fld)]); [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
pp_why_record({proj, _Ann, Rec, FldName}) -> pp_why_record({proj, _Ann, Rec, FldName}) ->
io_lib:format("arising from the projection of the field ~s (at ~s)", {pos(Rec),
[pp(FldName), io_lib:format("arising from the projection of the field ~s (at ~s)",
pp_loc(Rec)]). [pp(FldName), pp_loc(Rec)])}.
if_branches(If = {'if', Ann, _, Then, Else}) -> if_branches(If = {'if', Ann, _, Then, Else}) ->
@ -2362,9 +2412,13 @@ pp_expr(Label, Expr) ->
pp_type(Label, Type) -> pp_type(Label, Type) ->
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))). 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). line_number(T) -> aeso_syntax:get_ann(line, T, 0).
column_number(T) -> aeso_syntax:get_ann(col, 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) -> loc(T) ->
{line_number(T), column_number(T)}. {line_number(T), column_number(T)}.
@ -2375,6 +2429,9 @@ pp_loc(T) ->
_ -> io_lib:format("line ~p, column ~p", [Line, Col]) _ -> io_lib:format("line ~p, column ~p", [Line, Col])
end. end.
plural(No, _Yes, [_]) -> No;
plural(_No, Yes, _) -> Yes.
pp(T = {type_sig, _, _, _, _}) -> pp(T = {type_sig, _, _, _, _}) ->
pp(typesig_to_fun_t(T)); pp(typesig_to_fun_t(T));
pp([]) -> pp([]) ->

View File

@ -99,10 +99,10 @@ from_string(Backend, ContractString, Options) ->
from_string1(Backend, ContractString, Options) from_string1(Backend, ContractString, Options)
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> throw:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)}; {error, Errors};
error:{type_errors, Errors} -> throw:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)}; {error, Errors};
error:{code_errors, Errors} -> error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors, {error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
@ -230,10 +230,10 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
end end
catch catch
error:{parse_errors, Errors} -> throw:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{type_errors, Errors} -> throw:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{badmatch, {error, missing_call_function}} -> error:{badmatch, {error, missing_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"], {error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)}; fun (E) -> E end)};
@ -345,10 +345,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{type_errors, Errors} -> throw:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{badmatch, {error, missing_function}} -> error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"], {error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)}; fun (E) -> E end)};
@ -444,10 +444,10 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{type_errors, Errors} -> throw:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{badmatch, {error, missing_function}} -> error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"], {error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)}; fun (E) -> E end)};
@ -582,37 +582,7 @@ parse(Text, Options) ->
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast(). -spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Included, Options) -> parse(Text, Included, Options) ->
%% Try and return something sensible here! aeso_parser:string(Text, Included, Options).
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]}).
read_contract(Name) -> read_contract(Name) ->
file:read_file(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]).

71
src/aeso_errors.erl Normal file
View File

@ -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]).

View File

@ -9,7 +9,7 @@
-module(aeso_parse_lib). -module(aeso_parse_lib).
-export([parse/2, -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, lazy/1, choice/1, choice/2, tok/1, layout/0,
left/2, right/2, between/3, optional/1, left/2, right/2, between/3, optional/1,
many/1, many1/1, sep/2, sep1/2, 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). -spec lazy(fun(() -> parser(A))) -> parser(A).
lazy(Delayed) -> ?lazy(Delayed). 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. %% @doc A parser that always fails.
-spec fail(term()) -> parser(none()). -spec fail(term()) -> parser(none()).
fail(Err) -> ?fail(Err). fail(Err) -> ?fail(Err).
@ -322,6 +326,8 @@ current_pos(#ts{ tokens = [T | _] }) -> pos(T);
current_pos(#ts{ last = T }) -> end_pos(pos(T)). current_pos(#ts{ last = T }) -> end_pos(pos(T)).
-spec mk_error(#ts{}, term()) -> error(). -spec mk_error(#ts{}, term()) -> error().
mk_error(_Ts, {Pos, Err}) ->
{Pos, Err};
mk_error(Ts, Err) -> mk_error(Ts, Err) ->
{current_pos(Ts), Err}. {current_pos(Ts), Err}.

View File

@ -19,7 +19,7 @@
-import(aeso_parse_lib, -import(aeso_parse_lib,
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2, [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, 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]). left/2, right/2, optional/1]).

View File

@ -12,9 +12,7 @@
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-type parse_result() :: {ok, aeso_syntax:ast()} -type parse_result() :: aeso_syntax:ast() | none().
| {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), atom()}}.
-type include_hash() :: {string(), binary()}. -type include_hash() :: {string(), binary()}.
@ -33,13 +31,19 @@ string(String, Opts) ->
string(String, Included, Opts) -> string(String, Included, Opts) ->
case parse_and_scan(file(), String, Opts) of case parse_and_scan(file(), String, Opts) of
{ok, AST} -> {ok, AST} ->
expand_includes(AST, Included, Opts); case expand_includes(AST, Included, Opts) of
Err = {error, _} -> {ok, AST1} -> AST1;
Err {error, Err} -> throw({parse_errors, [mk_error(Err)]})
end;
{error, Err} ->
throw({parse_errors, [mk_error(Err)]})
end. end.
type(String) -> 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) -> parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)), set_current_file(proplists:get_value(src_file, Opts, no_file)),
@ -48,6 +52,24 @@ parse_and_scan(P, S, Opts) ->
Error -> Error Error -> Error
end. 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 ---------------------------------------------------------- %% -- Parsing rules ----------------------------------------------------------
file() -> choice([], block(decl())). 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}) -> parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(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()). -spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
ret_doc_err(Ann, Doc) -> 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()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
@ -600,7 +617,7 @@ stdlib_options() ->
get_include_code(File, Ann, Opts) -> get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), read_file(File, stdlib_options())} of case {read_file(File, Opts), read_file(File, stdlib_options())} of
{{ok, _}, {ok,_ }} -> {{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, Bin}} ->
{ok, binary_to_list(Bin)}; {ok, binary_to_list(Bin)};
{{ok, Bin}, _} -> {{ok, Bin}, _} ->

View File

@ -161,8 +161,10 @@ permissive_literals_fail_test() ->
"contract OracleTest =\n" "contract OracleTest =\n"
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n" " stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n", " Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} = {error, [Err]} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []), aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
?assertMatch("Cannot unify" ++ _, aeso_errors:pp(Err)),
?assertEqual(type_error, aeso_errors:type(Err)),
ok. ok.
encode_decode_calldata(FunName, Types, Args) -> encode_decode_calldata(FunName, Types, Args) ->

View File

@ -107,11 +107,11 @@ aci_test_contract(Name) ->
check_stub(Stub, Options) -> check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} -> Ast ->
try try
%% io:format("AST: ~120p\n", [Ast]), %% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, []) aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} -> catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]), io:format("Type error:\n~s\n", [TE]),
error(TE); error(TE);
_:R -> _:R ->

View File

@ -35,10 +35,10 @@ simple_compile_test_() ->
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
case compile(aevm, ContractName) of case compile(aevm, ContractName) of
<<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> -> <<"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
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++ {ContractName, ExpectedErrors} <- failing_contracts() ] ++
@ -65,9 +65,8 @@ simple_compile_test_() ->
ok ok
end} || Backend <- [aevm, fate] ]. end} || Backend <- [aevm, fate] ].
check_errors(Expect, ErrorString) -> check_errors(Expect, Actual0) ->
%% This removes the final single \n as well. Actual = [ list_to_binary(string:trim(aeso_errors:msg(Err))) || Err <- Actual0 ],
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
@ -81,8 +80,9 @@ compile(Backend, Name) ->
compile(Backend, Name, Options) -> compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), 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}, {backend, Backend} | Options]) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} -> ErrorString {error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@ -353,8 +353,7 @@ failing_contracts() ->
"but it calls\n" "but it calls\n"
" - state (at line 13, column 13)">>]} " - state (at line 13, column 13)">>]}
, {"field_parse_error", , {"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">>]}
"Cannot use nested fields or keys in record construction: p.x\n">>]}
, {"modifier_checks", , {"modifier_checks",
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>, [<<"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.">>, <<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,

View File

@ -39,7 +39,7 @@ simple_contracts_test_() ->
RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end, RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end,
NonAssoc = fun(Op) -> NonAssoc = fun(Op) ->
OpAtom = list_to_atom(Op), OpAtom = list_to_atom(Op),
?assertError({error, {_, parse_error, _}}, ?assertThrow({parse_errors, [_]},
parse_expr(NoPar({a, Op, {b, Op, c}}))) end, parse_expr(NoPar({a, Op, {b, Op, c}}))) end,
Stronger = fun(Op1, Op2) -> Stronger = fun(Op1, Op2) ->
CheckParens({{a, Op1, b}, Op2, c}), CheckParens({{a, Op1, b}, Op2, c}),
@ -74,10 +74,7 @@ roundtrip_contract(Name) ->
parse_string(Text) -> parse_string(Text, []). parse_string(Text) -> parse_string(Text, []).
parse_string(Text, Opts) -> parse_string(Text, Opts) ->
case aeso_parser:string(Text, Opts) of aeso_parser:string(Text, Opts).
{ok, Contract} -> Contract;
Err -> error(Err)
end.
parse_expr(Text) -> parse_expr(Text) ->
[{letval, _, _, _, Expr}] = [{letval, _, _, _, Expr}] =