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)]),
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" "when checking that the contract literal at ~s\n~s\n"
"has the type\n~s\n", "has the type\n~s\n",
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]); [pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]),
pp_error({non_linear_pattern, Pattern, Nonlinear}) -> mk_t_err(pos(Lit), Msg);
Plural = [ $s || length(Nonlinear) > 1 ], mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n", Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
[Plural, string:join(Nonlinear, ", "), pp_expr(" ", Pattern), pp_loc(Pattern)]); [plural("", "s", Nonlinear), string:join(Nonlinear, ", "),
pp_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) -> pp_expr(" ", Pattern), pp_loc(Pattern)]),
S = [ "s" || length(Fields) > 1 ], mk_t_err(pos(Pattern), Msg);
io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s", mk_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) ->
[S, string:join([ pp(F) || {_, F} <- Fields ], ", "), Msg = io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s",
pp_loc(First), [plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "),
[ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]); pp_loc(First), [ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]),
pp_error({missing_field, Field, Rec}) -> mk_t_err(pos(First), Msg);
io_lib:format("Record type ~s does not have field ~s (at ~s)\n", [pp(Rec), pp(Field), pp_loc(Field)]); mk_error({missing_field, Field, Rec}) ->
pp_error({missing_fields, Ann, RecType, Fields}) -> Msg = io_lib:format("Record type ~s does not have field ~s (at ~s)\n",
Many = length(Fields) > 1, [pp(Rec), pp(Field), pp_loc(Field)]),
S = [ "s" || Many ], mk_t_err(pos(Field), Msg);
Are = if Many -> "are"; true -> "is" end, mk_error({missing_fields, Ann, RecType, Fields}) ->
io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n", Msg = 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)]); [plural("", "s", Fields), string:join(Fields, ", "),
pp_error({no_records_with_all_fields, Fields = [{_, First} | _]}) -> plural("is", "are", Fields), pp(RecType), pp_loc(Ann)]),
S = [ "s" || length(Fields) > 1 ], mk_t_err(pos(Ann), Msg);
io_lib:format("No record type with field~s ~s (at ~s)\n", mk_error({no_records_with_all_fields, Fields = [{_, First} | _]}) ->
[S, string:join([ pp(F) || {_, F} <- Fields ], ", "), Msg = io_lib:format("No record type with field~s ~s (at ~s)\n",
pp_loc(First)]); [plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "),
pp_error({recursive_types_not_implemented, Types}) -> pp_loc(First)]),
S = if length(Types) > 1 -> "s are mutually"; mk_t_err(pos(First), Msg);
true -> " is" end, mk_error({recursive_types_not_implemented, Types}) ->
io_lib:format("The following type~s recursive, which is not yet supported:\n~s", S = plural(" is", "s are mutually", Types),
[S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]); Msg = io_lib:format("The following type~s recursive, which is not yet supported:\n~s",
pp_error({event_must_be_variant_type, Where}) -> [S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]),
io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]); mk_t_err(pos(hd(Types)), Msg);
pp_error({indexed_type_must_be_word, Type, Type}) -> mk_error({event_must_be_variant_type, Where}) ->
io_lib:format("The indexed type ~s (at ~s) is not a word type\n", Msg = io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]),
[pp_type("", Type), pp_loc(Type)]); mk_t_err(pos(Where), Msg);
pp_error({indexed_type_must_be_word, Type, Type1}) -> mk_error({indexed_type_must_be_word, Type, Type}) ->
io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n", Msg = io_lib:format("The indexed type ~s (at ~s) is not a word type\n",
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]); [pp_type("", Type), pp_loc(Type)]),
pp_error({payload_type_must_be_string, Type, Type}) -> mk_t_err(pos(Type), Msg);
io_lib:format("The payload type ~s (at ~s) should be string\n", mk_error({indexed_type_must_be_word, Type, Type1}) ->
[pp_type("", Type), pp_loc(Type)]); Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n",
pp_error({payload_type_must_be_string, Type, Type1}) -> [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n", mk_t_err(pos(Type), Msg);
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]); mk_error({payload_type_must_be_string, Type, Type}) ->
pp_error({event_0_to_3_indexed_values, Constr}) -> Msg = io_lib:format("The payload type ~s (at ~s) should be string\n",
io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n", [pp_type("", Type), pp_loc(Type)]),
[name(Constr), pp_loc(Constr)]); mk_t_err(pos(Type), Msg);
pp_error({event_0_to_1_string_values, Constr}) -> mk_error({payload_type_must_be_string, Type, Type1}) ->
io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n", Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n",
[name(Constr), pp_loc(Constr)]); [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
pp_error({repeated_constructor, Cs}) -> mk_t_err(pos(Type), Msg);
io_lib:format("Variant types must have distinct constructor names\n~s", mk_error({event_0_to_3_indexed_values, Constr}) ->
[[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]); Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n",
pp_error({bad_named_argument, [], Name}) -> [name(Constr), pp_loc(Constr)]),
io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n", mk_t_err(pos(Constr), Msg);
[pp(Name), pp_loc(Name)]); mk_error({event_0_to_1_string_values, Constr}) ->
pp_error({bad_named_argument, Args, Name}) -> Msg = io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n",
io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s", [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), [pp(Name), pp_loc(Name),
[ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)]) [ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)])
|| {named_arg_t, _, Arg, Type, _} <- Args ]]); || {named_arg_t, _, Arg, Type, _} <- Args ]]),
pp_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) -> mk_t_err(pos(Name), Msg);
io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n", mk_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) ->
[pp_typed("", Name, Type), pp_loc(Name)]); Msg = io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n",
pp_error({reserved_entrypoint, Name, Def}) -> [pp_typed("", Name, Type), pp_loc(Name)]),
io_lib:format("The name '~s' is reserved and cannot be used for a\ntop-level contract function (at ~s).\n", mk_t_err(pos(Name), Msg);
[Name, pp_loc(Def)]); mk_error({reserved_entrypoint, Name, Def}) ->
pp_error({duplicate_definition, Name, Locs}) -> Msg = io_lib:format("The name '~s' is reserved and cannot be used for a\n"
io_lib:format("Duplicate definitions of ~s at\n~s", "top-level contract function (at ~s).\n", [Name, pp_loc(Def)]),
[Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]); mk_t_err(pos(Def), Msg);
pp_error({duplicate_scope, Kind, Name, OtherKind, L}) -> mk_error({duplicate_definition, Name, Locs}) ->
io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", Msg = io_lib:format("Duplicate definitions of ~s at\n~s",
[Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]); [Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]),
pp_error({include, _, {string, Pos, Name}}) -> mk_t_err(pos(hd(Locs)), Msg);
io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", mk_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
[binary_to_list(Name), pp_loc(Pos)]); Msg = io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n",
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]),
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", mk_t_err(pos(Name), Msg);
[Name, pp_loc(Pos)]); mk_error({include, _, {string, Pos, Name}}) ->
pp_error({repeated_arg, Fun, Arg}) -> Msg = io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n",
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n", [binary_to_list(Name), pp_loc(Pos)]),
[Arg, pp(Fun), pp_loc(Fun)]); mk_t_err(pos(Pos), Msg);
pp_error({stateful_not_allowed, Id, Fun}) -> mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", Msg = io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[pp(Id), pp_loc(Id), pp(Fun)]); [Name, pp_loc(Pos)]),
pp_error({value_arg_not_allowed, Value, Fun}) -> mk_t_err(pos(Pos), Msg);
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n", mk_error({repeated_arg, Fun, Arg}) ->
[pp_expr("", Value), pp_loc(Value), pp(Fun)]); Msg = io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
pp_error({init_depends_on_state, Which, [_Init | Chain]}) -> [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}) ->
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" "contract functions must be declared with the 'entrypoint' keyword instead of\n"
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]); "'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
pp_error({unbound_type, Type}) -> mk_t_err(pos(Con), Msg);
io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]); mk_error({unbound_type, Type}) ->
pp_error({new_tuple_syntax, Ann, Ts}) -> Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]),
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", mk_t_err(pos(Type), Msg);
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]); mk_error({new_tuple_syntax, Ann, Ts}) ->
pp_error(Err) -> 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",
io_lib:format("Unknown error: ~p\n", [Err]). [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,26 +2283,29 @@ 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),
io_lib:format("when checking the definition of ~s (at ~s)\n"
" inferred type: ~s\n" " inferred type: ~s\n"
" given type: ~s\n", " given type: ~s\n",
[Name, pp(instantiate(Inferred)), pp(instantiate(Given))]); [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),
{pos(Fun),
io_lib:format("when checking the application at ~s of\n" io_lib:format("when checking the application at ~s of\n"
"~s\n" "~s\n"
"to arguments\n~s", "to arguments\n~s",
[pp_loc(Fun), [pp_loc(Fun),
pp_typed(" ", Fun, Inferred), pp_typed(" ", Fun, Inferred),
[ [pp_typed(" ", Arg, ArgT), "\n"] [ [pp_typed(" ", Arg, ArgT), "\n"]
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ]); || {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),
{pos(Fld),
case Fld of case Fld of
{field, _Ann, LV, Id, E} -> {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", 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",
@ -2274,73 +2323,74 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
[pp_loc(Fld), [pp_loc(Fld),
pp_typed(" ", Fld, FieldType), pp_typed(" ", Fld, FieldType),
pp_type(" ", InferredType)]) pp_type(" ", InferredType)])
end; 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} ->
{Pos,
io_lib:format("when checking that the record type\n~s\n~s\n" io_lib:format("when checking that the record type\n~s\n~s\n"
"matches the expected type\n~s\n", "matches the expected type\n~s\n",
[pp_type(" ", RecType), [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
pp_why_record(Fld),
pp_type(" ", InferredType)]);
{field, _Ann, _LV, _E} -> {field, _Ann, _LV, _E} ->
{Pos,
io_lib:format("when checking that the record type\n~s\n~s\n" io_lib:format("when checking that the record type\n~s\n~s\n"
"matches the expected type\n~s\n", "matches the expected type\n~s\n",
[pp_type(" ", RecType), [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
pp_why_record(Fld),
pp_type(" ", InferredType)]);
{proj, _Ann, Rec, _FldName} -> {proj, _Ann, Rec, _FldName} ->
{pos(Rec),
io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s\n", io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s\n",
[pp_typed(" ", Rec, InferredType), [pp_typed(" ", Rec, InferredType), pp_loc(Rec),
pp_loc(Rec), pp_type(" ", RecType), WhyRec])}
pp_type(" ", RecType),
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) ] ],
{pos(element(1, hd(Branches))),
io_lib:format("when comparing the types of the if-branches\n" 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)]) "~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)])
|| {B, BType} <- Branches ] ]); || {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}),
{pos(Pat),
io_lib:format("when checking the type of the pattern at ~s\n~s\n" io_lib:format("when checking the type of the pattern at ~s\n~s\n"
"against the expected type\n~s\n", "against the expected type\n~s\n",
[pp_loc(Pat), pp_typed(" ", Pat, PatType), [pp_loc(Pat), pp_typed(" ", Pat, PatType),
pp_type(" ", ExprType)]); 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}),
{pos(Expr),
io_lib:format("when checking the type of the expression at ~s\n~s\n" io_lib:format("when checking the type of the expression at ~s\n~s\n"
"against the expected type\n~s\n", "against the expected type\n~s\n",
[pp_loc(Expr), pp_typed(" ", Expr, Inferred), [pp_loc(Expr), pp_typed(" ", Expr, Inferred),
pp_type(" ", Expected)]); pp_type(" ", Expected)])};
pp_when({checking_init_type, Ann}) -> pp_when({checking_init_type, Ann}) ->
{pos(Ann),
io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n", io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n",
[pp_loc(Ann)]); [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}),
{pos(BindExpr),
io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n" io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n"
"against type \n~s\n", "against type \n~s\n",
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)] [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}) ->
{pos(Fld),
io_lib:format("arising from an assignment of the field ~s (at ~s)", io_lib:format("arising from an assignment of the field ~s (at ~s)",
[pp_expr("", {lvalue, [], LV}), [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
pp_loc(Fld)]);
pp_why_record(Fld = {field, _Ann, LV, _E}) -> pp_why_record(Fld = {field, _Ann, LV, _E}) ->
{pos(Fld),
io_lib:format("arising from an assignment of the field ~s (at ~s)", io_lib:format("arising from an assignment of the field ~s (at ~s)",
[pp_expr("", {lvalue, [], LV}), [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
pp_loc(Fld)]);
pp_why_record({proj, _Ann, Rec, FldName}) -> pp_why_record({proj, _Ann, Rec, FldName}) ->
{pos(Rec),
io_lib:format("arising from the projection of the field ~s (at ~s)", io_lib:format("arising from the projection of the field ~s (at ~s)",
[pp(FldName), [pp(FldName), pp_loc(Rec)])}.
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});
@ -82,7 +81,8 @@ 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}] =