Move missing_init_function to the type checker

This commit is contained in:
Gaith Hallak 2022-06-21 19:31:21 +04:00
parent f0c1a96213
commit 9df883b155
5 changed files with 34 additions and 20 deletions

View File

@ -1030,8 +1030,8 @@ infer_contract(Env0, What, Defs0, Options) ->
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Remove namespaces used in the current namespace %% Remove namespaces used in the current namespace
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces }, Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
%% Check that `init` doesn't read or write the state %% Check that `init` doesn't read or write the state and that `init` is not missing
check_state_dependencies(Env4, Defs1), check_state(Env4, Defs1),
destroy_and_report_type_errors(Env4), destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions %% Add inferred types of definitions
{Env5, TypeDefs ++ Decls ++ Defs1}. {Env5, TypeDefs ++ Decls ++ Defs1}.
@ -1623,8 +1623,22 @@ check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id,
end; end;
check_stateful_named_arg(_, _, _) -> ok. check_stateful_named_arg(_, _, _) -> ok.
%% Check that `init` doesn't read or write the state check_state_init(Env) ->
check_state_dependencies(Env, Defs) -> Top = Env#env.namespace,
StateType = lookup_type(Env, {id, [{origin, system}], "state"}),
case unfold_types_in_type(Env, StateType) of
false ->
ok;
{_, {_, {_, {alias_t, {tuple_t, _, []}}}}} ->
ok;
_ ->
#scope{ ann = AnnCon } = get_scope(Env, Top),
type_error({missing_init_function, {con, AnnCon, lists:last(Top)}})
end.
%% Check that `init` doesn't read or write the state and that `init` is defined
%% when the state type is not unit
check_state(Env, Defs) ->
Top = Env#env.namespace, Top = Env#env.namespace,
GetState = Top ++ ["state"], GetState = Top ++ ["state"],
SetState = Top ++ ["put"], SetState = Top ++ ["put"],
@ -1633,7 +1647,7 @@ check_state_dependencies(Env, Defs) ->
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ], Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]), Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
case maps:get(Init, Deps, false) of case maps:get(Init, Deps, false) of
false -> ok; %% No init, so nothing to check false -> get_option(no_code, false) orelse check_state_init(Env);
_ -> _ ->
[ type_error({init_depends_on_state, state, Chain}) [ type_error({init_depends_on_state, state, Chain})
|| Chain <- get_call_chains(Deps, Init, GetState) ], || Chain <- get_call_chains(Deps, Init, GetState) ],
@ -3504,6 +3518,10 @@ mk_error({parameterized_state, Ann}) ->
mk_error({parameterized_event, Ann}) -> mk_error({parameterized_event, Ann}) ->
Msg = "The event type cannot be parameterized", Msg = "The event type cannot be parameterized",
mk_t_err(pos(Ann), Msg); mk_t_err(pos(Ann), Msg);
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(Err) -> mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p", [Err]), Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg). mk_t_err(pos(0, 0), Msg).

View File

@ -326,7 +326,7 @@ get_option(Opt, Env, Default) ->
%% -- Compilation ------------------------------------------------------------ %% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}. -spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest]) to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
when ?IS_CONTRACT_HEAD(Contract) -> when ?IS_CONTRACT_HEAD(Contract) ->
case Contract =:= contract_interface of case Contract =:= contract_interface of
false -> false ->
@ -349,7 +349,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
event_type => EventType, event_type => EventType,
payable => Payable, payable => Payable,
functions => add_init_function( functions => add_init_function(
Env1, Con, StateType, Env1,
add_event_function(Env1, EventType, Funs)) }, add_event_function(Env1, EventType, Funs)) },
case Contract of case Contract of
contract_main -> [] = Rest, {Env1, ConFcode}; contract_main -> [] = Rest, {Env1, ConFcode};
@ -1189,11 +1189,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
%% -- Init function -- %% -- Init function --
add_init_function(Env, Main, StateType, Funs0) -> add_init_function(Env, 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, Main, StateType, Funs0), Funs = add_default_init_function(Env, Funs0),
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
InitFun = #{ body := InitBody} = maps:get(InitName, Funs), InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []}, Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
@ -1201,16 +1201,14 @@ add_init_function(Env, Main, StateType, Funs0) ->
Funs1 Funs1
end. end.
add_default_init_function(_Env, Main, StateType, Funs) -> add_default_init_function(_Env, 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. none ->
none when StateType == {tuple, []} ->
Funs#{ InitName => #{attrs => [], Funs#{ InitName => #{attrs => [],
args => [], args => [],
return => {tuple, []}, return => {tuple, []},
body => {tuple, []}} }; body => {tuple, []}} };
none -> fcode_error({missing_init_function, Main});
_ -> Funs _ -> Funs
end. end.

View File

@ -14,10 +14,6 @@ format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'", Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'",
[Kind, C]), [Kind, C]),
mk_err(pos(Decl), Msg); mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.",
mk_err(pos(Con), Msg, Cxt);
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) -> format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
What = case Why of higher_order -> "higher-order (contains function types)"; What = case Why of higher_order -> "higher-order (contains function types)";
polymorphic -> "polymorphic (contains type variables)" end, polymorphic -> "polymorphic (contains type variables)" end,

View File

@ -1035,6 +1035,11 @@ failing_contracts() ->
[<<?Pos(3,12) [<<?Pos(3,12)
"The event type cannot be parameterized">> "The event type cannot be parameterized">>
]) ])
, ?TYPE_ERROR(missing_init_function,
[<<?Pos(1,10)
"Missing `init` function for the contract `MissingInitFunction`.\n"
"The `init` function can only be omitted if the state type is `unit`">>
])
]. ].
-define(Path(File), "code_errors/" ??File). -define(Path(File), "code_errors/" ??File).
@ -1051,9 +1056,6 @@ failing_code_gen_contracts() ->
"The return type\n" "The return type\n"
" (int) => int\n" " (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).") "of entrypoint 'add' is higher-order (contains function types).")
, ?FATE_ERR(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'.")
, ?FATE_ERR(polymorphic_aens_resolve, 4, 5, , ?FATE_ERR(polymorphic_aens_resolve, 4, 5,
"Invalid return type of AENS.resolve:\n" "Invalid return type of AENS.resolve:\n"
" 'a\n" " 'a\n"