diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index d0e1181..393a522 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -76,8 +76,7 @@ file(File, Options0) -> {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); {error, Error} -> Msg = lists:flatten([File,": ",file:format_error(Error)]), - Pos = aeso_errors:pos(0, 0), - {error, [aeso_errors:new(file_error, Pos, Msg)]} + {error, [aeso_errors:new(file_error, Msg)]} end. add_include_path(File, Options) -> @@ -283,14 +282,14 @@ to_sophia_value(_, _, revert, Data, Options) -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}; {error, _} -> Msg = "Could not interpret the revert message\n", - {error, [aeso_errors:new(data_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end; fate -> try aeb_fate_encoding:deserialize(Data) of Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}} catch _:_ -> Msg = "Could not deserialize the revert message\n", - {error, [aeso_errors:new(data_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end end; to_sophia_value(ContractString, FunName, ok, Data, Options0) -> @@ -313,11 +312,11 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> Type0Str = prettypr:format(aeso_pretty:type(Type0)), Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", [Data, VmType, Type0Str]), - {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end; {error, _Err} -> Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]), - {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end; fate -> try @@ -326,11 +325,11 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> Type1 = prettypr:format(aeso_pretty:type(Type)), Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n", [aeb_fate_encoding:deserialize(Data), Type1]), - {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]}; + {error, [aeso_errors:new(data_error, Msg)]}; _:_ -> Type1 = prettypr:format(aeso_pretty:type(Type)), Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]), - {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end end catch @@ -395,11 +394,11 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> Type0Str = prettypr:format(aeso_pretty:type(Type0)), Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", [VmValue, VmType, Type0Str]), - {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end; {error, _Err} -> Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]), - {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end; fate -> case aeb_fate_abi:decode_calldata(FunName, Calldata) of @@ -411,13 +410,13 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> {ok, ArgTypes, AstArgs} catch throw:cannot_translate_to_sophia -> Type0Str = prettypr:format(aeso_pretty:type(Type0)), - Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n", + Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n", [FateArgs, Type0Str]), - {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end; {error, _} -> Msg = io_lib:format("Failed to decode calldata binary\n", []), - {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]} + {error, [aeso_errors:new(data_error, Msg)]} end end catch @@ -433,8 +432,7 @@ get_arg_icode(Funs) -> -dialyzer({nowarn_function, error_missing_call_function/0}). error_missing_call_function() -> Msg = "Internal error: missing '__call'-function", - Pos = aeso_errors:pos(0, 0), - aeso_errors:throw(aeso_errors:new(internal_error, Pos, Msg)). + aeso_errors:throw(aeso_errors:new(internal_error, Msg)). get_call_type([{contract, _, _, Defs}]) -> case [ {lists:last(QFunName), FunType} @@ -462,7 +460,7 @@ get_decode_type(FunName, [{contract, Ann, _, Defs}]) -> _ -> Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]), Pos = aeso_code_errors:pos(Ann), - aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)) + aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg)) end end; get_decode_type(FunName, [_ | Contracts]) -> diff --git a/src/aeso_errors.erl b/src/aeso_errors.erl index d19c5cf..c3f60ff 100644 --- a/src/aeso_errors.erl +++ b/src/aeso_errors.erl @@ -30,6 +30,7 @@ -export([ err_msg/1 , msg/1 + , new/2 , new/3 , new/4 , pos/2 @@ -40,6 +41,9 @@ , type/1 ]). +new(Type, Msg) -> + new(Type, pos(0, 0), Msg). + new(Type, Pos, Msg) -> #err{ type = Type, pos = Pos, message = Msg }. @@ -72,15 +76,23 @@ str_pos(#pos{file = F, line = L, col = C}) -> type(#err{ type = Type }) -> Type. -pp(#err{ pos = Pos } = Err) -> - lists:flatten(io_lib:format("~s~s", [pp_pos(Pos), msg(Err)])). +pp(#err{ type = Kind, pos = Pos } = Err) -> + lists:flatten(io_lib:format("~s~s:\n~s", [pp_kind(Kind), pp_pos(Pos), msg(Err)])). + +pp_kind(type_error) -> "Type error"; +pp_kind(parse_error) -> "Parse error"; +pp_kind(code_error) -> "Code generation error"; +pp_kind(file_error) -> "File error"; +pp_kind(data_error) -> "Data error"; +pp_kind(internal_error) -> "Internal error". pp_pos(#pos{file = no_file, line = 0, col = 0}) -> ""; pp_pos(#pos{file = no_file, line = L, col = C}) -> - io_lib:format("At line ~p, col ~p:\n", [L, 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:\n", [F, L, C]). + io_lib:format(" in '~s' at line ~p, col ~p", [F, L, C]). + to_json(#err{pos = Pos, type = Type, message = Msg, context = Cxt}) -> Json = #{ pos => pos_to_json(Pos), type => atom_to_binary(Type, utf8), diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 46f1268..ce04b33 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -77,21 +77,21 @@ to_sophia_value_neg_test() -> " entrypoint x(y : int) : string = \"hello\"\n" ], {error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)), - ?assertEqual("Failed to decode binary as type string\n", aeso_errors:pp(Err1)), + ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)), {error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]), - ?assertEqual("Failed to decode binary as type string\n", aeso_errors:pp(Err2)), + ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)), {error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)), - ?assertEqual("Could not interpret the revert message\n", aeso_errors:pp(Err3)), + ?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)), {error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]), - ?assertEqual("Could not deserialize the revert message\n", aeso_errors:pp(Err4)), + ?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)), ok. encode_calldata_neg_test() -> Code = [ "contract Foo =\n" " entrypoint x(y : int) : string = \"hello\"\n" ], - ExpErr1 = "At line 5, col 34:\nCannot unify int\n and bool\n" + ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n" "when checking the application at line 5, column 34 of\n" " x : (int) => string\nto arguments\n true : bool\n", {error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]), @@ -111,16 +111,16 @@ decode_calldata_neg_test() -> {ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]), {error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM), - ?assertEqual("Failed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)), + ?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)), {error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]), - ?assertEqual("Failed to decode calldata binary\n", aeso_errors:pp(Err2)), + ?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)), {error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]), - ?assertEqual("Cannot translate FATE value \"*\"\n of Sophia type (string)\n", aeso_errors:pp(Err3)), + ?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)), {error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM), - ?assertEqual("At line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)), + ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)), {error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]), - ?assertEqual("At line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)), + ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)), ok. @@ -215,7 +215,7 @@ permissive_literals_fail_test() -> " Chain.spend(o, 1000000)\n", {error, [Err]} = aeso_compiler:check_call(Contract, "haxx", ["#123"], []), - ?assertMatch("At line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)), + ?assertMatch("Type error at line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)), ?assertEqual(type_error, aeso_errors:type(Err)), ok. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 450ff36..3ba5fc9 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -35,7 +35,7 @@ simple_compile_test_() -> [ {"Test file not found error", fun() -> {error, Errors} = aeso_compiler:file("does_not_exist.aes"), - ExpErr = <<"does_not_exist.aes: no such file or directory">>, + ExpErr = <<"File error:\ndoes_not_exist.aes: no such file or directory">>, check_errors([ExpErr], Errors) end} ] ++ [ {"Testing error messages of " ++ ContractName, @@ -159,26 +159,31 @@ not_yet_compilable(aevm) -> []. %% Contracts that should produce type errors --define(Pos(File, Line, Col), "In '", (list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n"). --define(Pos(Line, Col), ?Pos(__File, Line, Col)). +-define(Pos(Kind, File, Line, Col), (list_to_binary(Kind))/binary, " error in '", + (list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n"). +-define(Pos(Line, Col), ?Pos(__Kind, __File, Line, Col)). --define(TEST(Name, Errs), +-define(ERROR(Kind, Name, Errs), (fun() -> + __Kind = Kind, __File = ??Name, {__File, Errs} end)()). +-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)). +-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)). + failing_contracts() -> %% Parse errors - [ ?TEST(field_parse_error, + [ ?PARSE_ERROR(field_parse_error, [<>]) - , ?TEST(vsemi, [<>]) - , ?TEST(vclose, [<>]) - , ?TEST(indent_fail, [<>]) + , ?PARSE_ERROR(vsemi, [<>]) + , ?PARSE_ERROR(vclose, [<>]) + , ?PARSE_ERROR(indent_fail, [<>]) %% Type errors - , ?TEST(name_clash, + , ?TYPE_ERROR(name_clash, [< "Duplicate definitions of state at\n" " - (builtin location)\n" " - line 17, column 3">>]) - , ?TEST(type_errors, + , ?TYPE_ERROR(type_errors, [<>, < "Let binding at line 54, column 5 must be followed by an expression">>, <>]) - , ?TEST(init_type_error, + , ?TYPE_ERROR(init_type_error, [<>]) - , ?TEST(missing_state_type, + , ?TYPE_ERROR(missing_state_type, [<>]) - , ?TEST(missing_fields_in_record_expression, + , ?TYPE_ERROR(missing_fields_in_record_expression, [<>, <>, <>]) - , ?TEST(namespace_clash, + , ?TYPE_ERROR(namespace_clash, [<>]) - , ?TEST(bad_events, + , ?TYPE_ERROR(bad_events, [<>, <>]) - , ?TEST(bad_events2, + , ?TYPE_ERROR(bad_events2, [<>, <>]) - , ?TEST(type_clash, + , ?TYPE_ERROR(type_clash, [< " r.foo : (gas : int, value : int) => Remote.themap\n" "against the expected type\n" " (gas : int, value : int) => map(string, int)">>]) - , ?TEST(bad_include_and_ns, + , ?TYPE_ERROR(bad_include_and_ns, [<>, <>]) - , ?TEST(bad_address_literals, + , ?TYPE_ERROR(bad_address_literals, [< " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" "against the expected type\n" " bytes(32)">>]) - , ?TEST(stateful, + , ?TYPE_ERROR(stateful, [<>, < "Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>, <>]) - , ?TEST(bad_init_state_access, + , ?TYPE_ERROR(bad_init_state_access, [< "The init function should return the initial state as its result and cannot read the state,\n" "but it calls\n" " - state (at line 13, column 13)">>]) - , ?TEST(modifier_checks, + , ?TYPE_ERROR(modifier_checks, [<>, < "Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>, < unit">>]) - , ?TEST(list_comp_not_a_list, + , ?TYPE_ERROR(list_comp_not_a_list, [<> ]) - , ?TEST(list_comp_if_not_bool, + , ?TYPE_ERROR(list_comp_if_not_bool, [<> ]) - , ?TEST(list_comp_bad_shadow, + , ?TYPE_ERROR(list_comp_bad_shadow, [<> ]) - , ?TEST(map_as_map_key, + , ?TYPE_ERROR(map_as_map_key, [< "Invalid key type\n" " lm\n" "Map keys cannot contain other maps.">>]) - , ?TEST(calling_init_function, + , ?TYPE_ERROR(calling_init_function, [<>]) - , ?TEST(bad_top_level_decl, + , ?TYPE_ERROR(bad_top_level_decl, [<>]) - , ?TEST(missing_event_type, + , ?TYPE_ERROR(missing_event_type, [<>]) ]. -define(Path(File), "code_errors/" ??File). --define(Msg(File, Line, Col, Err), <>). +-define(Msg(File, Line, Col, Err), <>). -define(SAME(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}). -define(AEVM(File, Line, Col, Err), {?Path(File), [{aevm, ?Msg(File, Line, Col, Err)}]}).