Move the higher order entrypoint error to type checker

This commit is contained in:
Gaith Hallak 2022-06-22 17:08:01 +04:00
parent 4645b279fa
commit e5e1f0212d
6 changed files with 50 additions and 42 deletions

View File

@ -1032,6 +1032,8 @@ infer_contract(Env0, What, Defs0, Options) ->
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
%% Check that `init` doesn't read or write the state and that `init` is not missing
check_state(Env4, Defs1),
%% Check that entrypoints have first-order arg types and return types
check_entrypoints(Defs1),
destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions
{Env5, TypeDefs ++ Decls ++ Defs1}.
@ -1623,6 +1625,27 @@ check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id,
end;
check_stateful_named_arg(_, _, _) -> ok.
check_entrypoints(Defs) ->
[ ensure_first_order_entrypoint(LetFun)
|| LetFun <- Defs,
aeso_syntax:get_ann(entrypoint, LetFun, false) ].
ensure_first_order_entrypoint({letfun, Ann, Id = {id, _, Name}, Args, Ret, _}) ->
[ ensure_first_order(ArgType, {higher_order_entrypoint, AnnArg, Id, {argument, ArgId, ArgType}})
|| {typed, AnnArg, ArgId, ArgType} <- Args ],
[ ensure_first_order(Ret, {higher_order_entrypoint, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok.
ensure_first_order(Type, Err) ->
is_first_order(Type) orelse type_error(Err).
is_first_order({fun_t, _, _, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
check_state_init(Env) ->
Top = Env#env.namespace,
StateType = lookup_type(Env, {id, [{origin, system}], "state"}),
@ -3522,6 +3545,19 @@ mk_error({missing_init_function, Con}) ->
Msg = io_lib:format("Missing `init` function for the contract `~s`.", [name(Con)]),
Cxt = "The `init` function can only be omitted if the state type is `unit`",
mk_t_err(pos(Con), Msg, Cxt);
mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) ->
What = "higher-order (contains function types)",
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(" `", 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",
[ThingS, Name, Bad]),
mk_t_err(pos(Ann), Msg);
mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg).

View File

@ -378,14 +378,12 @@ decls_to_fcode(Env, Decls) ->
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs,
args => FArgs,
return => FRet,
@ -823,14 +821,6 @@ validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]})
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
end.
ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
[ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|| {{typed, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
[ 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.
ensure_monomorphic(Type, Err) ->
case is_monomorphic(Type) of
true -> ok;

View File

@ -10,22 +10,6 @@
-export([format/1, pos/1]).
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.",
[ThingS, Name, Bad]),
case Why of
higher_order -> mk_err(pos(Ann), Msg)
end;
format({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
"~s\n"
@ -52,12 +36,6 @@ pos(Ann) ->
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).

View File

@ -1046,6 +1046,18 @@ failing_contracts() ->
"Missing `init` function for the contract `AliasToAliasToType`.\n"
"The `init` function can only be omitted if the state type is `unit`">>
])
, ?TYPE_ERROR(higher_order_entrypoint,
[<<?Pos(2,20)
"The argument\n"
" `f : (int) => int`\n"
"of entrypoint `apply` has a higher-order (contains function types) type">>
])
, ?TYPE_ERROR(higher_order_entrypoint_return,
[<<?Pos(2,3)
"The return type\n"
" `(int) => int`\n"
"of entrypoint `add` is higher-order (contains function types)">>
])
].
-define(Path(File), "code_errors/" ??File).
@ -1054,15 +1066,7 @@ failing_contracts() ->
-define(FATE_ERR(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
failing_code_gen_contracts() ->
[ ?FATE_ERR(higher_order_entrypoint, 2, 20,
"The argument\n"
" f : (int) => int\n"
"of entrypoint 'apply' has a higher-order (contains function types) type.")
, ?FATE_ERR(higher_order_entrypoint_return, 2, 3,
"The return type\n"
" (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).")
, ?FATE_ERR(polymorphic_aens_resolve, 4, 5,
[ ?FATE_ERR(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).")