Structured parse_errors and type_errors
This commit is contained in:
parent
9e955d5958
commit
249b61238e
@ -74,15 +74,13 @@ do_contract_interface(Type, ContractString, Options) ->
|
||||
string -> do_render_aci_json(JArray)
|
||||
end
|
||||
catch
|
||||
throw:{type_errors, Errors} -> {error, Errors};
|
||||
%% The compiler errors.
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
|
||||
error:{code_errors, Errors} ->
|
||||
{error, join_errors("Code errors", Errors,
|
||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||
%% General programming errors in the compiler just signal error.
|
||||
end.
|
||||
|
||||
join_errors(Prefix, Errors, Pfun) ->
|
||||
|
@ -36,6 +36,8 @@
|
||||
-type why_record() :: aeso_syntax:field(aeso_syntax:expr())
|
||||
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
|
||||
|
||||
-type pos() :: aeso_errors:pos().
|
||||
|
||||
-record(named_argument_constraint,
|
||||
{args :: named_args_t(),
|
||||
name :: aeso_syntax:id(),
|
||||
@ -535,7 +537,7 @@ global_env() ->
|
||||
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
|
||||
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
||||
|
||||
-spec infer(aeso_syntax:ast()) -> aeso_syntax:ast().
|
||||
-spec infer(aeso_syntax:ast()) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}.
|
||||
infer(Contracts) ->
|
||||
infer(Contracts, []).
|
||||
|
||||
@ -544,7 +546,8 @@ infer(Contracts) ->
|
||||
-spec init_env(list(option())) -> env().
|
||||
init_env(_Options) -> global_env().
|
||||
|
||||
-spec infer(aeso_syntax:ast(), list(option())) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}.
|
||||
-spec infer(aeso_syntax:ast(), list(option())) ->
|
||||
aeso_syntax:ast() | {env(), aeso_syntax:ast()}.
|
||||
infer(Contracts, Options) ->
|
||||
ets_init(), %% Init the ETS table state
|
||||
try
|
||||
@ -2059,12 +2062,11 @@ create_type_errors() ->
|
||||
ets_new(type_errors, [bag]).
|
||||
|
||||
destroy_and_report_type_errors(Env) ->
|
||||
Errors = lists:reverse(ets_tab2list(type_errors)),
|
||||
%% io:format("Type errors now: ~p\n", [Errors]),
|
||||
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
|
||||
Errors0 = lists:reverse(ets_tab2list(type_errors)),
|
||||
%% io:format("Type errors now: ~p\n", [Errors0]),
|
||||
ets_delete(type_errors),
|
||||
Errors /= [] andalso
|
||||
error({type_errors, [lists:flatten(Err) || Err <- PPErrors]}).
|
||||
Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ],
|
||||
Errors == [] orelse throw({type_errors, Errors}).
|
||||
|
||||
%% Strip current namespace from error message for nicer printing.
|
||||
unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) ->
|
||||
@ -2083,153 +2085,197 @@ unqualify1(NS, Xs) ->
|
||||
catch _:_ -> Xs
|
||||
end.
|
||||
|
||||
pp_error({cannot_unify, A, B, When}) ->
|
||||
io_lib:format("Cannot unify ~s\n"
|
||||
" and ~s\n"
|
||||
"~s", [pp(instantiate(A)), pp(instantiate(B)), pp_when(When)]);
|
||||
pp_error({unbound_variable, Id}) ->
|
||||
io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
||||
pp_error({undefined_field, Id}) ->
|
||||
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
||||
pp_error({not_a_record_type, Type, Why}) ->
|
||||
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]);
|
||||
pp_error({not_a_contract_type, Type, Lit}) ->
|
||||
io_lib:format("The type ~s is not a contract type\n"
|
||||
mk_t_err(Pos, Msg) ->
|
||||
aeso_errors:new(type_error, Pos, lists:flatten(Msg)).
|
||||
mk_t_err(Pos, Msg, Ctxt) ->
|
||||
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
|
||||
|
||||
mk_error({cannot_unify, A, B, When}) ->
|
||||
Msg = io_lib:format("Cannot unify ~s\n and ~s\n",
|
||||
[pp(instantiate(A)), pp(instantiate(B))]),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({unbound_variable, Id}) ->
|
||||
Msg = io_lib:format("Unbound variable ~s at ~s\n", [pp(Id), pp_loc(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({undefined_field, Id}) ->
|
||||
Msg = io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({not_a_record_type, Type, Why}) ->
|
||||
Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]),
|
||||
{Pos, Ctxt} = pp_why_record(Why),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({not_a_contract_type, Type, Lit}) ->
|
||||
Msg = io_lib:format("The type ~s is not a contract type\n"
|
||||
"when checking that the contract literal at ~s\n~s\n"
|
||||
"has the type\n~s\n",
|
||||
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]);
|
||||
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||
Plural = [ $s || length(Nonlinear) > 1 ],
|
||||
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
||||
[Plural, string:join(Nonlinear, ", "), pp_expr(" ", Pattern), pp_loc(Pattern)]);
|
||||
pp_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) ->
|
||||
S = [ "s" || length(Fields) > 1 ],
|
||||
io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s",
|
||||
[S, string:join([ pp(F) || {_, F} <- Fields ], ", "),
|
||||
pp_loc(First),
|
||||
[ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]);
|
||||
pp_error({missing_field, Field, Rec}) ->
|
||||
io_lib:format("Record type ~s does not have field ~s (at ~s)\n", [pp(Rec), pp(Field), pp_loc(Field)]);
|
||||
pp_error({missing_fields, Ann, RecType, Fields}) ->
|
||||
Many = length(Fields) > 1,
|
||||
S = [ "s" || Many ],
|
||||
Are = if Many -> "are"; true -> "is" end,
|
||||
io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n",
|
||||
[S, string:join(Fields, ", "), Are, pp(RecType), pp_loc(Ann)]);
|
||||
pp_error({no_records_with_all_fields, Fields = [{_, First} | _]}) ->
|
||||
S = [ "s" || length(Fields) > 1 ],
|
||||
io_lib:format("No record type with field~s ~s (at ~s)\n",
|
||||
[S, string:join([ pp(F) || {_, F} <- Fields ], ", "),
|
||||
pp_loc(First)]);
|
||||
pp_error({recursive_types_not_implemented, Types}) ->
|
||||
S = if length(Types) > 1 -> "s are mutually";
|
||||
true -> " is" end,
|
||||
io_lib:format("The following type~s recursive, which is not yet supported:\n~s",
|
||||
[S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]);
|
||||
pp_error({event_must_be_variant_type, Where}) ->
|
||||
io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]);
|
||||
pp_error({indexed_type_must_be_word, Type, Type}) ->
|
||||
io_lib:format("The indexed type ~s (at ~s) is not a word type\n",
|
||||
[pp_type("", Type), pp_loc(Type)]);
|
||||
pp_error({indexed_type_must_be_word, Type, Type1}) ->
|
||||
io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n",
|
||||
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]);
|
||||
pp_error({payload_type_must_be_string, Type, Type}) ->
|
||||
io_lib:format("The payload type ~s (at ~s) should be string\n",
|
||||
[pp_type("", Type), pp_loc(Type)]);
|
||||
pp_error({payload_type_must_be_string, Type, Type1}) ->
|
||||
io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n",
|
||||
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]);
|
||||
pp_error({event_0_to_3_indexed_values, Constr}) ->
|
||||
io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n",
|
||||
[name(Constr), pp_loc(Constr)]);
|
||||
pp_error({event_0_to_1_string_values, Constr}) ->
|
||||
io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n",
|
||||
[name(Constr), pp_loc(Constr)]);
|
||||
pp_error({repeated_constructor, Cs}) ->
|
||||
io_lib:format("Variant types must have distinct constructor names\n~s",
|
||||
[[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]);
|
||||
pp_error({bad_named_argument, [], Name}) ->
|
||||
io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n",
|
||||
[pp(Name), pp_loc(Name)]);
|
||||
pp_error({bad_named_argument, Args, Name}) ->
|
||||
io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s",
|
||||
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]),
|
||||
mk_t_err(pos(Lit), Msg);
|
||||
mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||
Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
||||
[plural("", "s", Nonlinear), string:join(Nonlinear, ", "),
|
||||
pp_expr(" ", Pattern), pp_loc(Pattern)]),
|
||||
mk_t_err(pos(Pattern), Msg);
|
||||
mk_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) ->
|
||||
Msg = io_lib:format("Ambiguous record type with field~s ~s (at ~s) could be one of\n~s",
|
||||
[plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "),
|
||||
pp_loc(First), [ [" - ", pp(C), " (at ", pp_loc(C), ")\n"] || C <- Candidates ]]),
|
||||
mk_t_err(pos(First), Msg);
|
||||
mk_error({missing_field, Field, Rec}) ->
|
||||
Msg = io_lib:format("Record type ~s does not have field ~s (at ~s)\n",
|
||||
[pp(Rec), pp(Field), pp_loc(Field)]),
|
||||
mk_t_err(pos(Field), Msg);
|
||||
mk_error({missing_fields, Ann, RecType, Fields}) ->
|
||||
Msg = io_lib:format("The field~s ~s ~s missing when constructing an element of type ~s (at ~s)\n",
|
||||
[plural("", "s", Fields), string:join(Fields, ", "),
|
||||
plural("is", "are", Fields), pp(RecType), pp_loc(Ann)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({no_records_with_all_fields, Fields = [{_, First} | _]}) ->
|
||||
Msg = io_lib:format("No record type with field~s ~s (at ~s)\n",
|
||||
[plural("", "s", Fields), string:join([ pp(F) || {_, F} <- Fields ], ", "),
|
||||
pp_loc(First)]),
|
||||
mk_t_err(pos(First), Msg);
|
||||
mk_error({recursive_types_not_implemented, Types}) ->
|
||||
S = plural(" is", "s are mutually", Types),
|
||||
Msg = io_lib:format("The following type~s recursive, which is not yet supported:\n~s",
|
||||
[S, [io_lib:format(" - ~s (at ~s)\n", [pp(T), pp_loc(T)]) || T <- Types]]),
|
||||
mk_t_err(pos(hd(Types)), Msg);
|
||||
mk_error({event_must_be_variant_type, Where}) ->
|
||||
Msg = io_lib:format("The event type must be a variant type (at ~s)\n", [pp_loc(Where)]),
|
||||
mk_t_err(pos(Where), Msg);
|
||||
mk_error({indexed_type_must_be_word, Type, Type}) ->
|
||||
Msg = io_lib:format("The indexed type ~s (at ~s) is not a word type\n",
|
||||
[pp_type("", Type), pp_loc(Type)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({indexed_type_must_be_word, Type, Type1}) ->
|
||||
Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n",
|
||||
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({payload_type_must_be_string, Type, Type}) ->
|
||||
Msg = io_lib:format("The payload type ~s (at ~s) should be string\n",
|
||||
[pp_type("", Type), pp_loc(Type)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({payload_type_must_be_string, Type, Type1}) ->
|
||||
Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n",
|
||||
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({event_0_to_3_indexed_values, Constr}) ->
|
||||
Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n",
|
||||
[name(Constr), pp_loc(Constr)]),
|
||||
mk_t_err(pos(Constr), Msg);
|
||||
mk_error({event_0_to_1_string_values, Constr}) ->
|
||||
Msg = io_lib:format("The event constructor ~s (at ~s) has too many non-indexed values (max 1)\n",
|
||||
[name(Constr), pp_loc(Constr)]),
|
||||
mk_t_err(pos(Constr), Msg);
|
||||
mk_error({repeated_constructor, Cs}) ->
|
||||
Msg = io_lib:format("Variant types must have distinct constructor names\n~s",
|
||||
[[ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]),
|
||||
mk_t_err(pos(element(1, hd(Cs))), Msg);
|
||||
mk_error({bad_named_argument, [], Name}) ->
|
||||
Msg = io_lib:format("Named argument ~s (at ~s) supplied to function expecting no named arguments.\n",
|
||||
[pp(Name), pp_loc(Name)]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({bad_named_argument, Args, Name}) ->
|
||||
Msg = io_lib:format("Named argument ~s (at ~s) is not one of the expected named arguments\n~s",
|
||||
[pp(Name), pp_loc(Name),
|
||||
[ io_lib:format("~s\n", [pp_typed(" - ", Arg, Type)])
|
||||
|| {named_arg_t, _, Arg, Type, _} <- Args ]]);
|
||||
pp_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) ->
|
||||
io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n",
|
||||
[pp_typed("", Name, Type), pp_loc(Name)]);
|
||||
pp_error({reserved_entrypoint, Name, Def}) ->
|
||||
io_lib:format("The name '~s' is reserved and cannot be used for a\ntop-level contract function (at ~s).\n",
|
||||
[Name, pp_loc(Def)]);
|
||||
pp_error({duplicate_definition, Name, Locs}) ->
|
||||
io_lib:format("Duplicate definitions of ~s at\n~s",
|
||||
[Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]);
|
||||
pp_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
|
||||
io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n",
|
||||
[Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]);
|
||||
pp_error({include, _, {string, Pos, Name}}) ->
|
||||
io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n",
|
||||
[binary_to_list(Name), pp_loc(Pos)]);
|
||||
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
||||
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
||||
[Name, pp_loc(Pos)]);
|
||||
pp_error({repeated_arg, Fun, Arg}) ->
|
||||
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
|
||||
[Arg, pp(Fun), pp_loc(Fun)]);
|
||||
pp_error({stateful_not_allowed, Id, Fun}) ->
|
||||
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp(Id), pp_loc(Id), pp(Fun)]);
|
||||
pp_error({value_arg_not_allowed, Value, Fun}) ->
|
||||
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
|
||||
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
|
||||
|| {named_arg_t, _, Arg, Type, _} <- Args ]]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) ->
|
||||
Msg = io_lib:format("Named argument ~s (at ~s) supplied to function with unknown named arguments.\n",
|
||||
[pp_typed("", Name, Type), pp_loc(Name)]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({reserved_entrypoint, Name, Def}) ->
|
||||
Msg = io_lib:format("The name '~s' is reserved and cannot be used for a\n"
|
||||
"top-level contract function (at ~s).\n", [Name, pp_loc(Def)]),
|
||||
mk_t_err(pos(Def), Msg);
|
||||
mk_error({duplicate_definition, Name, Locs}) ->
|
||||
Msg = io_lib:format("Duplicate definitions of ~s at\n~s",
|
||||
[Name, [ [" - ", pp_loc(L), "\n"] || L <- Locs ]]),
|
||||
mk_t_err(pos(hd(Locs)), Msg);
|
||||
mk_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
|
||||
Msg = io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n",
|
||||
[Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({include, _, {string, Pos, Name}}) ->
|
||||
Msg = io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n",
|
||||
[binary_to_list(Name), pp_loc(Pos)]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
||||
Msg = io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
||||
[Name, pp_loc(Pos)]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({repeated_arg, Fun, Arg}) ->
|
||||
Msg = io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
|
||||
[Arg, pp(Fun), pp_loc(Fun)]),
|
||||
mk_t_err(pos(Fun), Msg);
|
||||
mk_error({stateful_not_allowed, Id, Fun}) ->
|
||||
Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp(Id), pp_loc(Id), pp(Fun)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({value_arg_not_allowed, Value, Fun}) ->
|
||||
Msg = io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp_expr("", Value), pp_loc(Value), pp(Fun)]),
|
||||
mk_t_err(pos(Value), Msg);
|
||||
mk_error({init_depends_on_state, Which, [_Init | Chain]}) ->
|
||||
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
|
||||
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
|
||||
Msg = io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
|
||||
[if Which == put -> "write"; true -> "read" end,
|
||||
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|
||||
|| {[_, Fun], Ann} <- Chain]]);
|
||||
pp_error({missing_body_for_let, Ann}) ->
|
||||
io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]);
|
||||
pp_error({public_modifier_in_contract, Decl}) ->
|
||||
|| {[_, Fun], Ann} <- Chain]]),
|
||||
mk_t_err(pos(element(2, hd(Chain))), Msg);
|
||||
mk_error({missing_body_for_let, Ann}) ->
|
||||
Msg = io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({public_modifier_in_contract, Decl}) ->
|
||||
Decl1 = mk_entrypoint(Decl),
|
||||
io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n",
|
||||
Msg = io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n",
|
||||
[pp_expr("", element(3, Decl)), pp_loc(Decl),
|
||||
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
|
||||
pp_error({init_must_be_an_entrypoint, Decl}) ->
|
||||
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({init_must_be_an_entrypoint, Decl}) ->
|
||||
Decl1 = mk_entrypoint(Decl),
|
||||
io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n",
|
||||
Msg = io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n",
|
||||
[pp_loc(Decl),
|
||||
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
|
||||
pp_error({proto_must_be_entrypoint, Decl}) ->
|
||||
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({proto_must_be_entrypoint, Decl}) ->
|
||||
Decl1 = mk_entrypoint(Decl),
|
||||
io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n",
|
||||
Msg = io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n",
|
||||
[pp_expr("", element(3, Decl)), pp_loc(Decl),
|
||||
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
|
||||
pp_error({proto_in_namespace, Decl}) ->
|
||||
io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n",
|
||||
[pp_loc(Decl)]);
|
||||
pp_error({entrypoint_in_namespace, Decl}) ->
|
||||
io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n",
|
||||
[pp_loc(Decl)]);
|
||||
pp_error({private_entrypoint, Decl}) ->
|
||||
io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n",
|
||||
[pp_expr("", element(3, Decl)), pp_loc(Decl)]);
|
||||
pp_error({private_and_public, Decl}) ->
|
||||
io_lib:format("The function ~s (at ~s) cannot be both public and private.\n",
|
||||
[pp_expr("", element(3, Decl)), pp_loc(Decl)]);
|
||||
pp_error({contract_has_no_entrypoints, Con}) ->
|
||||
io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n"
|
||||
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({proto_in_namespace, Decl}) ->
|
||||
Msg = io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n",
|
||||
[pp_loc(Decl)]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({entrypoint_in_namespace, Decl}) ->
|
||||
Msg = io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n",
|
||||
[pp_loc(Decl)]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({private_entrypoint, Decl}) ->
|
||||
Msg = io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n",
|
||||
[pp_expr("", element(3, Decl)), pp_loc(Decl)]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({private_and_public, Decl}) ->
|
||||
Msg = io_lib:format("The function ~s (at ~s) cannot be both public and private.\n",
|
||||
[pp_expr("", element(3, Decl)), pp_loc(Decl)]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({contract_has_no_entrypoints, Con}) ->
|
||||
Msg = io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n"
|
||||
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
|
||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]);
|
||||
pp_error({unbound_type, Type}) ->
|
||||
io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]);
|
||||
pp_error({new_tuple_syntax, Ann, Ts}) ->
|
||||
io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n",
|
||||
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]);
|
||||
pp_error(Err) ->
|
||||
io_lib:format("Unknown error: ~p\n", [Err]).
|
||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
||||
mk_t_err(pos(Con), Msg);
|
||||
mk_error({unbound_type, Type}) ->
|
||||
Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({new_tuple_syntax, Ann, Ts}) ->
|
||||
Msg = io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n",
|
||||
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
|
||||
mk_entrypoint(Decl) ->
|
||||
Ann = [entrypoint | lists:keydelete(public, 1,
|
||||
@ -2237,26 +2283,29 @@ mk_entrypoint(Decl) ->
|
||||
aeso_syntax:get_ann(Decl))) -- [public, private]],
|
||||
aeso_syntax:set_ann(Ann, Decl).
|
||||
|
||||
pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]);
|
||||
pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]);
|
||||
pp_when({todo, What}) -> {pos(0, 0), io_lib:format("[TODO] ~p\n", [What])};
|
||||
pp_when({at, Ann}) -> {pos(Ann), io_lib:format("at ~s\n", [pp_loc(Ann)])};
|
||||
pp_when({check_typesig, Name, Inferred, Given}) ->
|
||||
io_lib:format("when checking the definition of ~s\n"
|
||||
{pos(Given),
|
||||
io_lib:format("when checking the definition of ~s (at ~s)\n"
|
||||
" inferred 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}) ->
|
||||
Inferred = instantiate(Inferred0),
|
||||
ArgTypes = instantiate(ArgTypes0),
|
||||
{pos(Fun),
|
||||
io_lib:format("when checking the application at ~s of\n"
|
||||
"~s\n"
|
||||
"to arguments\n~s",
|
||||
[pp_loc(Fun),
|
||||
pp_typed(" ", Fun, Inferred),
|
||||
[ [pp_typed(" ", Arg, ArgT), "\n"]
|
||||
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ]);
|
||||
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])};
|
||||
pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
||||
FieldType = instantiate(FieldType0),
|
||||
InferredType = instantiate(InferredType0),
|
||||
{pos(Fld),
|
||||
case Fld of
|
||||
{field, _Ann, LV, Id, E} ->
|
||||
io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n",
|
||||
@ -2274,73 +2323,74 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
||||
[pp_loc(Fld),
|
||||
pp_typed(" ", Fld, FieldType),
|
||||
pp_type(" ", InferredType)])
|
||||
end;
|
||||
end};
|
||||
pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
|
||||
RecType = instantiate(RecType0),
|
||||
InferredType = instantiate(InferredType0),
|
||||
{Pos, WhyRec} = pp_why_record(Fld),
|
||||
case Fld of
|
||||
{field, _Ann, _LV, _Id, _E} ->
|
||||
{Pos,
|
||||
io_lib:format("when checking that the record type\n~s\n~s\n"
|
||||
"matches the expected type\n~s\n",
|
||||
[pp_type(" ", RecType),
|
||||
pp_why_record(Fld),
|
||||
pp_type(" ", InferredType)]);
|
||||
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
|
||||
{field, _Ann, _LV, _E} ->
|
||||
{Pos,
|
||||
io_lib:format("when checking that the record type\n~s\n~s\n"
|
||||
"matches the expected type\n~s\n",
|
||||
[pp_type(" ", RecType),
|
||||
pp_why_record(Fld),
|
||||
pp_type(" ", InferredType)]);
|
||||
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
|
||||
{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",
|
||||
[pp_typed(" ", Rec, InferredType),
|
||||
pp_loc(Rec),
|
||||
pp_type(" ", RecType),
|
||||
pp_why_record(Fld)])
|
||||
[pp_typed(" ", Rec, InferredType), pp_loc(Rec),
|
||||
pp_type(" ", RecType), WhyRec])}
|
||||
end;
|
||||
pp_when({if_branches, Then, ThenType0, Else, ElseType0}) ->
|
||||
{ThenType, ElseType} = instantiate({ThenType0, ElseType0}),
|
||||
Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ],
|
||||
{pos(element(1, hd(Branches))),
|
||||
io_lib:format("when comparing the types of the if-branches\n"
|
||||
"~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)])
|
||||
|| {B, BType} <- Branches ] ]);
|
||||
|| {B, BType} <- Branches ] ])};
|
||||
pp_when({case_pat, Pat, PatType0, ExprType0}) ->
|
||||
{PatType, ExprType} = instantiate({PatType0, ExprType0}),
|
||||
{pos(Pat),
|
||||
io_lib:format("when checking the type of the pattern at ~s\n~s\n"
|
||||
"against the expected type\n~s\n",
|
||||
[pp_loc(Pat), pp_typed(" ", Pat, PatType),
|
||||
pp_type(" ", ExprType)]);
|
||||
pp_type(" ", ExprType)])};
|
||||
pp_when({check_expr, Expr, Inferred0, Expected0}) ->
|
||||
{Inferred, Expected} = instantiate({Inferred0, Expected0}),
|
||||
{pos(Expr),
|
||||
io_lib:format("when checking the type of the expression at ~s\n~s\n"
|
||||
"against the expected type\n~s\n",
|
||||
[pp_loc(Expr), pp_typed(" ", Expr, Inferred),
|
||||
pp_type(" ", Expected)]);
|
||||
pp_type(" ", Expected)])};
|
||||
pp_when({checking_init_type, Ann}) ->
|
||||
{pos(Ann),
|
||||
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}) ->
|
||||
{Inferred, Expected} = instantiate({Inferred0, Expected0}),
|
||||
{pos(BindExpr),
|
||||
io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n"
|
||||
"against type \n~s\n",
|
||||
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)]
|
||||
);
|
||||
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)])};
|
||||
pp_when(unknown) -> {pos(0,0), ""}.
|
||||
|
||||
pp_when(unknown) -> "".
|
||||
|
||||
-spec pp_why_record(why_record()) -> iolist().
|
||||
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
||||
pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||
[pp_expr("", {lvalue, [], LV}),
|
||||
pp_loc(Fld)]);
|
||||
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
|
||||
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||
[pp_expr("", {lvalue, [], LV}),
|
||||
pp_loc(Fld)]);
|
||||
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
|
||||
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
||||
{pos(Rec),
|
||||
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
||||
[pp(FldName),
|
||||
pp_loc(Rec)]).
|
||||
[pp(FldName), pp_loc(Rec)])}.
|
||||
|
||||
|
||||
if_branches(If = {'if', Ann, _, Then, Else}) ->
|
||||
@ -2362,9 +2412,13 @@ pp_expr(Label, Expr) ->
|
||||
pp_type(Label, Type) ->
|
||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))).
|
||||
|
||||
src_file(T) -> aeso_syntax:get_ann(src_file, T, no_file).
|
||||
line_number(T) -> aeso_syntax:get_ann(line, T, 0).
|
||||
column_number(T) -> aeso_syntax:get_ann(col, T, 0).
|
||||
|
||||
pos(T) -> aeso_errors:pos(src_file(T), line_number(T), column_number(T)).
|
||||
pos(L, C) -> aeso_errors:pos(L, C).
|
||||
|
||||
loc(T) ->
|
||||
{line_number(T), column_number(T)}.
|
||||
|
||||
@ -2375,6 +2429,9 @@ pp_loc(T) ->
|
||||
_ -> io_lib:format("line ~p, column ~p", [Line, Col])
|
||||
end.
|
||||
|
||||
plural(No, _Yes, [_]) -> No;
|
||||
plural(_No, Yes, _) -> Yes.
|
||||
|
||||
pp(T = {type_sig, _, _, _, _}) ->
|
||||
pp(typesig_to_fun_t(T));
|
||||
pp([]) ->
|
||||
|
@ -99,10 +99,10 @@ from_string(Backend, ContractString, Options) ->
|
||||
from_string1(Backend, ContractString, Options)
|
||||
catch
|
||||
%% The compiler errors.
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
|
||||
throw:{parse_errors, Errors} ->
|
||||
{error, Errors};
|
||||
throw:{type_errors, Errors} ->
|
||||
{error, Errors};
|
||||
error:{code_errors, Errors} ->
|
||||
{error, join_errors("Code errors", Errors,
|
||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||
@ -230,10 +230,10 @@ check_call1(ContractString0, FunName, Args, Options) ->
|
||||
{ok, FunName, CallArgs}
|
||||
end
|
||||
catch
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
||||
throw:{parse_errors, Errors} ->
|
||||
{error, Errors};
|
||||
throw:{type_errors, Errors} ->
|
||||
{error, Errors};
|
||||
error:{badmatch, {error, missing_call_function}} ->
|
||||
{error, join_errors("Type errors", ["missing __call function"],
|
||||
fun (E) -> E end)};
|
||||
@ -345,10 +345,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
|
||||
end
|
||||
end
|
||||
catch
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
||||
throw:{parse_errors, Errors} ->
|
||||
{error, Errors};
|
||||
throw:{type_errors, Errors} ->
|
||||
{error, Errors};
|
||||
error:{badmatch, {error, missing_function}} ->
|
||||
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
|
||||
fun (E) -> E end)};
|
||||
@ -444,10 +444,10 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
|
||||
end
|
||||
end
|
||||
catch
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
||||
throw:{parse_errors, Errors} ->
|
||||
{error, Errors};
|
||||
throw:{type_errors, Errors} ->
|
||||
{error, Errors};
|
||||
error:{badmatch, {error, missing_function}} ->
|
||||
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
|
||||
fun (E) -> E end)};
|
||||
@ -582,37 +582,7 @@ parse(Text, Options) ->
|
||||
|
||||
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
|
||||
parse(Text, Included, Options) ->
|
||||
%% Try and return something sensible here!
|
||||
case aeso_parser:string(Text, Included, Options) of
|
||||
%% Yay, it worked!
|
||||
{ok, Contract} -> Contract;
|
||||
%% Scan errors.
|
||||
{error, {Pos, scan_error}} ->
|
||||
parse_error(Pos, "scan error");
|
||||
{error, {Pos, scan_error_no_state}} ->
|
||||
parse_error(Pos, "scan error");
|
||||
%% Parse errors.
|
||||
{error, {Pos, parse_error, Error}} ->
|
||||
parse_error(Pos, Error);
|
||||
{error, {Pos, ambiguous_parse, As}} ->
|
||||
ErrorString = io_lib:format("Ambiguous ~p", [As]),
|
||||
parse_error(Pos, ErrorString);
|
||||
%% Include error
|
||||
{error, {Pos, include_error, File}} ->
|
||||
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
|
||||
end.
|
||||
|
||||
-spec parse_error(aeso_parse_lib:pos(), string()) -> none().
|
||||
parse_error(Pos, ErrorString) ->
|
||||
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
|
||||
error({parse_errors, [Error]}).
|
||||
aeso_parser:string(Text, Included, Options).
|
||||
|
||||
read_contract(Name) ->
|
||||
file:read_file(Name).
|
||||
|
||||
pos_error({Line, Pos}) ->
|
||||
io_lib:format("line ~p, column ~p", [Line, Pos]);
|
||||
pos_error({no_file, Line, Pos}) ->
|
||||
pos_error({Line, Pos});
|
||||
pos_error({File, Line, Pos}) ->
|
||||
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
|
||||
|
71
src/aeso_errors.erl
Normal file
71
src/aeso_errors.erl
Normal 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]).
|
@ -9,7 +9,7 @@
|
||||
-module(aeso_parse_lib).
|
||||
|
||||
-export([parse/2,
|
||||
return/1, fail/0, fail/1, map/2, bind/2,
|
||||
return/1, fail/0, fail/1, fail/2, map/2, bind/2,
|
||||
lazy/1, choice/1, choice/2, tok/1, layout/0,
|
||||
left/2, right/2, between/3, optional/1,
|
||||
many/1, many1/1, sep/2, sep1/2,
|
||||
@ -98,6 +98,10 @@ apply_p(X, K) -> K(X).
|
||||
-spec lazy(fun(() -> parser(A))) -> parser(A).
|
||||
lazy(Delayed) -> ?lazy(Delayed).
|
||||
|
||||
%% @doc A parser that always fails at a known location.
|
||||
-spec fail(pos(), term()) -> parser(none()).
|
||||
fail(Pos, Err) -> ?fail({Pos, Err}).
|
||||
|
||||
%% @doc A parser that always fails.
|
||||
-spec fail(term()) -> parser(none()).
|
||||
fail(Err) -> ?fail(Err).
|
||||
@ -322,6 +326,8 @@ current_pos(#ts{ tokens = [T | _] }) -> pos(T);
|
||||
current_pos(#ts{ last = T }) -> end_pos(pos(T)).
|
||||
|
||||
-spec mk_error(#ts{}, term()) -> error().
|
||||
mk_error(_Ts, {Pos, Err}) ->
|
||||
{Pos, Err};
|
||||
mk_error(Ts, Err) ->
|
||||
{current_pos(Ts), Err}.
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
-import(aeso_parse_lib,
|
||||
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
||||
infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0,
|
||||
fail/0, fail/1, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2,
|
||||
fail/0, fail/1, fail/2, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2,
|
||||
left/2, right/2, optional/1]).
|
||||
|
||||
|
||||
|
@ -12,9 +12,7 @@
|
||||
|
||||
-include("aeso_parse_lib.hrl").
|
||||
|
||||
-type parse_result() :: {ok, aeso_syntax:ast()}
|
||||
| {error, {aeso_parse_lib:pos(), atom(), term()}}
|
||||
| {error, {aeso_parse_lib:pos(), atom()}}.
|
||||
-type parse_result() :: aeso_syntax:ast() | none().
|
||||
|
||||
-type include_hash() :: {string(), binary()}.
|
||||
|
||||
@ -33,13 +31,19 @@ string(String, Opts) ->
|
||||
string(String, Included, Opts) ->
|
||||
case parse_and_scan(file(), String, Opts) of
|
||||
{ok, AST} ->
|
||||
expand_includes(AST, Included, Opts);
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
case expand_includes(AST, Included, Opts) of
|
||||
{ok, AST1} -> AST1;
|
||||
{error, Err} -> throw({parse_errors, [mk_error(Err)]})
|
||||
end;
|
||||
{error, Err} ->
|
||||
throw({parse_errors, [mk_error(Err)]})
|
||||
end.
|
||||
|
||||
type(String) ->
|
||||
parse_and_scan(type(), String, []).
|
||||
case parse_and_scan(type(), String, []) of
|
||||
{ok, AST} -> {ok, AST};
|
||||
{error, Err} -> {error, [mk_error(Err)]}
|
||||
end.
|
||||
|
||||
parse_and_scan(P, S, Opts) ->
|
||||
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||
@ -48,6 +52,24 @@ parse_and_scan(P, S, Opts) ->
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
mk_p_err(Pos, Msg) ->
|
||||
aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
|
||||
|
||||
mk_error({Pos, ScanE}) when ScanE == scan_error; ScanE == scan_error_no_state ->
|
||||
mk_p_err(Pos, "Scan error\n");
|
||||
mk_error({Pos, parse_error, Err}) ->
|
||||
Msg = io_lib:format("~s\n", [Err]),
|
||||
mk_p_err(Pos, Msg);
|
||||
mk_error({Pos, ambiguous_parse, As}) ->
|
||||
Msg = io_lib:format("Ambiguous parse result: ~p\n", [As]),
|
||||
mk_p_err(Pos, Msg);
|
||||
mk_error({Pos, include_error, File}) ->
|
||||
Msg = io_lib:format("Couldn't find include file '~s'\n", [File]),
|
||||
mk_p_err(Pos, Msg).
|
||||
|
||||
mk_pos({Line, Col}) -> aeso_errors:pos(Line, Col);
|
||||
mk_pos({File, Line, Col}) -> aeso_errors:pos(File, Line, Col).
|
||||
|
||||
%% -- Parsing rules ----------------------------------------------------------
|
||||
|
||||
file() -> choice([], block(decl())).
|
||||
@ -533,14 +555,9 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
||||
parse_field_pattern({field, Ann, F, E}) ->
|
||||
{field, Ann, F, parse_pattern(E)}.
|
||||
|
||||
return_error({no_file, L, C}, Err) ->
|
||||
fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
|
||||
return_error({F, L, C}, Err) ->
|
||||
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
|
||||
|
||||
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
|
||||
ret_doc_err(Ann, Doc) ->
|
||||
return_error(ann_pos(Ann), prettypr:format(Doc)).
|
||||
fail(ann_pos(Ann), prettypr:format(Doc)).
|
||||
|
||||
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
|
||||
bad_expr_err(Reason, E) ->
|
||||
@ -600,7 +617,7 @@ stdlib_options() ->
|
||||
get_include_code(File, Ann, Opts) ->
|
||||
case {read_file(File, Opts), read_file(File, stdlib_options())} of
|
||||
{{ok, _}, {ok,_ }} ->
|
||||
return_error(ann_pos(Ann), "Illegal redefinition of standard library " ++ File);
|
||||
fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File);
|
||||
{_, {ok, Bin}} ->
|
||||
{ok, binary_to_list(Bin)};
|
||||
{{ok, Bin}, _} ->
|
||||
|
@ -161,8 +161,10 @@ permissive_literals_fail_test() ->
|
||||
"contract OracleTest =\n"
|
||||
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
|
||||
" Chain.spend(o, 1000000)\n",
|
||||
{error, <<"Type errors\nCannot unify", _/binary>>} =
|
||||
{error, [Err]} =
|
||||
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
||||
?assertMatch("Cannot unify" ++ _, aeso_errors:pp(Err)),
|
||||
?assertEqual(type_error, aeso_errors:type(Err)),
|
||||
ok.
|
||||
|
||||
encode_decode_calldata(FunName, Types, Args) ->
|
||||
|
@ -107,11 +107,11 @@ aci_test_contract(Name) ->
|
||||
|
||||
check_stub(Stub, Options) ->
|
||||
case aeso_parser:string(binary_to_list(Stub), Options) of
|
||||
{ok, Ast} ->
|
||||
Ast ->
|
||||
try
|
||||
%% io:format("AST: ~120p\n", [Ast]),
|
||||
aeso_ast_infer_types:infer(Ast, [])
|
||||
catch _:{type_errors, TE} ->
|
||||
catch throw:{type_errors, TE} ->
|
||||
io:format("Type error:\n~s\n", [TE]),
|
||||
error(TE);
|
||||
_:R ->
|
||||
|
@ -35,10 +35,10 @@ simple_compile_test_() ->
|
||||
[ {"Testing error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
case compile(aevm, ContractName) of
|
||||
<<"Type errors\n", ErrorString/binary>> ->
|
||||
check_errors(lists:sort(ExpectedErrors), ErrorString);
|
||||
<<"Parse errors\n", ErrorString/binary>> ->
|
||||
check_errors(lists:sort(ExpectedErrors), ErrorString)
|
||||
check_errors(lists:sort(ExpectedErrors), ErrorString);
|
||||
Errors ->
|
||||
check_errors(lists:sort(ExpectedErrors), Errors)
|
||||
end
|
||||
end} ||
|
||||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
||||
@ -65,9 +65,8 @@ simple_compile_test_() ->
|
||||
ok
|
||||
end} || Backend <- [aevm, fate] ].
|
||||
|
||||
check_errors(Expect, ErrorString) ->
|
||||
%% This removes the final single \n as well.
|
||||
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
|
||||
check_errors(Expect, Actual0) ->
|
||||
Actual = [ list_to_binary(string:trim(aeso_errors:msg(Err))) || Err <- Actual0 ],
|
||||
case {Expect -- Actual, Actual -- Expect} of
|
||||
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
|
||||
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
|
||||
@ -82,7 +81,8 @@ compile(Backend, Name, Options) ->
|
||||
String = aeso_test_utils:read_contract(Name),
|
||||
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of
|
||||
{ok, Map} -> Map;
|
||||
{error, ErrorString} -> ErrorString
|
||||
{error, ErrorString} when is_binary(ErrorString) -> ErrorString;
|
||||
{error, Errors} -> Errors
|
||||
end.
|
||||
|
||||
%% compilable_contracts() -> [ContractName].
|
||||
@ -353,8 +353,7 @@ failing_contracts() ->
|
||||
"but it calls\n"
|
||||
" - state (at line 13, column 13)">>]}
|
||||
, {"field_parse_error",
|
||||
[<<"line 6, column 1: In field_parse_error at 5:26:\n"
|
||||
"Cannot use nested fields or keys in record construction: p.x\n">>]}
|
||||
[<<"Cannot use nested fields or keys in record construction: p.x">>]}
|
||||
, {"modifier_checks",
|
||||
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
|
||||
<<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
|
||||
|
@ -39,7 +39,7 @@ simple_contracts_test_() ->
|
||||
RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end,
|
||||
NonAssoc = fun(Op) ->
|
||||
OpAtom = list_to_atom(Op),
|
||||
?assertError({error, {_, parse_error, _}},
|
||||
?assertThrow({parse_errors, [_]},
|
||||
parse_expr(NoPar({a, Op, {b, Op, c}}))) end,
|
||||
Stronger = fun(Op1, Op2) ->
|
||||
CheckParens({{a, Op1, b}, Op2, c}),
|
||||
@ -74,10 +74,7 @@ roundtrip_contract(Name) ->
|
||||
parse_string(Text) -> parse_string(Text, []).
|
||||
|
||||
parse_string(Text, Opts) ->
|
||||
case aeso_parser:string(Text, Opts) of
|
||||
{ok, Contract} -> Contract;
|
||||
Err -> error(Err)
|
||||
end.
|
||||
aeso_parser:string(Text, Opts).
|
||||
|
||||
parse_expr(Text) ->
|
||||
[{letval, _, _, _, Expr}] =
|
||||
|
Loading…
x
Reference in New Issue
Block a user