Merge pull request #140 from aeternity/PT-168026292-structured_error_messages

PT-168026292 structured error messages
This commit is contained in:
Ulf Norell 2019-09-05 09:03:19 +02:00 committed by GitHub
commit ecfa04ba17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1752 additions and 925 deletions

View File

@ -74,21 +74,9 @@ do_contract_interface(Type, ContractString, Options) ->
string -> do_render_aci_json(JArray) string -> do_render_aci_json(JArray)
end end
catch catch
%% The compiler errors. throw:{error, Errors} -> {error, 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. end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)}, C0 = #{name => encode_name(Name)},

View File

@ -12,7 +12,7 @@
-module(aeso_ast_infer_types). -module(aeso_ast_infer_types).
-export([infer/1, infer/2, infer_constant/1, unfold_types_in_type/3]). -export([infer/1, infer/2, unfold_types_in_type/3]).
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} -type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()}
| {app_t, aeso_syntax:ann(), utype(), [utype()]} | {app_t, aeso_syntax:ann(), utype(), [utype()]}
@ -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(),
@ -203,18 +205,18 @@ bind_state(Env) ->
{S, _} -> {qid, Ann, S}; {S, _} -> {qid, Ann, S};
false -> Unit false -> Unit
end, end,
Event =
case lookup_type(Env, {id, Ann, "event"}) of
{E, _} -> {qid, Ann, E};
false -> {id, Ann, "event"} %% will cause type error if used(?)
end,
Env1 = bind_funs([{"state", State}, Env1 = bind_funs([{"state", State},
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env), {"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
case lookup_type(Env, {id, Ann, "event"}) of
{E, _} ->
%% We bind Chain.event in a local 'Chain' namespace. %% We bind Chain.event in a local 'Chain' namespace.
Event = {qid, Ann, E},
pop_scope( pop_scope(
bind_fun("event", {fun_t, Ann, [], [Event], Unit}, bind_fun("event", {fun_t, Ann, [], [Event], Unit},
push_scope(namespace, {con, Ann, "Chain"}, Env1))). push_scope(namespace, {con, Ann, "Chain"}, Env1)));
false -> Env1
end.
-spec bind_field(name(), field_info(), env()) -> env(). -spec bind_field(name(), field_info(), env()) -> env().
bind_field(X, Info, Env = #env{ fields = Fields }) -> bind_field(X, Info, Env = #env{ fields = Fields }) ->
@ -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
@ -599,13 +602,6 @@ infer_contract_top(Env, Kind, Defs0, _Options) ->
Defs = desugar(Defs0), Defs = desugar(Defs0),
infer_contract(Env, Kind, Defs). infer_contract(Env, Kind, Defs).
%% TODO: revisit
infer_constant({letval, Attrs,_Pattern, Type, E}) ->
ets_init(), %% Init the ETS table state
{typed, _, _, PatType} =
infer_expr(global_env(), {typed, Attrs, E, arg_type(Type)}),
instantiate(PatType).
%% infer_contract takes a proplist mapping global names to types, and %% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions. %% a list of definitions.
-spec infer_contract(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec infer_contract(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
@ -710,7 +706,8 @@ check_modifiers(Env, Contracts) ->
{true, []} -> type_error({contract_has_no_entrypoints, Con}); {true, []} -> type_error({contract_has_no_entrypoints, Con});
_ -> ok _ -> ok
end; end;
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls) {namespace, _, _, Decls} -> check_modifiers1(namespace, Decls);
Decl -> type_error({bad_top_level_decl, Decl})
end || C <- Contracts ], end || C <- Contracts ],
destroy_and_report_type_errors(Env). destroy_and_report_type_errors(Env).
@ -721,12 +718,15 @@ check_modifiers1(What, Decls) when is_list(Decls) ->
check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) == fun_decl -> check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) == fun_decl ->
Public = aeso_syntax:get_ann(public, Decl, false), Public = aeso_syntax:get_ann(public, Decl, false),
Private = aeso_syntax:get_ann(private, Decl, false), Private = aeso_syntax:get_ann(private, Decl, false),
Payable = aeso_syntax:get_ann(payable, Decl, false),
Entrypoint = aeso_syntax:get_ann(entrypoint, Decl, false), Entrypoint = aeso_syntax:get_ann(entrypoint, Decl, false),
FunDecl = element(1, Decl) == fun_decl, FunDecl = element(1, Decl) == fun_decl,
{id, _, Name} = element(3, Decl), {id, _, Name} = element(3, Decl),
IsInit = Name == "init" andalso What == contract,
_ = [ type_error({proto_must_be_entrypoint, Decl}) || FunDecl, Private orelse not Entrypoint, What == contract ], _ = [ type_error({proto_must_be_entrypoint, Decl}) || FunDecl, Private orelse not Entrypoint, What == contract ],
_ = [ type_error({proto_in_namespace, Decl}) || FunDecl, What == namespace ], _ = [ type_error({proto_in_namespace, Decl}) || FunDecl, What == namespace ],
_ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, Name == "init", What == contract ], _ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, IsInit ],
_ = [ type_error({init_must_not_be_payable, Decl}) || Payable, IsInit ],
_ = [ type_error({public_modifier_in_contract, Decl}) || Public, not Private, not Entrypoint, What == contract ], _ = [ type_error({public_modifier_in_contract, Decl}) || Public, not Private, not Entrypoint, What == contract ],
_ = [ type_error({entrypoint_in_namespace, Decl}) || Entrypoint, What == namespace ], _ = [ type_error({entrypoint_in_namespace, Decl}) || Entrypoint, What == namespace ],
_ = [ type_error({private_entrypoint, Decl}) || Private, Entrypoint ], _ = [ type_error({private_entrypoint, Decl}) || Private, Entrypoint ],
@ -1709,7 +1709,7 @@ solve_known_record_types(Env, Constraints) ->
C C
end; end;
_ -> _ ->
type_error({not_a_record_type, RecId, When}), type_error({not_a_record_type, RecType, When}),
not_solved not_solved
end end
end end
@ -1824,6 +1824,10 @@ unfold_types(_Env, X, _Options) ->
unfold_types_in_type(Env, T) -> unfold_types_in_type(Env, T) ->
unfold_types_in_type(Env, T, []). unfold_types_in_type(Env, T, []).
unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]}, Options) ->
Args1 = [KeyType, _] = unfold_types_in_type(Env, Args, Options),
[ type_error({map_in_map_key, KeyType0}) || has_maps(KeyType) ],
{app_t, Ann, Id, Args1};
unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) -> unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) ->
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false), UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
@ -1871,6 +1875,13 @@ unfold_types_in_type(Env, [H|T], Options) ->
unfold_types_in_type(_Env, X, _Options) -> unfold_types_in_type(_Env, X, _Options) ->
X. X.
has_maps({app_t, _, {id, _, "map"}, _}) ->
true;
has_maps(L) when is_list(L) ->
lists:any(fun has_maps/1, L);
has_maps(T) when is_tuple(T) ->
has_maps(tuple_to_list(T));
has_maps(_) -> false.
subst_tvars(Env, Type) -> subst_tvars(Env, Type) ->
subst_tvars1([{V, T} || {{tvar, _, V}, T} <- Env], Type). subst_tvars1([{V, T} || {{tvar, _, V}, T} <- Env], Type).
@ -2066,12 +2077,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]}). aeso_errors:throw(Errors). %% No-op if 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}) ->
@ -2090,153 +2100,225 @@ 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)]),
case Id of
{qid, _, ["Chain", "event"]} ->
Cxt = "Did you forget to define the event type?",
mk_t_err(pos(Id), Msg, Cxt);
_ -> mk_t_err(pos(Id), Msg)
end;
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(lists:last(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({init_must_not_be_payable, Decl}) ->
Msg = io_lib:format("The init function (at ~s) cannot be payable.\n"
"You don't need the 'payable' annotation to be able to attach\n"
"funds to the create contract transaction.",
[pp_loc(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({map_in_map_key, KeyType}) ->
Msg = io_lib:format("Invalid key type\n~s\n", [pp_type(" ", KeyType)]),
Cxt = "Map keys cannot contain other maps.\n",
mk_t_err(pos(KeyType), Msg, Cxt);
mk_error({cannot_call_init_function, Ann}) ->
Msg = "The 'init' function is called exclusively by the create contract transaction\n"
"and cannot be called from the contract code.\n",
mk_t_err(pos(Ann), Msg);
mk_error({bad_top_level_decl, Decl}) ->
What = case element(1, Decl) of
letval -> "function or entrypoint";
_ -> "contract or namespace"
end,
Id = element(3, Decl),
Msg = io_lib:format("The definition of '~s' must appear inside a ~s.\n",
[pp_expr("", Id), What]),
mk_t_err(pos(Decl), 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,
@ -2244,26 +2326,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",
@ -2281,73 +2366,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}) ->
@ -2369,9 +2455,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(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)}.
@ -2382,6 +2472,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

@ -67,6 +67,7 @@
| {def_u, fun_name(), arity()} | {def_u, fun_name(), arity()}
| {remote_u, [ftype()], ftype(), fexpr(), fun_name()} | {remote_u, [ftype()], ftype(), fexpr(), fun_name()}
| {builtin_u, builtin(), arity()} | {builtin_u, builtin(), arity()}
| {builtin_u, builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args.
| {lam, [var_name()], fexpr()}. | {lam, [var_name()], fexpr()}.
-type fsplit() :: {split, ftype(), var_name(), [fcase()]} -type fsplit() :: {split, ftype(), var_name(), [fcase()]}
@ -140,6 +141,7 @@
functions := #{ fun_name() => fun_def() } }. functions := #{ fun_name() => fun_def() } }.
-define(HASH_BYTES, 32). -define(HASH_BYTES, 32).
%% -- Entrypoint ------------------------------------------------------------- %% -- Entrypoint -------------------------------------------------------------
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2 %% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
@ -232,7 +234,7 @@ is_no_code(Env) ->
%% -- Compilation ------------------------------------------------------------ %% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) -> to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
#{ builtins := Builtins } = Env, #{ builtins := Builtins } = Env,
MainEnv = Env#{ context => {main_contract, Main}, MainEnv = Env#{ context => {main_contract, Main},
builtins => Builtins#{[Main, "state"] => {get_state, none}, builtins => Builtins#{[Main, "state"] => {get_state, none},
@ -247,8 +249,10 @@ to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) ->
state_type => StateType, state_type => StateType,
event_type => EventType, event_type => EventType,
payable => Payable, payable => Payable,
functions => add_init_function(Env1, StateType, functions => add_init_function(Env1, MainCon, StateType,
add_event_function(Env1, EventType, Funs)) }; add_event_function(Env1, EventType, Funs)) };
to_fcode(_Env, [NotContract]) ->
fcode_error({last_declaration_must_be_contract, NotContract});
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
to_fcode(Env1, Code); to_fcode(Env1, Code);
@ -270,21 +274,21 @@ decls_to_fcode(Env, Decls) ->
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_decl, _, _, _}) -> Env;
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, Ann, {id, _, Name}, _}) -> decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of case is_no_code(Env) of
false -> fcode_error({missing_definition, Name, lists:keydelete(entrypoint, 1, Ann)}); false -> fcode_error({missing_definition, Id});
true -> Env true -> Env
end; end;
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env; decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) -> decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def); typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, Body}) -> decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) ->
Attrs = get_attributes(Ann), Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)), FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args), FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret), FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body), FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, FArgs, FRet) [ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ], || aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs, Def = #{ attrs => Attrs,
args => FArgs, args => FArgs,
@ -294,9 +298,10 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R
Env#{ functions := NewFuns }. Env#{ functions := NewFuns }.
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env(). -spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
check_state_and_event_types(Env, Id, Xs),
Q = qname(Env, Name), Q = qname(Env, Name),
FDef = fun(Args) -> FDef = fun(Args) when length(Args) == length(Xs) ->
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)), Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
case Def of case Def of
{record_t, Fields} -> {todo, Xs, Args, record_t, Fields}; {record_t, Fields} -> {todo, Xs, Args, record_t, Fields};
@ -307,7 +312,9 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
end || Con <- Cons ], end || Con <- Cons ],
{variant, FCons}; {variant, FCons};
{alias_t, Type} -> {todo, Xs, Args, alias_t, Type} {alias_t, Type} -> {todo, Xs, Args, alias_t, Type}
end end, end;
(Args) -> internal_error({type_arity_mismatch, Name, length(Args), length(Xs)})
end,
Constructors = Constructors =
case Def of case Def of
{variant_t, Cons} -> {variant_t, Cons} ->
@ -328,6 +335,14 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
end, end,
bind_type(Env2, Q, FDef). bind_type(Env2, Q, FDef).
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) ->
case Id of
{id, _, "state"} -> fcode_error({parameterized_state, Id});
{id, _, "event"} -> fcode_error({parameterized_event, Id});
_ -> ok
end;
check_state_and_event_types(_, _, _) -> ok.
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype(). -spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
type_to_fcode(Env, Type) -> type_to_fcode(Env, Type) ->
type_to_fcode(Env, #{}, Type). type_to_fcode(Env, #{}, Type).
@ -392,7 +407,28 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
%% Variables %% Variables
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]); expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
expr_to_fcode(Env, _Type, {qid, _, X}) -> resolve_var(Env, X); expr_to_fcode(Env, Type, {qid, Ann, X}) ->
case resolve_var(Env, X) of
{builtin_u, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
B =:= oracle_get_answer;
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
OType = get_oracle_type(B, Type),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(Ann, OType, QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = aens_resolve, Ar} ->
{fun_t, _, _, _, ResType} = Type,
AensType = type_to_fcode(Env, ResType),
validate_aens_resolve_type(Ann, ResType, AensType),
TypeArgs = [{lit, {typerep, AensType}}],
{builtin_u, B, Ar, TypeArgs};
Other -> Other
end;
%% Constructors %% Constructors
expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon -> expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon ->
@ -402,7 +438,7 @@ expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Args}) when C
Arity = lists:nth(I + 1, Arities), Arity = lists:nth(I + 1, Arities),
case length(Args) == Arity of case length(Args) == Arity of
true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]};
false -> fcode_error({constructor_arity_mismatch, Con, length(Args), Arity}) false -> internal_error({constructor_arity_mismatch, Con, length(Args), Arity})
end; end;
%% Tuples %% Tuples
@ -513,29 +549,11 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end; end;
%% Function calls %% Function calls
expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args), Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of case expr_to_fcode(Env, Fun) of
{builtin_u, B, _} when B =:= oracle_query; {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(B, FArgs ++ TypeArgs);
B =:= oracle_get_question;
B =:= oracle_get_answer;
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
%% Get the type of the oracle from the args or the expression itself
OType = get_oracle_type(B, Type, Args1),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(aeso_syntax:get_ann(Fun), QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _} when B =:= aens_resolve ->
%% Get the type we are assuming the name resolves to
AensType = type_to_fcode(Env, Type),
validate_aens_resolve_type(aeso_syntax:get_ann(Fun), AensType),
TypeArgs = [{lit, {typerep, AensType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs); {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs);
{def_u, F, _Ar} -> {def, F, FArgs}; {def_u, F, _Ar} -> {def, F, FArgs};
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
@ -601,30 +619,37 @@ make_if(Cond, Then, Else) ->
{'let', X, Cond, make_if({var, X}, Then, Else)}. {'let', X, Cond, make_if({var, X}, Then, Else)}.
get_oracle_type(oracle_register, OType, _Args) -> OType; get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType;
get_oracle_type(oracle_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_question, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_answer, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_get_answer, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) -> OType; get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType. get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
validate_oracle_type(Ann, QType, RType) -> validate_oracle_type(Ann, Type, QType, RType) ->
ensure_monomorphic(QType, {polymorphic_query_type, Ann, QType}), ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {polymorphic_response_type, Ann, RType}), ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {higher_order_query_type, Ann, QType}), ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {higher_order_response_type, Ann, RType}), ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
ok. ok.
validate_aens_resolve_type(Ann, {variant, [[], [Type]]}) -> validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
ensure_monomorphic(Type, {polymorphic_aens_resolve, Ann, Type}), case FType of
ensure_first_order(Type, {higher_order_aens_resolve, Ann, Type}), string -> ok;
ok. address -> ok;
contract -> ok;
{oracle, _, _} -> ok;
oracle_query -> ok;
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
end.
ensure_first_order_entrypoint(Ann, Args, Ret) -> ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
[ ensure_first_order(T, {higher_order_entrypoint_argument, Ann, X, T}) [ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|| {X, T} <- Args ], || {{arg, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
ensure_first_order(Ret, {higher_order_entrypoint_return, Ann, Ret}), [ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok. ok.
ensure_monomorphic(Type, Err) -> ensure_monomorphic(Type, Err) ->
@ -904,18 +929,18 @@ builtin_to_fcode(Builtin, Args) ->
%% -- Init function -- %% -- Init function --
add_init_function(Env, StateType, Funs0) -> add_init_function(Env, Main, StateType, Funs0) ->
case is_no_code(Env) of case is_no_code(Env) of
true -> Funs0; true -> Funs0;
false -> false ->
Funs = add_default_init_function(Env, StateType, Funs0), Funs = add_default_init_function(Env, Main, StateType, Funs0),
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
InitFun = #{ body := InitBody} = maps:get(InitName, Funs), InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Funs#{ InitName => InitFun#{ return => {tuple, []}, Funs#{ InitName => InitFun#{ return => {tuple, []},
body => {builtin, set_state, [InitBody]} } } body => {builtin, set_state, [InitBody]} } }
end. end.
add_default_init_function(_Env, StateType, Funs) -> add_default_init_function(_Env, Main, StateType, Funs) ->
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
case maps:get(InitName, Funs, none) of case maps:get(InitName, Funs, none) of
%% Only add default init function if state is unit. %% Only add default init function if state is unit.
@ -924,7 +949,7 @@ add_default_init_function(_Env, StateType, Funs) ->
args => [], args => [],
return => {tuple, []}, return => {tuple, []},
body => {tuple, []}} }; body => {tuple, []}} };
none -> fcode_error(missing_init_function); none -> fcode_error({missing_init_function, Main});
_ -> Funs _ -> Funs
end. end.
@ -1006,9 +1031,14 @@ make_closure(FVs, Xs, Body) ->
lambda_lift_expr({lam, Xs, Body}) -> lambda_lift_expr({lam, Xs, Body}) ->
FVs = free_vars({lam, Xs, Body}), FVs = free_vars({lam, Xs, Body}),
make_closure(FVs, Xs, lambda_lift_expr(Body)); make_closure(FVs, Xs, lambda_lift_expr(Body));
lambda_lift_expr({Tag, F, Ar}) when Tag == def_u; Tag == builtin_u -> lambda_lift_expr(UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u ->
[Tag, F, Ar | _] = tuple_to_list(UExpr),
ExtraArgs = case UExpr of
{builtin_u, _, _, TypeArgs} -> TypeArgs;
_ -> []
end,
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ],
Args = [{var, X} || X <- Xs], Args = [{var, X} || X <- Xs] ++ ExtraArgs,
Body = case Tag of Body = case Tag of
builtin_u -> builtin_to_fcode(F, Args); builtin_u -> builtin_to_fcode(F, Args);
def_u -> {def, F, Args} def_u -> {def, F, Args}
@ -1115,7 +1145,7 @@ lookup_type(Env, {qid, _, Name}, Args) ->
lookup_type(Env, Name, Args); lookup_type(Env, Name, Args);
lookup_type(Env, Name, Args) -> lookup_type(Env, Name, Args) ->
case lookup_type(Env, Name, Args, not_found) of case lookup_type(Env, Name, Args, not_found) of
not_found -> error({unknown_type, Name}); not_found -> internal_error({unknown_type, Name});
Type -> Type Type -> Type
end. end.
@ -1200,7 +1230,7 @@ resolve_var(Env, Q) -> resolve_fun(Env, Q).
resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) -> resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) ->
case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of
{not_found, not_found} -> fcode_error({unbound_variable, Q}); {not_found, not_found} -> internal_error({unbound_variable, Q});
{_, {B, none}} -> {builtin, B, []}; {_, {B, none}} -> {builtin, B, []};
{_, {B, Ar}} -> {builtin_u, B, Ar}; {_, {B, Ar}} -> {builtin_u, B, Ar};
{{Fun, Ar}, _} -> {def_u, Fun, Ar} {{Fun, Ar}, _} -> {def_u, Fun, Ar}
@ -1258,6 +1288,7 @@ free_vars(Expr) ->
{remote_u, _, _, Ct, _} -> free_vars(Ct); {remote_u, _, _, Ct, _} -> free_vars(Ct);
{builtin, _, As} -> free_vars(As); {builtin, _, As} -> free_vars(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{builtin_u, _, _, _} -> []; %% Typereps are always literals
{con, _, _, As} -> free_vars(As); {con, _, _, As} -> free_vars(As);
{tuple, As} -> free_vars(As); {tuple, As} -> free_vars(As);
{proj, A, _} -> free_vars(A); {proj, A, _} -> free_vars(A);
@ -1286,6 +1317,7 @@ used_defs(Expr) ->
{remote_u, _, _, Ct, _} -> used_defs(Ct); {remote_u, _, _, Ct, _} -> used_defs(Ct);
{builtin, _, As} -> used_defs(As); {builtin, _, As} -> used_defs(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{builtin_u, _, _, _} -> [];
{con, _, _, As} -> used_defs(As); {con, _, _, As} -> used_defs(As);
{tuple, As} -> used_defs(As); {tuple, As} -> used_defs(As);
{proj, A, _} -> used_defs(A); {proj, A, _} -> used_defs(A);
@ -1326,6 +1358,7 @@ rename(Ren, Expr) ->
{def_u, _, _} -> Expr; {def_u, _, _} -> Expr;
{builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]};
{builtin_u, _, _} -> Expr; {builtin_u, _, _} -> Expr;
{builtin_u, _, _, _} -> Expr;
{remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]};
{remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F}; {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F};
{con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]};
@ -1440,8 +1473,14 @@ get_attributes(Ann) ->
indexed(Xs) -> indexed(Xs) ->
lists:zip(lists:seq(1, length(Xs)), Xs). lists:zip(lists:seq(1, length(Xs)), Xs).
fcode_error(Err) -> -dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
error(Err).
fcode_error(Error) ->
aeso_errors:throw(aeso_code_errors:format(Error)).
internal_error(Error) ->
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
aeso_errors:throw(aeso_errors:new(internal_error, aeso_errors:pos(0, 0), Msg)).
%% -- Pretty printing -------------------------------------------------------- %% -- Pretty printing --------------------------------------------------------
@ -1466,7 +1505,8 @@ pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")).
pp_text(<<>>) -> prettypr:text("\"\""); pp_text(<<>>) -> prettypr:text("\"\"");
pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)]))); pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)])));
pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S])); pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S]));
pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)). pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A));
pp_text(N) when is_integer(N) -> prettypr:text(integer_to_list(N)).
pp_int(I) -> prettypr:text(integer_to_list(I)). pp_int(I) -> prettypr:text(integer_to_list(I)).
@ -1540,6 +1580,8 @@ pp_fexpr({'let', X, A, B}) ->
pp_fexpr(B)]); pp_fexpr(B)]);
pp_fexpr({builtin_u, B, N}) -> pp_fexpr({builtin_u, B, N}) ->
pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); pp_beside([pp_text(B), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin_u, B, N, TypeArgs}) ->
pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, TypeArgs}), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin, B, As}) -> pp_fexpr({builtin, B, As}) ->
pp_call(pp_text(B), As); pp_call(pp_text(B), As);
pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) -> pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) ->

View File

@ -21,8 +21,8 @@ convert_typed(TypedTree, Options) ->
case lists:last(TypedTree) of case lists:last(TypedTree) of
{contract, Attrs, {con, _, Con}, _} -> {contract, Attrs, {con, _, Con}, _} ->
{proplists:get_value(payable, Attrs, false), Con}; {proplists:get_value(payable, Attrs, false), Con};
_ -> Decl ->
gen_error(last_declaration_must_be_contract) gen_error({last_declaration_must_be_contract, Decl})
end, end,
NewIcode = aeso_icode:set_payable(Payable, NewIcode = aeso_icode:set_payable(Payable,
aeso_icode:set_name(Name, aeso_icode:new(Options))), aeso_icode:set_name(Name, aeso_icode:new(Options))),
@ -41,18 +41,19 @@ code([], Icode, Options) ->
%% Generate error on correct format. %% Generate error on correct format.
-dialyzer({nowarn_function, gen_error/1}).
gen_error(Error) -> gen_error(Error) ->
error({code_errors, [Error]}). aeso_errors:throw(aeso_code_errors:format(Error)).
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}, Options) -> add_default_init_function(Icode = #{namespace := NS, functions := Funs, state_type := State}, Options) ->
NoCode = proplists:get_value(no_code, Options, false), NoCode = proplists:get_value(no_code, Options, false),
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode), {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when NoCode -> Icode; false when NoCode -> Icode;
false when State /= {tuple, []} -> false when State /= {tuple, []} ->
gen_error(missing_init_function); gen_error({missing_init_function, NS});
false -> false ->
Type = {tuple, [typerep, {tuple, []}]}, Type = {tuple, [typerep, {tuple, []}]},
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] }, Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
@ -66,7 +67,7 @@ contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
NS = aeso_icode:get_namespace(Icode), NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)), Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1)); contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], contract_to_icode([Decl = {type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) -> Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode), TypeDef = make_type_def(Args, Def, Icode),
NewConstructors = NewConstructors =
@ -82,10 +83,14 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode1 = Icode#{ types := Types#{ TName => TypeDef }, Icode1 = Icode#{ types := Types#{ TName => TypeDef },
constructors := maps:merge(Constructors, NewConstructors) }, constructors := maps:merge(Constructors, NewConstructors) },
Icode2 = case Name of Icode2 = case Name of
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) }; "state" when Args == [] ->
"state" -> gen_error(state_type_cannot_be_parameterized); case is_first_order_type(Def) of
true -> Icode1#{ state_type => ast_typerep(Def, Icode) };
false -> gen_error({higher_order_state, Decl})
end;
"state" -> gen_error({parameterized_state, Id});
"event" when Args == [] -> Icode1#{ event_type => Def }; "event" when Args == [] -> Icode1#{ event_type => Def };
"event" -> gen_error(event_type_cannot_be_parameterized); "event" -> gen_error({parameterized_event, Id});
_ -> Icode1 _ -> Icode1
end, end,
contract_to_icode(Rest, Icode2); contract_to_icode(Rest, Icode2);
@ -113,8 +118,12 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode), NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode); contract_to_icode(Rest, NewIcode);
contract_to_icode([], Icode) -> Icode; contract_to_icode([], Icode) -> Icode;
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) -> contract_to_icode([{fun_decl, _, Id, _} | Code], Icode = #{ options := Options }) ->
contract_to_icode(Code, Icode); NoCode = proplists:get_value(no_code, Options, false),
case aeso_icode:in_main_contract(Icode) andalso not NoCode of
true -> gen_error({missing_definition, Id});
false -> contract_to_icode(Code, Icode)
end;
contract_to_icode([Decl | Code], Icode) -> contract_to_icode([Decl | Code], Icode) ->
io:format("Unhandled declaration: ~p\n", [Decl]), io:format("Unhandled declaration: ~p\n", [Decl]),
contract_to_icode(Code, Icode). contract_to_icode(Code, Icode).
@ -140,20 +149,7 @@ ast_type(T, Icode) ->
-define(option_t(A), {app_t, _, {id, _, "option"}, [A]}). -define(option_t(A), {app_t, _, {id, _, "option"}, [A]}).
-define(map_t(K, V), {app_t, _, {id, _, "map"}, [K, V]}). -define(map_t(K, V), {app_t, _, {id, _, "map"}, [K, V]}).
ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) ->
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_name := Con }) ->
aeso_builtins:check_event_type(Icode),
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
%% Chain environment %% Chain environment
ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) ->
#prim_balance{ address = ast_body(Address, Icode) };
ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]);
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
prim_gas_left;
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address; ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator; ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address }; ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
@ -166,133 +162,19 @@ ast_body({qid, _, ["Chain", "timestamp"]}, _Icode) -> prim_timestamp;
ast_body({qid, _, ["Chain", "block_height"]}, _Icode) -> prim_block_height; ast_body({qid, _, ["Chain", "block_height"]}, _Icode) -> prim_block_height;
ast_body({qid, _, ["Chain", "difficulty"]}, _Icode) -> prim_difficulty; ast_body({qid, _, ["Chain", "difficulty"]}, _Icode) -> prim_difficulty;
ast_body({qid, _, ["Chain", "gas_limit"]}, _Icode) -> prim_gas_limit; ast_body({qid, _, ["Chain", "gas_limit"]}, _Icode) -> prim_gas_limit;
%% TODO: eta expand!
ast_body({qid, _, ["Chain", "balance"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.balance'});
ast_body({qid, _, ["Chain", "block_hash"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.block_hash'});
ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.spend'});
%% State %% State
ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state; ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state;
ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) -> ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) ->
#prim_put{ state = ast_body(NewState, Icode) }; #prim_put{ state = ast_body(NewState, Icode) };
ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) -> ast_body({typed, _, Id = {qid, _, [Con, "put"]}, Type}, Icode = #{ contract_name := Con }) ->
gen_error({underapplied_primitive, put}); %% TODO: eta eta_expand(Id, Type, Icode);
%% Abort
ast_body(?id_app("abort", [String], _, _), Icode) ->
builtin_call(abort, [ast_body(String, Icode)]);
ast_body(?id_app("require", [Bool, String], _, _), Icode) ->
builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]);
%% Authentication %% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) -> ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0}, prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0},
[], [], aeso_icode:option_typerep(word)); [], [], aeso_icode:option_typerep(word));
%% Oracles
ast_body(?qid_app(["Oracle", "register"], Args, _, ?oracle_t(QType, RType)), Icode) ->
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0},
[ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode),
ast_type_value(QType, Icode), ast_type_value(RType, Icode)],
[word, sign_t(), word, ttl_t(Icode), typerep, typerep], word);
ast_body(?qid_app(["Oracle", "query_fee"], [Oracle], _, _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0},
[ast_body(Oracle, Icode)], [word], word);
ast_body(?qid_app(["Oracle", "query"], [Oracle, Q, QFee, QTTL, RTTL], [_, QType, _, _, _], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode),
[ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)],
[word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word);
ast_body(?qid_app(["Oracle", "extend"], Args, _, _), Icode) ->
{Sign, [Oracle, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)],
[word, sign_t(), ttl_t(Icode)], {tuple, []});
ast_body(?qid_app(["Oracle", "respond"], Args, [_, _, RType], _), Icode) ->
{Sign, [Oracle, Query, R]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)],
[word, word, sign_t(), ast_type(RType, Icode)], {tuple, []});
ast_body(?qid_app(["Oracle", "get_question"], [Oracle, Q], [_, ?query_t(QType, _)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode));
ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
ast_body({qid, _, ["Oracle", "respond"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.respond'});
ast_body({qid, _, ["Oracle", "query_fee"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query_fee'});
ast_body({qid, _, ["Oracle", "get_answer"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.get_answer'});
ast_body({qid, _, ["Oracle", "get_question"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.get_question'});
%% Name service
ast_body(?qid_app(["AENS", "resolve"], [Name, Key], _, ?option_t(Type)), Icode) ->
case is_monomorphic(Type) of
true ->
case ast_type(Type, Icode) of
T when T == word; T == string -> ok;
_ -> gen_error({invalid_result_type, 'AENS.resolve', Type})
end,
prim_call(?PRIM_CALL_AENS_RESOLVE, #integer{value = 0},
[ast_body(Name, Icode), ast_body(Key, Icode), ast_type_value(Type, Icode)],
[string, string, typerep], aeso_icode:option_typerep(ast_type(Type, Icode)));
false ->
gen_error({unresolved_result_type, 'AENS.resolve', Type})
end;
ast_body(?qid_app(["AENS", "preclaim"], Args, _, _), Icode) ->
{Sign, [Addr, CHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_PRECLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(CHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)],
[word, string, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'});
ast_body({qid, _, ["AENS", "preclaim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.preclaim'});
ast_body({qid, _, ["AENS", "claim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.claim'});
ast_body({qid, _, ["AENS", "transfer"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.transfer'});
ast_body({qid, _, ["AENS", "revoke"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.revoke'});
%% Maps %% Maps
%% -- map lookup m[k] %% -- map lookup m[k]
@ -306,35 +188,6 @@ ast_body({map_get, _, Map, Key, Val}, Icode) ->
Fun = {map_lookup_default, ast_typerep(ValType, Icode)}, Fun = {map_lookup_default, ast_typerep(ValType, Icode)},
builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]); builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]);
%% -- lookup functions
ast_body(?qid_app(["Map", "lookup"], [Key, Map], _, _), Icode) ->
map_get(Key, Map, Icode);
ast_body(?qid_app(["Map", "lookup_default"], [Key, Map, Val], _, _), Icode) ->
{_, ValType} = check_monomorphic_map(Map, Icode),
Fun = {map_lookup_default, ast_typerep(ValType, Icode)},
builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]);
ast_body(?qid_app(["Map", "member"], [Key, Map], _, _), Icode) ->
builtin_call(map_member, [ast_body(Map, Icode), ast_body(Key, Icode)]);
ast_body(?qid_app(["Map", "size"], [Map], _, _), Icode) ->
builtin_call(map_size, [ast_body(Map, Icode)]);
ast_body(?qid_app(["Map", "delete"], [Key, Map], _, _), Icode) ->
map_del(Key, Map, Icode);
%% -- map conversion to/from list
ast_body(App = ?qid_app(["Map", "from_list"], [List], _, MapType), Icode) ->
Ann = aeso_syntax:get_ann(App),
{KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode),
builtin_call(map_from_list, [ast_body(List, Icode), map_empty(KeyType, ValType, Icode)]);
ast_body(?qid_app(["Map", "to_list"], [Map], _, _), Icode) ->
map_tolist(Map, Icode);
ast_body({qid, _, ["Map", "from_list"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.from_list'});
%% ast_body({qid, _, ["Map", "to_list"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.to_list'});
ast_body({qid, _, ["Map", "lookup"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.lookup'});
ast_body({qid, _, ["Map", "lookup_default"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.lookup_default'});
ast_body({qid, _, ["Map", "member"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.member'});
%% -- map construction { k1 = v1, k2 = v2 } %% -- map construction { k1 = v1, k2 = v2 }
ast_body({typed, Ann, {map, _, KVs}, MapType}, Icode) -> ast_body({typed, Ann, {map, _, KVs}, MapType}, Icode) ->
{KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), {KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode),
@ -356,104 +209,22 @@ ast_body({map, _, Map, [Upd]}, Icode) ->
ast_body({map, Ann, Map, [Upd | Upds]}, Icode) -> ast_body({map, Ann, Map, [Upd | Upds]}, Icode) ->
ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode); ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode);
%% Crypto
ast_body(?qid_app(["Crypto", "verify_sig"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "verify_sig_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, Addr, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)],
[word, bytes_t(20), bytes_t(65)], word);
ast_body(?qid_app(["Crypto", "ecrecover_secp256k1"], [Msg, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Sig, Icode)],
[word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20)));
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "blake2b"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode);
ast_body(?qid_app(["String", "sha256"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode);
ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode);
%% Strings
%% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]);
%% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]);
%% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
#unop{ op = 'sha3', rand = ast_body(String, Icode) };
%% -- Bits %% -- Bits
ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
ast_body({qid, _, ["Bits", "none"]}, _Icode) -> ast_body({qid, _, ["Bits", "none"]}, _Icode) ->
#integer{ value = 0 }; #integer{ value = 0 };
ast_body({qid, _, ["Bits", "all"]}, _Icode) -> ast_body({qid, _, ["Bits", "all"]}, _Icode) ->
#integer{ value = 1 bsl 256 - 1 }; #integer{ value = 1 bsl 256 - 1 };
ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
%% -- Conversion %% -- Conversion
ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]);
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_payable"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({typed, _, Id = {qid, _, _}, Type}, Icode) ->
case is_builtin_fun(Id, Icode) of
true -> eta_expand(Id, Type, Icode);
false -> ast_body(Id, Icode)
end;
ast_body({qid, _, Name}, _Icode) -> ast_body({qid, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
@ -482,16 +253,12 @@ ast_body({list,_,Args}, Icode) ->
%% Typed contract calls %% Typed contract calls
ast_body({proj, _, {typed, _, Addr, {con, _, _}}, {id, _, "address"}}, Icode) -> ast_body({proj, _, {typed, _, Addr, {con, _, _}}, {id, _, "address"}}, Icode) ->
ast_body(Addr, Icode); %% Values of contract types _are_ addresses. ast_body(Addr, Icode); %% Values of contract types _are_ addresses.
ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id, _, FunName}}, ast_body({app, _, {typed, _, {proj, _, Addr, {id, _, FunName}},
{fun_t, _, NamedT, ArgsT, OutT}}, Args0}, Icode) -> {fun_t, _, NamedT, ArgsT, OutT}}, Args0}, Icode) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0], NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
Args = Args0 -- NamedArgs, Args = Args0 -- NamedArgs,
ArgOpts = [ {Name, ast_body(Value, Icode)} || {named_arg, _, {id, _, Name}, Value} <- NamedArgs ], ArgOpts = [ {Name, ast_body(Value, Icode)} || {named_arg, _, {id, _, Name}, Value} <- NamedArgs ],
Defaults = [ {Name, ast_body(Default, Icode)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedT ], Defaults = [ {Name, ast_body(Default, Icode)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedT ],
%% TODO: eta expand
length(Args) /= length(ArgsT) andalso
gen_error({underapplied_contract_call,
string:join([Contract, FunName], ".")}),
ArgsI = [ ast_body(Arg, Icode) || Arg <- Args ], ArgsI = [ ast_body(Arg, Icode) || Arg <- Args ],
ArgType = ast_typerep({tuple_t, [], ArgsT}), ArgType = ast_typerep({tuple_t, [], ArgsT}),
Gas = proplists:get_value("gas", ArgOpts ++ Defaults), Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
@ -509,9 +276,8 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
%% entrypoint on the callee side. %% entrypoint on the callee side.
type_hash= #integer{value = 0} type_hash= #integer{value = 0}
}; };
ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode) -> ast_body({proj, _, Con = {typed, _, _, {con, _, _}}, _Fun}, _Icode) ->
gen_error({underapplied_contract_call, gen_error({unapplied_contract_call, Con});
string:join([Contract, FunName], ".")});
ast_body({con, _, Name}, Icode) -> ast_body({con, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode), Tag = aeso_icode:get_constructor_tag([Name], Icode),
@ -529,7 +295,7 @@ ast_body({app, _, {'..', _}, [A, B]}, Icode) ->
#funcall #funcall
{ function = #var_ref{ name = ["ListInternal", "from_to"] } { function = #var_ref{ name = ["ListInternal", "from_to"] }
, args = [ast_body(A, Icode), ast_body(B, Icode)] }; , args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_body({app,As,Fun,Args}, Icode) -> ast_body({app, As, Fun, Args}, Icode) ->
case aeso_syntax:get_ann(format, As) of case aeso_syntax:get_ann(format, As) of
infix -> infix ->
{Op, _} = Fun, {Op, _} = Fun,
@ -540,8 +306,13 @@ ast_body({app,As,Fun,Args}, Icode) ->
[A] = Args, [A] = Args,
#unop{op = Op, rand = ast_body(A, Icode)}; #unop{op = Op, rand = ast_body(A, Icode)};
_ -> _ ->
{typed, _, Fun1, {fun_t, _, _, ArgsT, RetT}} = Fun,
case is_builtin_fun(Fun1, Icode) of
true -> builtin_code(As, Fun1, Args, ArgsT, RetT, Icode);
false ->
#funcall{function=ast_body(Fun, Icode), #funcall{function=ast_body(Fun, Icode),
args=[ast_body(A, Icode) || A <- Args]} args=[ast_body(A, Icode) || A <- Args]}
end
end; end;
ast_body({list_comp, _, Yield, []}, Icode) -> ast_body({list_comp, _, Yield, []}, Icode) ->
#list{elems = [ast_body(Yield, Icode)]}; #list{elems = [ast_body(Yield, Icode)]};
@ -571,9 +342,12 @@ ast_body({switch,_,A,Cases}, Icode) ->
#switch{expr=ast_body(A, Icode), #switch{expr=ast_body(A, Icode),
cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)} cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)}
|| {'case',_,Pat,Body} <- Cases]}; || {'case',_,Pat,Body} <- Cases]};
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) -> ast_body({block, As, [{letval, _, Pat, _, E} | Rest]}, Icode) ->
#switch{expr=ast_body(E, Icode), E1 = ast_body(E, Icode),
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]}; Pat1 = ast_body(Pat, Icode),
Rest1 = ast_body({block, As, Rest}, Icode),
#switch{expr = E1,
cases = [{Pat1, Rest1}]};
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) -> ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode); ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
ast_body({block,_,[]}, _Icode) -> ast_body({block,_,[]}, _Icode) ->
@ -600,8 +374,6 @@ ast_body({typed,_,{record,Attrs,Fields},{record_t,DefFields}}, Icode) ->
ast_body(E, Icode) ast_body(E, Icode)
end end
|| {field_t,_,{id,_,Name},_} <- DefFields]}; || {field_t,_,{id,_,Name},_} <- DefFields]};
ast_body({typed,_,{record,Attrs,_Fields},T}, _Icode) ->
gen_error({record_has_bad_type,Attrs,T});
ast_body({proj,_,{typed,_,Record,{record_t,Fields}},{id,_,FieldName}}, Icode) -> ast_body({proj,_,{typed,_,Record,{record_t,Fields}},{id,_,FieldName}}, Icode) ->
[Index] = [I [Index] = [I
|| {I,{field_t,_,{id,_,Name},_}} <- || {I,{field_t,_,{id,_,Name},_}} <-
@ -638,16 +410,14 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
when Op == '=='; Op == '!='; when Op == '=='; Op == '!=';
Op == '<'; Op == '>'; Op == '<'; Op == '>';
Op == '<='; Op == '=<'; Op == '>=' -> Op == '<='; Op == '=<'; Op == '>=' ->
Monomorphic = is_monomorphic(Type), [ gen_error({cant_compare_type_aevm, Ann, Op, Type}) || not is_simple_type(Type) ],
case ast_typerep(Type, Icode) of case ast_typerep(Type, Icode) of
_ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}; word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
OtherType -> OtherType ->
Neg = case Op of Neg = case Op of
'==' -> fun(X) -> X end; '==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end; '!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare_type_aevm, Ann, Op, Type})
end, end,
Args = [ast_body(A, Icode), ast_body(B, Icode)], Args = [ast_body(A, Icode), ast_body(B, Icode)],
Builtin = Builtin =
@ -658,10 +428,10 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
case lists:usort(Types) of case lists:usort(Types) of
[word] -> [word] ->
builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]); builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]);
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare_type_aevm, Ann, Op, Type})
end; end;
_ -> _ ->
gen_error({cant_compare, Ann, Op, Type}) gen_error({cant_compare_type_aevm, Ann, Op, Type})
end, end,
Neg(Builtin) Neg(Builtin)
end; end;
@ -670,18 +440,311 @@ ast_binop('++', _, A, B, Icode) ->
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
is_builtin_fun({qid, _, ["Chain","spend"]}, _Icode) -> true;
is_builtin_fun({qid, _, [Con, "Chain", "event"]}, #{ contract_name := Con }) -> true;
is_builtin_fun({qid, _, ["Chain", "balance"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Chain", "block_hash"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Call", "gas_left"]}, _Icode) -> true;
is_builtin_fun({id, _, "abort"}, _Icode) -> true;
is_builtin_fun({id, _, "require"}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "register"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "query_fee"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "query"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "extend"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "respond"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "get_question"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "get_answer"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "check"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "check_query"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "resolve"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "preclaim"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "claim"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "transfer"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "revoke"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "lookup"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "lookup_default"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "member"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "size"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "delete"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "from_list"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "to_list"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "verify_sig"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "verify_sig_secp256k1"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "ecverify_secp256k1"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "ecrecover_secp256k1"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "sha3"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "sha256"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "blake2b"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "sha256"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "blake2b"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "length"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "concat"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "sha3"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "test"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "set"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "clear"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "union"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "intersection"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "difference"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "sum"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Int", "to_str"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "to_str"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_oracle"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_contract"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_payable"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "to_int"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true;
is_builtin_fun(_, _) -> false.
%% -- Code generation for builtin functions --
%% Chain operations
builtin_code(_, {qid, _, ["Chain","spend"]}, [To, Amount], _, _, Icode) ->
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
builtin_code(_, {qid, _, [Con, "Chain", "event"]}, [Event], _, _, Icode = #{ contract_name := Con }) ->
aeso_builtins:check_event_type(Icode),
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
builtin_code(_, {qid, _, ["Chain", "balance"]}, [Address], _, _, Icode) ->
#prim_balance{ address = ast_body(Address, Icode) };
builtin_code(_, {qid, _, ["Chain", "block_hash"]}, [Height], _, _, Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]);
builtin_code(_, {qid, _, ["Call", "gas_left"]}, [], _, _, _Icode) ->
prim_gas_left;
%% Abort
builtin_code(_, {id, _, "abort"}, [String], _, _, Icode) ->
builtin_call(abort, [ast_body(String, Icode)]);
builtin_code(_, {id, _, "require"}, [Bool, String], _, _, Icode) ->
builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]);
%% Oracles
builtin_code(_, {qid, Ann, ["Oracle", "register"]}, Args, _, OracleType = ?oracle_t(QType, RType), Icode) ->
check_oracle_type(Ann, OracleType),
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0},
[ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode),
ast_type_value(QType, Icode), ast_type_value(RType, Icode)],
[word, sign_t(), word, ttl_t(Icode), typerep, typerep], word);
builtin_code(_, {qid, _, ["Oracle", "query_fee"]}, [Oracle], [_], _, Icode) ->
prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0},
[ast_body(Oracle, Icode)], [word], word);
builtin_code(_, {qid, Ann, ["Oracle", "query"]}, [Oracle, Q, QFee, QTTL, RTTL], [OracleType, QType, _, _, _], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode),
[ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)],
[word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word);
builtin_code(_, {qid, _, ["Oracle", "extend"]}, Args, [_, _], _, Icode) ->
{Sign, [Oracle, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)],
[word, sign_t(), ttl_t(Icode)], {tuple, []});
builtin_code(_, {qid, Ann, ["Oracle", "respond"]}, Args, [OracleType, _, RType], _, Icode) ->
check_oracle_type(Ann, OracleType),
{Sign, [Oracle, Query, R]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)],
[word, word, sign_t(), ast_type(RType, Icode)], {tuple, []});
builtin_code(_, {qid, Ann, ["Oracle", "get_question"]}, [Oracle, Q], [OracleType, ?query_t(QType, _)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode));
builtin_code(_, {qid, Ann, ["Oracle", "get_answer"]}, [Oracle, Q], [OracleType, ?query_t(_, RType)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
builtin_code(_, {qid, Ann, ["Oracle", "check"]}, [Oracle], [OracleType = ?oracle_t(Q, R)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
builtin_code(_, {qid, Ann, ["Oracle", "check_query"]}, [Oracle, Query], [OracleType, ?query_t(Q, R)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
%% Name service
builtin_code(_, {qid, Ann, ["AENS", "resolve"]}, [Name, Key], _, ?option_t(Type), Icode) ->
case is_monomorphic(Type) of
true ->
case ast_type(Type, Icode) of
T when T == word; T == string -> ok;
_ -> gen_error({invalid_aens_resolve_type, Ann, Type})
end,
prim_call(?PRIM_CALL_AENS_RESOLVE, #integer{value = 0},
[ast_body(Name, Icode), ast_body(Key, Icode), ast_type_value(Type, Icode)],
[string, string, typerep], aeso_icode:option_typerep(ast_type(Type, Icode)));
false ->
gen_error({invalid_aens_resolve_type, Ann, Type})
end;
builtin_code(_, {qid, _, ["AENS", "preclaim"]}, Args, _, _, Icode) ->
{Sign, [Addr, CHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_PRECLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(CHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
builtin_code(_, {qid, _, ["AENS", "claim"]}, Args, _, _, Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)],
[word, string, word, word, sign_t()], {tuple, []});
builtin_code(_, {qid, _, ["AENS", "transfer"]}, Args, _, _, Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []});
builtin_code(_, {qid, _, ["AENS", "revoke"]}, Args, _, _, Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
%% -- Maps
%% -- lookup functions
builtin_code(_, {qid, _, ["Map", "lookup"]}, [Key, Map], _, _, Icode) ->
map_get(Key, Map, Icode);
builtin_code(_, {qid, _, ["Map", "lookup_default"]}, [Key, Map, Val], _, _, Icode) ->
{_, ValType} = check_monomorphic_map(Map, Icode),
Fun = {map_lookup_default, ast_typerep(ValType, Icode)},
builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]);
builtin_code(_, {qid, _, ["Map", "member"]}, [Key, Map], _, _, Icode) ->
builtin_call(map_member, [ast_body(Map, Icode), ast_body(Key, Icode)]);
builtin_code(_, {qid, _, ["Map", "size"]}, [Map], _, _, Icode) ->
builtin_call(map_size, [ast_body(Map, Icode)]);
builtin_code(_, {qid, _, ["Map", "delete"]}, [Key, Map], _, _, Icode) ->
map_del(Key, Map, Icode);
%% -- map conversion to/from list
builtin_code(_, {qid, Ann, ["Map", "from_list"]}, [List], _, MapType, Icode) ->
{KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode),
builtin_call(map_from_list, [ast_body(List, Icode), map_empty(KeyType, ValType, Icode)]);
builtin_code(_, {qid, _, ["Map", "to_list"]}, [Map], _, _, Icode) ->
map_tolist(Map, Icode);
%% Crypto
builtin_code(_, {qid, _, ["Crypto", "verify_sig"]}, [Msg, PK, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
builtin_code(_, {qid, _, ["Crypto", "verify_sig_secp256k1"]}, [Msg, PK, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
builtin_code(_, {qid, _, ["Crypto", "ecverify_secp256k1"]}, [Msg, Addr, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)],
[word, bytes_t(20), bytes_t(65)], word);
builtin_code(_, {qid, _, ["Crypto", "ecrecover_secp256k1"]}, [Msg, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Sig, Icode)],
[word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20)));
builtin_code(_, {qid, _, ["Crypto", "sha3"]}, [Term], [Type], _, Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
builtin_code(_, {qid, _, ["Crypto", "sha256"]}, [Term], [Type], _, Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode);
builtin_code(_, {qid, _, ["Crypto", "blake2b"]}, [Term], [Type], _, Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode);
builtin_code(_, {qid, _, ["String", "sha256"]}, [String], _, _, Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode);
builtin_code(_, {qid, _, ["String", "blake2b"]}, [String], _, _, Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode);
%% Strings
%% -- String length
builtin_code(_, {qid, _, ["String", "length"]}, [String], _, _, Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]);
%% -- String concat
builtin_code(_, {qid, _, ["String", "concat"]}, [String1, String2], _, _, Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]);
%% -- String hash (sha3)
builtin_code(_, {qid, _, ["String", "sha3"]}, [String], _, _, Icode) ->
#unop{ op = 'sha3', rand = ast_body(String, Icode) };
builtin_code(_, {qid, _, ["Bits", Fun]}, Args, _, _, Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
builtin_code(_, {qid, _, ["Bits", "sum"]}, [Bits], _, _, Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
builtin_code(_, {qid, _, ["Int", "to_str"]}, [Int], _, _, Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]);
builtin_code(_, {qid, _, ["Address", "to_str"]}, [Addr], _, _, Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
builtin_code(_, {qid, _, ["Address", "is_oracle"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
builtin_code(_, {qid, _, ["Address", "is_contract"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
builtin_code(_, {qid, _, ["Address", "is_payable"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
builtin_code(_, {qid, _, ["Bytes", "to_str"]}, [Bytes], _, _, Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
builtin_code(_As, Fun, _Args, _ArgsT, _RetT, _Icode) ->
gen_error({missing_code_for, Fun}).
eta_expand(Id = {_, Ann0, _}, Type = {fun_t, _, [], ArgsT, _}, Icode) ->
Ann = [{origin, system} | Ann0],
Xs = [ {arg, Ann, {id, Ann, "%" ++ integer_to_list(I)}, T} ||
{I, T} <- lists:zip(lists:seq(1, length(ArgsT)), ArgsT) ],
Args = [ {typed, Ann, X, T} || {arg, _, X, T} <- Xs ],
ast_body({lam, Ann, Xs, {app, Ann, {typed, Ann, Id, Type}, Args}}, Icode);
eta_expand(Id, _Type, _Icode) ->
gen_error({unapplied_builtin, Id}).
check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map({typed, Ann, _, MapType}, Icode) ->
check_monomorphic_map(Ann, MapType, Icode). check_monomorphic_map(Ann, MapType, Icode).
check_monomorphic_map(Ann, Type = ?map_t(KeyType, ValType), Icode) -> -dialyzer({nowarn_function, check_monomorphic_map/3}).
case is_monomorphic(KeyType) of check_monomorphic_map(Ann, ?map_t(KeyType, ValType), _Icode) ->
true -> Err = fun(Why) -> gen_error({invalid_map_key_type, Why, Ann, KeyType}) end,
case has_maps(ast_type(KeyType, Icode)) of [ Err(polymorphic) || not is_monomorphic(KeyType) ],
false -> {KeyType, ValType}; [ Err(function) || not is_first_order_type(KeyType) ],
true -> gen_error({cant_use_map_as_map_keys, Ann, Type}) {KeyType, ValType}.
end;
false -> gen_error({cant_compile_map_with_polymorphic_keys, Ann, Type})
end.
map_empty(KeyType, ValType, Icode) -> map_empty(KeyType, ValType, Icode) ->
prim_call(?PRIM_CALL_MAP_EMPTY, #integer{value = 0}, prim_call(?PRIM_CALL_MAP_EMPTY, #integer{value = 0},
@ -689,8 +752,8 @@ map_empty(KeyType, ValType, Icode) ->
ast_type_value(ValType, Icode)], ast_type_value(ValType, Icode)],
[typerep, typerep], word). [typerep, typerep], word).
map_get(Key, Map = {typed, Ann, _, MapType}, Icode) -> map_get(Key, Map = {typed, _Ann, _, MapType}, Icode) ->
{_KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), {_KeyType, ValType} = check_monomorphic_map(aeso_syntax:get_ann(Key), MapType, Icode),
builtin_call({map_lookup, ast_type(ValType, Icode)}, [ast_body(Map, Icode), ast_body(Key, Icode)]). builtin_call({map_lookup, ast_type(ValType, Icode)}, [ast_body(Map, Icode), ast_body(Key, Icode)]).
map_put(Key, Val, Map, Icode) -> map_put(Key, Val, Map, Icode) ->
@ -720,14 +783,29 @@ map_upd(Key, Default, ValFun, Map = {typed, Ann, _, MapType}, Icode) ->
builtin_call(FunName, Args). builtin_call(FunName, Args).
check_entrypoint_type(Ann, Name, Args, Ret) -> check_entrypoint_type(Ann, Name, Args, Ret) ->
Check = fun(T, Err) -> CheckFirstOrder = fun(T, Err) ->
case is_simple_type(T) of case is_first_order_type(T) of
false -> gen_error(Err); false -> gen_error(Err);
true -> ok true -> ok
end end, end end,
[ Check(T, {entrypoint_argument_must_have_simple_type, Ann1, Name, X, T}) CheckMonomorphic = fun(T, Err) ->
case is_monomorphic(T) of
false -> gen_error(Err);
true -> ok
end end,
[ CheckFirstOrder(T, {invalid_entrypoint, higher_order, Ann1, Name, {argument, X, T}})
|| {arg, Ann1, X, T} <- Args ], || {arg, Ann1, X, T} <- Args ],
Check(Ret, {entrypoint_must_have_simple_return_type, Ann, Name, Ret}). CheckFirstOrder(Ret, {invalid_entrypoint, higher_order, Ann, Name, {result, Ret}}),
[ CheckMonomorphic(T, {invalid_entrypoint, polymorphic, Ann1, Name, {argument, X, T}})
|| {arg, Ann1, X, T} <- Args ],
CheckMonomorphic(Ret, {invalid_entrypoint, polymorphic, Ann, Name, {result, Ret}}).
check_oracle_type(Ann, Type = ?oracle_t(QType, RType)) ->
[ gen_error({invalid_oracle_type, Why, Which, Ann, Type})
|| {Why, Check} <- [{polymorphic, fun is_monomorphic/1},
{higher_order, fun is_first_order_type/1}],
{Which, T} <- [{query, QType}, {response, RType}],
not Check(T) ].
is_simple_type({tvar, _, _}) -> false; is_simple_type({tvar, _, _}) -> false;
is_simple_type({fun_t, _, _, _, _}) -> false; is_simple_type({fun_t, _, _, _, _}) -> false;
@ -735,6 +813,11 @@ is_simple_type(Ts) when is_list(Ts) -> lists:all(fun is_simple_type/1, Ts);
is_simple_type(T) when is_tuple(T) -> is_simple_type(tuple_to_list(T)); is_simple_type(T) when is_tuple(T) -> is_simple_type(tuple_to_list(T));
is_simple_type(_) -> true. is_simple_type(_) -> true.
is_first_order_type({fun_t, _, _, _, _}) -> false;
is_first_order_type(Ts) when is_list(Ts) -> lists:all(fun is_first_order_type/1, Ts);
is_first_order_type(T) when is_tuple(T) -> is_first_order_type(tuple_to_list(T));
is_first_order_type(_) -> true.
is_monomorphic({tvar, _, _}) -> false; is_monomorphic({tvar, _, _}) -> false;
is_monomorphic([H|T]) -> is_monomorphic([H|T]) ->
is_monomorphic(H) andalso is_monomorphic(T); is_monomorphic(H) andalso is_monomorphic(T);
@ -870,14 +953,6 @@ ast_fun_to_icode(Name, Attrs, Args, Body, TypeRep, #{functions := Funs} = Icode)
NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs], NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs],
aeso_icode:set_functions(NewFuns, Icode). aeso_icode:set_functions(NewFuns, Icode).
has_maps({map, _, _}) -> true;
has_maps(word) -> false;
has_maps(string) -> false;
has_maps(typerep) -> false;
has_maps({list, T}) -> has_maps(T);
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
%% A function is private if not an 'entrypoint', or if it's not defined in the %% A function is private if not an 'entrypoint', or if it's not defined in the
%% main contract name space. (NOTE: changes when we introduce inheritance). %% main contract name space. (NOTE: changes when we introduce inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) -> is_private(Ann, #{ contract_name := MainContract } = Icode) ->

120
src/aeso_code_errors.erl Normal file
View File

@ -0,0 +1,120 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Formatting of code generation errors.
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(aeso_code_errors).
-export([format/1]).
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",
[C]),
mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n",
mk_err(pos(Con), Msg, Cxt);
format({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]),
mk_err(pos(Id), Msg);
format({parameterized_state, Decl}) ->
Msg = "The state type cannot be parameterized.\n",
mk_err(pos(Decl), Msg);
format({parameterized_event, Decl}) ->
Msg = "The event type cannot be parameterized.\n",
mk_err(pos(Decl), Msg);
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
What = case Why of higher_order -> "higher-order (contains function types)";
polymorphic -> "polymorphic (contains type variables)" end,
ThingS = case Thing of
{argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]);
{result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)])
end,
Bad = case Thing of
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
{result, _} -> io_lib:format("is ~s", [What])
end,
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n",
[ThingS, Name, Bad]),
case Why of
polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n");
higher_order -> mk_err(pos(Ann), Msg)
end;
format({cant_compare_type_aevm, Ann, Op, Type}) ->
StringAndTuple = [ "- type string\n"
"- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ],
Msg = io_lib:format("Cannot compare values of type\n"
"~s\n"
"The AEVM only supports '~s' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"~s",
[pp_type(2, Type), Op, StringAndTuple]),
Cxt = "Use FATE if you need to compare arbitrary types.\n",
mk_err(pos(Ann), Msg, Cxt);
format({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
"~s\n"
"It must be a string or a pubkey type (address, oracle, etc).\n",
[pp_type(2, T)]),
mk_err(pos(Ann), Msg);
format({unapplied_contract_call, Contract}) ->
Msg = io_lib:format("The AEVM does not support unapplied contract call to\n"
"~s\n", [pp_expr(2, Contract)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Contract), Msg, Cxt);
format({unapplied_builtin, Id}) ->
Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Id), Msg, Cxt);
format({invalid_map_key_type, Why, Ann, Type}) ->
Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]),
Cxt = case Why of
polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n";
function -> "Map keys cannot be higher-order.\n"
end,
mk_err(pos(Ann), Msg, Cxt);
format({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]),
Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]),
mk_err(pos(Ann), Msg, Cxt);
format({higher_order_state, {type_def, Ann, _, _, State}}) ->
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
mk_err(pos(Ann), Msg, Cxt);
format(Err) ->
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
pos(Ann) ->
File = aeso_syntax:get_ann(file, Ann, no_file),
Line = aeso_syntax:get_ann(line, Ann, 0),
Col = aeso_syntax:get_ann(col, Ann, 0),
aeso_errors:pos(File, Line, Col).
pp_typed(E, T) ->
prettypr:format(prettypr:nest(2,
lists:foldr(fun prettypr:beside/2, prettypr:empty(),
[aeso_pretty:expr(E), prettypr:text(" : "),
aeso_pretty:type(T)]))).
pp_expr(E) ->
pp_expr(0, E).
pp_expr(N, E) ->
prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))).
pp_type(N, T) ->
prettypr:format(prettypr:nest(N, aeso_pretty:type(T))).
mk_err(Pos, Msg) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg)).
mk_err(Pos, Msg, Cxt) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)).

View File

@ -98,15 +98,7 @@ from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) from_string1(Backend, ContractString, Options)
catch catch
%% The compiler errors. throw:{error, Errors} -> {error, 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. end.
from_string1(aevm, ContractString, Options) -> from_string1(aevm, ContractString, Options) ->
@ -230,16 +222,10 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, 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:{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)}
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) -> arguments_of_body(CallName, _FunName, Fcode) ->
@ -345,16 +331,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, 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:{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)}
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
@ -444,16 +424,11 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, Errors};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
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)}
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
@ -582,37 +557,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]).

View File

@ -1,42 +0,0 @@
-module(aeso_constants).
-export([string/1, get_type/1]).
string(Str) ->
case aeso_parser:string("let _ = " ++ Str) of
{ok, [{letval, _, _, _, E}]} -> {ok, E};
{ok, Other} -> error({internal_error, should_be_letval, Other});
Err -> Err
end.
get_type(Str) ->
case aeso_parser:string("let _ = " ++ Str) of
{ok, [Ast]} ->
AstT = aeso_ast_infer_types:infer_constant(Ast),
T = ast_to_type(AstT),
{ok, T};
{ok, Other} -> error({internal_error, should_be_letval, Other});
Err -> Err
end.
ast_to_type({id, _, T}) ->
T;
ast_to_type({tuple_t, _, []}) -> "()";
ast_to_type({tuple_t, _, Ts}) ->
"(" ++ list_ast_to_type(Ts) ++ ")";
ast_to_type({app_t,_, {id, _, "list"}, [T]}) ->
lists:flatten("list(" ++ ast_to_type(T) ++ ")");
ast_to_type({app_t,_, {id, _, "option"}, [T]}) ->
lists:flatten("option(" ++ ast_to_type(T) ++ ")").
list_ast_to_type([T]) ->
ast_to_type(T);
list_ast_to_type([T|Ts]) ->
ast_to_type(T)
++ ", "
++ list_ast_to_type(Ts).

79
src/aeso_errors.erl Normal file
View File

@ -0,0 +1,79 @@
%%%-------------------------------------------------------------------
%%% @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
, throw/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 }.
-spec throw(_) -> ok | no_return().
throw([]) -> ok;
throw(Errs) when is_list(Errs) ->
erlang:throw({error, Errs});
throw(#err{} = Err) ->
erlang:throw({error, [Err]}).
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

@ -16,6 +16,7 @@
set_payable/2, set_payable/2,
enter_namespace/2, enter_namespace/2,
get_namespace/1, get_namespace/1,
in_main_contract/1,
qualify/2, qualify/2,
set_functions/2, set_functions/2,
map_typerep/2, map_typerep/2,
@ -120,6 +121,10 @@ enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
enter_namespace(NS, Icode) -> enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }. Icode#{ namespace => NS }.
-spec in_main_contract(icode()) -> boolean().
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
in_main_contract(_Icode) -> false.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon(). -spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false). get_namespace(Icode) -> maps:get(namespace, Icode, false).

View File

@ -27,7 +27,7 @@ convert(#{ contract_name := _ContractName
}, },
_Options) -> _Options) ->
%% Create a function dispatcher %% Create a function dispatcher
DispatchFun = {"_main", [], [{"arg", "_"}], DispatchFun = {"%main", [], [{"arg", "_"}],
{switch, {var_ref, "arg"}, {switch, {var_ref, "arg"},
[{{tuple, [fun_hash(Fun), [{{tuple, [fun_hash(Fun),
{tuple, make_args(Args)}]}, {tuple, make_args(Args)}]},
@ -44,7 +44,7 @@ convert(#{ contract_name := _ContractName
%% taken from the stack %% taken from the stack
StopLabel = make_ref(), StopLabel = make_ref(),
StatefulStopLabel = make_ref(), StatefulStopLabel = make_ref(),
MainFunction = lookup_fun(Funs, "_main"), MainFunction = lookup_fun(Funs, "%main"),
StateTypeValue = aeso_ast_to_icode:type_value(StateType), StateTypeValue = aeso_ast_to_icode:type_value(StateType),

View File

@ -9,12 +9,14 @@
-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,
infixl/2, infixr/2]). infixl/2, infixr/2]).
-export([current_file/0, set_current_file/1]).
%% -- Types ------------------------------------------------------------------ %% -- Types ------------------------------------------------------------------
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
@ -98,6 +100,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).
@ -155,7 +161,7 @@ layout() -> ?layout.
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}. -spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
parse(P, S) -> parse(P, S) ->
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of
{[], {Pos, Err}} -> {error, {Pos, parse_error, flatten_error(Err)}}; {[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
{[A], _} -> {ok, A}; {[A], _} -> {ok, A};
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}} {As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
end. end.
@ -285,7 +291,7 @@ parse1({tok_bind, Map}, Ts, Acc, Err) ->
%% y + y)(4) %% y + y)(4)
case maps:get(vclose, Map, '$not_found') of case maps:get(vclose, Map, '$not_found') of
'$not_found' -> '$not_found' ->
{Acc, unexpected_token_error(Ts, T)}; {Acc, unexpected_token_error(Ts, maps:keys(Map), T)};
F -> F ->
VClose = {vclose, pos(T)}, VClose = {vclose, pos(T)},
Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }), Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }),
@ -322,12 +328,52 @@ 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}.
-spec unexpected_token_error(#ts{}, token()) -> error(). -spec unexpected_token_error(#ts{}, token()) -> error().
unexpected_token_error(Ts, T) -> unexpected_token_error(Ts, T) ->
mk_error(Ts, io_lib:format("Unexpected token ~p", [tag(T)])). unexpected_token_error(Ts, [], T).
unexpected_token_error(Ts, Expect, {Tag, _}) when Tag == vclose; Tag == vsemi ->
Braces = [')', ']', '}'],
Fix = case lists:filter(fun(T) -> lists:member(T, Braces) end, Expect) of
[] -> " Probable causes:\n"
" - something is missing in the previous statement, or\n"
" - this line should be indented more.";
[T | _] -> io_lib:format(" Did you forget a ~p?", [T])
end,
Msg = io_lib:format("Unexpected indentation.~s", [Fix]),
mk_error(Ts, Msg);
unexpected_token_error(Ts, Expect, T) ->
ExpectCon = lists:member(con, Expect),
ExpectId = lists:member(id, Expect),
Fix = case T of
{id, _, X} when ExpectCon, hd(X) /= $_ -> io_lib:format(" Did you mean ~s?", [mk_upper(X)]);
{con, _, X} when ExpectId -> io_lib:format(" Did you mean ~s?", [mk_lower(X)]);
{qcon, _, Xs} when ExpectCon -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]);
{qid, _, Xs} when ExpectId -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]);
_ -> ""
end,
mk_error(Ts, io_lib:format("Unexpected ~s.~s", [describe(T), Fix])).
mk_upper([C | Rest]) -> string:to_upper([C]) ++ Rest.
mk_lower([C | Rest]) -> string:to_lower([C]) ++ Rest.
describe({id, _, X}) -> io_lib:format("identifier ~s", [X]);
describe({con, _, X}) -> io_lib:format("identifier ~s", [X]);
describe({qid, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]);
describe({qcon, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]);
describe({tvar, _, X}) -> io_lib:format("type variable ~s", [X]);
describe({char, _, _}) -> "character literal";
describe({string, _, _}) -> "string literal";
describe({hex, _, _}) -> "integer literal";
describe({int, _, _}) -> "integer literal";
describe({bytes, _, _}) -> "bytes literal";
describe(T) -> io_lib:format("token '~s'", [tag(T)]).
%% Get the next token from a token stream. Inserts layout tokens if necessary. %% Get the next token from a token stream. Inserts layout tokens if necessary.
-spec next_token(#ts{}) -> false | {token(), #ts{}}. -spec next_token(#ts{}) -> false | {token(), #ts{}}.
@ -411,3 +457,13 @@ merge_with(Fun, Map1, Map2) ->
end, Map2, maps:to_list(Map1)) end, Map2, maps:to_list(Map1))
end. end.
%% Current source file
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
add_current_file({L, C}) -> {current_file(), L, C};
add_current_file(Pos) -> Pos.

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

@ -11,10 +11,9 @@
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-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 +32,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} -> parse_error(Err)
end;
{error, Err} ->
parse_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 +53,28 @@ parse_and_scan(P, S, Opts) ->
Error -> Error Error -> Error
end. end.
-dialyzer({nowarn_function, parse_error/1}).
parse_error(Err) ->
aeso_errors:throw(mk_error(Err)).
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())).
@ -425,12 +452,6 @@ bracket_list(P) -> brackets(comma_sep(P)).
-spec pos_ann(ann_line(), ann_col()) -> ann(). -spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}]. pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) -> ann_pos(Ann) ->
{proplists:get_value(file, Ann), {proplists:get_value(file, Ann),
proplists:get_value(line, Ann), proplists:get_value(line, Ann),
@ -533,14 +554,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 +616,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("At line 3, col 5:\nCannot 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

@ -34,14 +34,26 @@ simple_compile_test_() ->
not lists:member(ContractName, not_yet_compilable(Backend))] ++ not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
case compile(aevm, ContractName) of Errors = compile(aevm, ContractName),
<<"Type errors\n", ErrorString/binary>> -> check_errors(ExpectedErrors, Errors)
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString)
end
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++ {ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing " ++ atom_to_list(Backend) ++ " code generation error messages of " ++ ContractName,
fun() ->
Errors = compile(Backend, ContractName),
Expect =
case is_binary(ExpectedError) of
true -> [ExpectedError];
false ->
case proplists:get_value(Backend, ExpectedError, no_error) of
no_error -> no_error;
Err -> [Err]
end
end,
check_errors(Expect, Errors)
end} ||
{ContractName, ExpectedError} <- failing_code_gen_contracts(),
Backend <- [aevm, fate] ] ++
[ {"Testing include with explicit files", [ {"Testing include with explicit files",
fun() -> fun() ->
FileSystem = maps:from_list( FileSystem = maps:from_list(
@ -63,11 +75,15 @@ simple_compile_test_() ->
Backend == fate -> 20 end, Backend == fate -> 20 end,
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
ok ok
end} || Backend <- [aevm, fate] ]. end} || Backend <- [aevm, fate] ] ++
[].
check_errors(Expect, ErrorString) -> check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
%% This removes the final single \n as well. check_errors(Expect, #{}) ->
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]), ?assertEqual({error, Expect}, ok);
check_errors(Expect0, Actual0) ->
Expect = lists:sort(Expect0),
Actual = [ list_to_binary(string:trim(aeso_errors:pp(Err))) || Err <- Actual0 ],
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});
@ -80,9 +96,10 @@ compile(Backend, Name) ->
compile(Backend, Name, Options) -> compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of case aeso_compiler:from_string(String, [{src_file, Name ++ ".aes"}, {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].
@ -127,7 +144,8 @@ compilable_contracts() ->
"double_include", "double_include",
"manual_stdlib_include", "manual_stdlib_include",
"list_comp", "list_comp",
"payable" "payable",
"unapplied_builtins"
]. ].
not_yet_compilable(fate) -> []; not_yet_compilable(fate) -> [];
@ -135,242 +153,465 @@ not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% 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(TEST(Name, Errs),
(fun() ->
__File = ??Name,
{__File, Errs}
end)()).
failing_contracts() -> failing_contracts() ->
[ {"name_clash", %% Parse errors
[<<"Duplicate definitions of abort at\n" [ ?TEST(field_parse_error,
[<<?Pos(5, 26)
"Cannot use nested fields or keys in record construction: p.x">>])
, ?TEST(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
, ?TEST(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
, ?TEST(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
%% Type errors
, ?TEST(name_clash,
[<<?Pos(14, 3)
"Duplicate definitions of abort at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 14, column 3">>, " - line 14, column 3">>,
<<"Duplicate definitions of require at\n" <<?Pos(15, 3)
"Duplicate definitions of require at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 15, column 3">>, " - line 15, column 3">>,
<<"Duplicate definitions of double_def at\n" <<?Pos(11, 3)
"Duplicate definitions of double_def at\n"
" - line 10, column 3\n" " - line 10, column 3\n"
" - line 11, column 3">>, " - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n" <<?Pos(5, 3)
"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n" " - line 4, column 3\n"
" - line 5, column 3">>, " - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n" <<?Pos(8, 3)
"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n" " - line 7, column 3\n"
" - line 8, column 3">>, " - line 8, column 3">>,
<<"Duplicate definitions of put at\n" <<?Pos(16, 3)
"Duplicate definitions of put at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 16, column 3">>, " - line 16, column 3">>,
<<"Duplicate definitions of state at\n" <<?Pos(17, 3)
"Duplicate definitions of state at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 17, column 3">>]} " - line 17, column 3">>])
, {"type_errors", , ?TEST(type_errors,
[<<"Unbound variable zz at line 17, column 23">>, [<<?Pos(17, 23)
<<"Cannot unify int\n" "Unbound variable zz at line 17, column 23">>,
<<?Pos(26, 9)
"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n" " (::) : (int, list(int)) => list(int)\n"
"to arguments\n" "to arguments\n"
" x : int\n" " x : int\n"
" x : int">>, " x : int">>,
<<"Cannot unify string\n" <<?Pos(9, 48)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 48)\n" " x : map(string, string) (at line 9, column 48)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n" <<?Pos(34, 47)
"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 47\n" "when checking the type of the expression at line 34, column 47\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string">>, " string">>,
<<"Cannot unify string\n" <<?Pos(34, 52)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 52\n" "when checking the type of the expression at line 34, column 52\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify string\n" <<?Pos(32, 18)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 32, column 18\n" "when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n" " \"x\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify string\n" <<?Pos(11, 58)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 58\n" "when checking the type of the expression at line 11, column 58\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify int\n" <<?Pos(38, 13)
"Cannot unify int\n"
" and string\n" " and string\n"
"when comparing the types of the if-branches\n" "when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>, " - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n" <<?Pos(22, 40)
"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>, "arising from the projection of the field y (at line 22, column 40)">>,
<<"Not a record type: string\n" <<?Pos(21, 44)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>, "arising from an assignment of the field y (at line 21, column 44)">>,
<<"Not a record type: string\n" <<?Pos(20, 40)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>, "arising from an assignment of the field y (at line 20, column 40)">>,
<<"Not a record type: string\n" <<?Pos(19, 37)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>, "arising from an assignment of the field y (at line 19, column 37)">>,
<<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" <<?Pos(13, 27)
"Ambiguous record type with field y (at line 13, column 27) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n" <<?Pos(26, 7)
"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>, " x :: x (at line 26, column 7)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 14).">>, <<?Pos(44, 14)
<<"Repeated argument y to function repeated_arg (at line 44, column 14).">>, "Repeated argument x to function repeated_arg (at line 44, column 14).">>,
<<"No record type with fields y, z (at line 14, column 24)">>, <<?Pos(44, 14)
<<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>, "Repeated argument y to function repeated_arg (at line 44, column 14).">>,
<<"Record type r2 does not have field y (at line 15, column 24)">>, <<?Pos(14, 24)
<<"Let binding at line 47, column 5 must be followed by an expression">>, "No record type with fields y, z (at line 14, column 24)">>,
<<"Let binding at line 50, column 5 must be followed by an expression">>, <<?Pos(15, 26)
<<"Let binding at line 54, column 5 must be followed by an expression">>, "The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<"Let binding at line 58, column 5 must be followed by an expression">>]} <<?Pos(15, 24)
, {"init_type_error", "Record type r2 does not have field y (at line 15, column 24)">>,
[<<"Cannot unify string\n" <<?Pos(47, 5)
"Let binding at line 47, column 5 must be followed by an expression">>,
<<?Pos(50, 5)
"Let binding at line 50, column 5 must be followed by an expression">>,
<<?Pos(54, 5)
"Let binding at line 54, column 5 must be followed by an expression">>,
<<?Pos(58, 5)
"Let binding at line 58, column 5 must be followed by an expression">>])
, ?TEST(init_type_error,
[<<?Pos(7, 3)
"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>])
, {"missing_state_type", , ?TEST(missing_state_type,
[<<"Cannot unify string\n" [<<?Pos(5, 3)
"Cannot unify string\n"
" and unit\n" " and unit\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>])
, {"missing_fields_in_record_expression", , ?TEST(missing_fields_in_record_expression,
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, [<<?Pos(7, 42)
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, "The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>,
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} <<?Pos(8, 42)
, {"namespace_clash", "The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>,
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} <<?Pos(6, 42)
, {"bad_events", "The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>])
[<<"The indexed type string (at line 9, column 25) is not a word type">>, , ?TEST(namespace_clash,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} [<<?Pos(4, 10)
, {"bad_events2", "The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>])
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, , ?TEST(bad_events,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} [<<?Pos(9, 25)
, {"type_clash", "The indexed type string (at line 9, column 25) is not a word type">>,
[<<"Cannot unify int\n" <<?Pos(10, 25)
"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>])
, ?TEST(bad_events2,
[<<?Pos(9, 7)
"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<?Pos(10, 7)
"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>])
, ?TEST(type_clash,
[<<?Pos(12, 42)
"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the record projection at line 12, column 42\n" "when checking the record projection at line 12, column 42\n"
" r.foo : (gas : int, value : int) => Remote.themap\n" " r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n" "against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]} " (gas : int, value : int) => map(string, int)">>])
, {"bad_include_and_ns", , ?TEST(bad_include_and_ns,
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>, [<<?Pos(2, 11)
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]} "Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
, {"bad_address_literals", <<?Pos(3, 13)
[<<"The type bytes(32) is not a contract type\n" "Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>])
, ?TEST(bad_address_literals,
[<<?Pos(32, 5)
"The type bytes(32) is not a contract type\n"
"when checking that the contract literal at line 32, column 5\n" "when checking that the contract literal at line 32, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" bytes(32)">>, " bytes(32)">>,
<<"The type oracle(int, bool) is not a contract type\n" <<?Pos(30, 5)
"The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal at line 30, column 5\n" "when checking that the contract literal at line 30, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"The type address is not a contract type\n" <<?Pos(28, 5)
"The type address is not a contract type\n"
"when checking that the contract literal at line 28, column 5\n" "when checking that the contract literal at line 28, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
<<"Cannot unify oracle_query('a, 'b)\n" <<?Pos(25, 5)
"Cannot unify oracle_query('a, 'b)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 25, column 5\n" "when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n" " oracle_query('a, 'b)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n" <<?Pos(23, 5)
"Cannot unify oracle_query('c, 'd)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n" "when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n" " oracle_query('c, 'd)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n" <<?Pos(21, 5)
"Cannot unify oracle_query('e, 'f)\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n" "when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n" " oracle_query('e, 'f)\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n" <<?Pos(18, 5)
"Cannot unify oracle('g, 'h)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 18, column 5\n" "when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n" " oracle('g, 'h)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle('i, 'j)\n" <<?Pos(16, 5)
"Cannot unify oracle('i, 'j)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n" "when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n" " oracle('i, 'j)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n" <<?Pos(14, 5)
"Cannot unify oracle('k, 'l)\n"
" and oracle_query(int, bool)\n" " and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n" "when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\n" " oracle('k, 'l)\n"
"against the expected type\n" "against the expected type\n"
" oracle_query(int, bool)">>, " oracle_query(int, bool)">>,
<<"Cannot unify address\n" <<?Pos(11, 5)
"Cannot unify address\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 11, column 5\n" "when checking the type of the expression at line 11, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify address\n" <<?Pos(9, 5)
"Cannot unify address\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 9, column 5\n" "when checking the type of the expression at line 9, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify address\n" <<?Pos(7, 5)
"Cannot unify address\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 7, column 5\n" "when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>]} " bytes(32)">>])
, {"stateful", , ?TEST(stateful,
[<<"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>, [<<?Pos(13, 35)
<<"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>, "Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>, <<?Pos(14, 35)
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>, "Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>,
<<"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>, <<?Pos(16, 15)
<<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>, "Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>, <<?Pos(20, 31)
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]} "Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
, {"bad_init_state_access", <<?Pos(35, 47)
[<<"The init function should return the initial state as its result and cannot write the state,\n" "Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>,
<<?Pos(48, 57)
"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<?Pos(49, 56)
"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>,
<<?Pos(52, 17)
"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>])
, ?TEST(bad_init_state_access,
[<<?Pos(11, 5)
"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n" "but it calls\n"
" - set_state (at line 11, column 5), which calls\n" " - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 38), which calls\n" " - roundabout (at line 8, column 38), which calls\n"
" - put (at line 7, column 39)">>, " - put (at line 7, column 39)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n" <<?Pos(12, 5)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n" "but it calls\n"
" - new_state (at line 12, column 5), which calls\n" " - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 29)">>, " - state (at line 5, column 29)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n" <<?Pos(13, 13)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n" "but it calls\n"
" - state (at line 13, column 13)">>]} " - state (at line 13, column 13)">>])
, {"field_parse_error", , ?TEST(modifier_checks,
[<<"line 6, column 1: In field_parse_error at 5:26:\n" [<<?Pos(11, 3)
"Cannot use nested fields or keys in record construction: p.x\n">>]} "The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
, {"modifier_checks", <<?Pos(3, 3)
[<<"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.">>, <<?Pos(5, 10)
<<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>, "The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>, <<?Pos(12, 3)
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>, "The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>, <<?Pos(6, 3)
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]} "Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
, {"list_comp_not_a_list", <<?Pos(10, 3)
[<<"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">> "Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
]} <<?Pos(6, 3)
, {"list_comp_if_not_bool", "Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>])
[<<"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">> , ?TEST(list_comp_not_a_list,
]} [<<?Pos(2, 36)
, {"list_comp_bad_shadow", "Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
[<<"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">> ])
]} , ?TEST(list_comp_if_not_bool,
[<<?Pos(2, 44)
"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
])
, ?TEST(list_comp_bad_shadow,
[<<?Pos(2, 53)
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
])
, ?TEST(map_as_map_key,
[<<?Pos(5, 25)
"Invalid key type\n"
" map(int, int)\n"
"Map keys cannot contain other maps.">>,
<<?Pos(6, 25)
"Invalid key type\n"
" lm\n"
"Map keys cannot contain other maps.">>])
, ?TEST(calling_init_function,
[<<?Pos(7, 28)
"The 'init' function is called exclusively by the create contract transaction\n"
"and cannot be called from the contract code.">>])
, ?TEST(bad_top_level_decl,
[<<?Pos(1, 1) "The definition of 'square' must appear inside a contract or namespace.">>])
, ?TEST(missing_event_type,
[<<?Pos(3, 5)
"Unbound variable Chain.event at line 3, column 5\n"
"Did you forget to define the event type?">>])
].
-define(Path(File), "code_errors/" ??File).
-define(Msg(File, Line, Col, Err), <<?Pos(?Path(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)}]}).
-define(FATE(File, Line, Col, Err), {?Path(File), [{fate, ?Msg(File, Line, Col, Err)}]}).
-define(BOTH(File, Line, Col, ErrAEVM, ErrFATE),
{?Path(File), [{aevm, ?Msg(File, Line, Col, ErrAEVM)},
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
failing_code_gen_contracts() ->
[ ?SAME(last_declaration_must_be_contract, 1, 1,
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
, ?SAME(missing_definition, 2, 14,
"Missing definition of function 'foo'.")
, ?AEVM(polymorphic_entrypoint, 2, 17,
"The argument\n"
" x : 'a\n"
"of entrypoint 'id' has a polymorphic (contains type variables) type.\n"
"Use the FATE backend if you want polymorphic entrypoints.")
, ?AEVM(polymorphic_entrypoint_return, 2, 3,
"The return type\n"
" 'a\n"
"of entrypoint 'fail' is polymorphic (contains type variables).\n"
"Use the FATE backend if you want polymorphic entrypoints.")
, ?SAME(higher_order_entrypoint, 2, 20,
"The argument\n"
" f : (int) => int\n"
"of entrypoint 'apply' has a higher-order (contains function types) type.")
, ?SAME(higher_order_entrypoint_return, 2, 3,
"The return type\n"
" (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).")
, ?SAME(missing_init_function, 1, 10,
"Missing init function for the contract 'MissingInitFunction'.\n"
"The 'init' function can only be omitted if the state type is 'unit'.")
, ?SAME(parameterised_state, 3, 8,
"The state type cannot be parameterized.")
, ?SAME(parameterised_event, 3, 12,
"The event type cannot be parameterized.")
, ?SAME(polymorphic_aens_resolve, 4, 5,
"Invalid return type of AENS.resolve:\n"
" 'a\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?SAME(bad_aens_resolve, 6, 5,
"Invalid return type of AENS.resolve:\n"
" list(int)\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?AEVM(polymorphic_compare, 4, 5,
"Cannot compare values of type\n"
" 'a\n"
"The AEVM only supports '==' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"- type string\n"
"- tuple or record of word type\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(complex_compare, 4, 5,
"Cannot compare values of type\n"
" (string * int)\n"
"The AEVM only supports '!=' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"- type string\n"
"- tuple or record of word type\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(complex_compare_leq, 4, 5,
"Cannot compare values of type\n"
" (int * int)\n"
"The AEVM only supports '=<' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(higher_order_compare, 4, 5,
"Cannot compare values of type\n"
" (int) => int\n"
"The AEVM only supports '<' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(unapplied_contract_call, 6, 19,
"The AEVM does not support unapplied contract call to\n"
" r : Remote\n"
"Use FATE if you need this.")
, ?AEVM(unapplied_named_arg_builtin, 4, 15,
"The AEVM does not support unapplied use of Oracle.register.\n"
"Use FATE if you need this.")
, ?AEVM(polymorphic_map_keys, 4, 34,
"Invalid map key type\n"
" 'a\n"
"Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.")
, ?AEVM(higher_order_map_keys, 4, 42,
"Invalid map key type\n"
" (int) => int\n"
"Map keys cannot be higher-order.")
, ?SAME(polymorphic_query_type, 3, 5,
"Invalid oracle type\n"
" oracle('a, 'b)\n"
"The query type must not be polymorphic (contain type variables).")
, ?SAME(polymorphic_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, 'r)\n"
"The response type must not be polymorphic (contain type variables).")
, ?SAME(higher_order_query_type, 3, 5,
"Invalid oracle type\n"
" oracle((int) => int, string)\n"
"The query type must not be higher-order (contain function types).")
, ?SAME(higher_order_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, (int) => int)\n"
"The response type must not be higher-order (contain function types).")
, ?AEVM(higher_order_state, 3, 3,
"Invalid state type\n"
" {f : (int) => int}\n"
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
]. ].

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({error, [_]},
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}] =

View File

@ -0,0 +1,3 @@
function square(x) = x ^ 2
contract Main =
entrypoint main() = square(10)

View File

@ -0,0 +1,7 @@
contract CallingInitFunction =
type state = int * int
entrypoint init() = (1, 2)
entrypoint call_init() = init()

View File

@ -0,0 +1,9 @@
contract BadAENSresolve =
type t('a) = option(list('a))
function fail() : t(int) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()

View File

@ -0,0 +1,4 @@
contract ComplexCompare =
entrypoint test(x : string * int) =
("foo", 1) != x

View File

@ -0,0 +1,4 @@
contract ComplexCompare =
entrypoint test(x : int) =
(1, 2) =< (x, x + 1)

View File

@ -0,0 +1,8 @@
contract HigherOrderCompare =
function cmp(x : int => int, y) : bool =
x < y
entrypoint test() =
let f(x) = (y) => x + y
cmp(f(1), f(2))

View File

@ -0,0 +1,2 @@
contract HigherOrderEntrypoint =
entrypoint apply(f : int => int, x : int) = f(x)

View File

@ -0,0 +1,2 @@
contract HigherOrderEntrypoint =
entrypoint add(x : int) = (y) => x + y

View File

@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
function foo(m) : t(int => int) = {[m] = 0}
entrypoint main() = ()

View File

@ -0,0 +1,5 @@
contract HigherOrderQueryType =
stateful function foo(o) : oracle_query(_, string ) =
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
entrypoint main() = ()

View File

@ -0,0 +1,5 @@
contract HigherOrderResponseType =
stateful function foo(o, q : oracle_query(string, _)) =
Oracle.respond(o, q, (x) => x + 1)
entrypoint main() = ()

View File

@ -0,0 +1,7 @@
contract HigherOrderState =
record state = {f : int => int}
entrypoint init() = {f = (x) => x}
entrypoint apply(n) = state.f(n)
stateful entrypoint inc() = put(state{ f = (x) => state.f(x + 1) })

View File

@ -0,0 +1,2 @@
namespace LastDeclarationIsNotAContract =
function add(x, y) = x + y

View File

@ -0,0 +1,3 @@
contract MissingDefinition =
entrypoint foo : int => int
entrypoint main() = foo(0)

View File

@ -0,0 +1,3 @@
contract MissingInitFunction =
type state = int * int

View File

@ -0,0 +1,4 @@
contract ParameterisedEvent =
datatype event('a) = Event(int)

View File

@ -0,0 +1,4 @@
contract ParameterisedState =
type state('a) = list('a)

View File

@ -0,0 +1,7 @@
contract PolymorphicAENSresolve =
function fail() : option('a) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()

View File

@ -0,0 +1,7 @@
contract PolymorphicCompare =
function cmp(x : 'a, y : 'a) : bool =
x == y
entrypoint test() =
cmp(4, 6) && cmp(true, false)

View File

@ -0,0 +1,3 @@
contract PolymorphicEntrypoint =
entrypoint id(x : 'a) : 'a = x

View File

@ -0,0 +1,3 @@
contract PolymorphicEntrypoint =
entrypoint fail() : 'a = abort("fail")

View File

@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
function foo(m) : t('a) = {[m] = 0}
entrypoint main() = ()

View File

@ -0,0 +1,5 @@
contract PolymorphicQueryType =
stateful function is_oracle(o) =
Oracle.check(o)
entrypoint main() = ()

View File

@ -0,0 +1,5 @@
contract PolymorphicResponseType =
function is_oracle(o : oracle(string, 'r)) =
Oracle.check(o)
entrypoint main(o : oracle(string, int)) = is_oracle(o)

View File

@ -0,0 +1,9 @@
contract Remote =
entrypoint foo : int => int
contract UnappliedContractCall =
function f(r) = r.foo
entrypoint test(r) = f(r)(0)

View File

@ -0,0 +1,5 @@
contract UnappliedNamedArgBuiltin =
// Allowed in FATE, but not AEVM
stateful entrypoint main(s) =
let reg = Oracle.register
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)

View File

@ -0,0 +1,3 @@
contract IndentFail =
entrypoint twoSpace() = ()
entrypoint oneSpace() = ()

View File

@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
type lm = list(map(int, int))
entrypoint foo(m) : t(map(int, int)) = {[m] = 0}
entrypoint bar(m) : t(lm) = Map.delete(m, {})

View File

@ -0,0 +1,3 @@
contract MissingEventType =
entrypoint main() =
Chain.event("MAIN")

View File

@ -0,0 +1,63 @@
// Builtins without named arguments can appear unapplied in both AEVM and FATE.
// Named argument builtins are:
// Oracle.register
// Oracle.respond
// AENS.preclaim
// AENS.claim
// AENS.transfer
// AENS.revoke
// Oracle.extend
contract UnappliedBuiltins =
entrypoint main() = ()
type o = oracle(int, int)
type t = list(int * string)
type m = map(int, int)
datatype event = Event(int)
stateful function chain_spend() = Chain.spend
function chain_event() = Chain.event
function chain_balance() = Chain.balance
function chain_block_hash() = Chain.block_hash
function call_gas_left() = Call.gas_left
function b_abort() = abort
function b_require() = require
function oracle_query_fee() = Oracle.query_fee
stateful function oracle_query() = Oracle.query : (o, _, _, _, _) => _
function oracle_get_question() = Oracle.get_question : (o, _) => _
function oracle_get_answer() = Oracle.get_answer : (o, _) => _
function oracle_check() = Oracle.check : o => _
function oracle_check_query() = Oracle.check_query : (o, _) => _
function aens_resolve() = AENS.resolve : (_, _) => option(string)
function map_lookup() = Map.lookup : (_, m) => _
function map_lookup_default() = Map.lookup_default : (_, m, _) => _
function map_member() = Map.member : (_, m) => _
function map_size() = Map.size : m => _
function map_delete() = Map.delete : (_, m) => _
function map_from_list() = Map.from_list : _ => m
function map_to_list() = Map.to_list : m => _
function crypto_verify_sig() = Crypto.verify_sig
function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1
function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1
function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1
function crypto_sha3() = Crypto.sha3 : t => _
function crypto_sha256() = Crypto.sha256 : t => _
function crypto_blake2b() = Crypto.blake2b : t => _
function string_sha256() = String.sha256
function string_blake2b() = String.blake2b
function string_length() = String.length
function string_concat() = String.concat
function string_sha3() = String.sha3
function bits_test() = Bits.test
function bits_set() = Bits.set
function bits_clear() = Bits.clear
function bits_union() = Bits.union
function bits_intersection() = Bits.intersection
function bits_difference() = Bits.difference
function bits_sum() = Bits.sum
function int_to_str() = Int.to_str
function address_to_str() = Address.to_str
function address_is_oracle() = Address.is_oracle
function address_is_contract() = Address.is_contract
function address_is_payable() = Address.is_payable
function bytes_to_int() = Bytes.to_int : bytes(10) => int
function bytes_to_str() = Bytes.to_str : bytes(99) => string

View File

@ -0,0 +1,4 @@
contract VClose =
entrypoint missing_bracket() =
let x = [1, 2, 3
entrypoint bar() = ()

3
test/contracts/vsemi.aes Normal file
View File

@ -0,0 +1,3 @@
contract VSemi =
record missing_brace = { x : int
entrypoint foo() = ()