Merge pull request #163 from aeternity/fail-on-multiple-contracts

Fail on defined functions in contract prototypes
This commit is contained in:
Ulf Norell 2019-10-01 14:28:06 +02:00 committed by GitHub
commit 32d52f0abc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 22 deletions

View File

@ -110,7 +110,7 @@
, in_pattern = false :: boolean() , in_pattern = false :: boolean()
, stateful = false :: boolean() , stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id() , current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract , what = top :: top | namespace | contract | main_contract
}). }).
-type env() :: #env{}. -type env() :: #env{}.
@ -175,12 +175,13 @@ bind_fun(X, Type, Env) ->
end. end.
-spec force_bind_fun(name(), type() | typesig(), env()) -> env(). -spec force_bind_fun(name(), type() | typesig(), env()) -> env().
force_bind_fun(X, Type, Env) -> force_bind_fun(X, Type, Env = #env{ what = What }) ->
Ann = aeso_syntax:get_ann(Type), Ann = aeso_syntax:get_ann(Type),
NoCode = get_option(no_code, false), NoCode = get_option(no_code, false),
Entry = case X == "init" andalso Env#env.what == contract andalso not NoCode of Entry = if X == "init", What == main_contract, not NoCode ->
true -> {reserved_init, Ann, Type}; {reserved_init, Ann, Type};
false -> {Ann, Type} What == contract -> {contract_fun, Ann, Type};
true -> {Ann, Type}
end, end,
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) -> on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
Scope#scope{ funs = [{X, Entry} | Funs] } Scope#scope{ funs = [{X, Entry} | Funs] }
@ -306,6 +307,9 @@ lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
{reserved_init, Ann1, Type} -> {reserved_init, Ann1, Type} ->
type_error({cannot_call_init_function, Ann}), type_error({cannot_call_init_function, Ann}),
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error {QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
{contract_fun, Ann1, Type} ->
type_error({contract_treated_as_namespace, Ann, QName}),
{QName, {Ann1, Type}};
{Ann1, _} = E -> {Ann1, _} = E ->
%% Check that it's not private (or we can see private funs) %% Check that it's not private (or we can see private funs)
case not is_private(Ann1) orelse AllowPrivate of case not is_private(Ann1) orelse AllowPrivate of
@ -582,7 +586,8 @@ infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) -> infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName), check_scope_name_clash(Env, contract, ConName),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options), What = if Rest == [] -> main_contract; true -> contract end,
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
Contract1 = {contract, Ann, ConName, Code1}, Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1), Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2), Env3 = bind_contract(Contract1, Env2),
@ -605,7 +610,7 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env) destroy_and_report_type_errors(Env)
end. end.
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}. {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, _Options) -> infer_contract_top(Env, Kind, Defs0, _Options) ->
Defs = desugar(Defs0), Defs = desugar(Defs0),
@ -613,7 +618,7 @@ infer_contract_top(Env, Kind, Defs0, _Options) ->
%% 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(), main_contract | contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs) -> infer_contract(Env0, What, Defs) ->
Env = Env0#env{ what = What }, Env = Env0#env{ what = What },
Kind = fun({type_def, _, _, _, _}) -> type; Kind = fun({type_def, _, _, _, _}) -> type;
@ -628,7 +633,8 @@ infer_contract(Env0, What, Defs) ->
Env2 = Env2 =
case What of case What of
namespace -> Env1; namespace -> Env1;
contract -> bind_state(Env1) %% bind state and put contract -> Env1;
main_contract -> bind_state(Env1) %% bind state and put
end, end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]), {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
Env3 = bind_funs(ProtoSigs, Env2), Env3 = bind_funs(ProtoSigs, Env2),
@ -707,19 +713,33 @@ check_unexpected(Xs) ->
check_modifiers(Env, Contracts) -> check_modifiers(Env, Contracts) ->
create_type_errors(), create_type_errors(),
[ case C of check_modifiers_(Env, Contracts),
{contract, _, Con, Decls} -> destroy_and_report_type_errors(Env).
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
IsMain = Rest == [],
check_modifiers1(contract, Decls), check_modifiers1(contract, Decls),
case {lists:keymember(letfun, 1, Decls), case {lists:keymember(letfun, 1, Decls),
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
{true, []} -> type_error({contract_has_no_entrypoints, Con}); {true, []} -> type_error({contract_has_no_entrypoints, Con});
_ -> ok _ when not IsMain ->
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
[] -> ok
end; end;
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls); _ -> ok
{pragma, Ann, Pragma} -> check_pragma(Env, Ann, Pragma); end,
Decl -> type_error({bad_top_level_decl, Decl}) check_modifiers_(Env, Rest);
end || C <- Contracts ], check_modifiers_(Env, [{namespace, _, _, Decls} | Rest]) ->
destroy_and_report_type_errors(Env). check_modifiers1(namespace, Decls),
check_modifiers_(Env, Rest);
check_modifiers_(Env, [{pragma, Ann, Pragma} | Rest]) ->
check_pragma(Env, Ann, Pragma),
check_modifiers_(Env, Rest);
check_modifiers_(Env, [Decl | Rest]) ->
type_error({bad_top_level_decl, Decl}),
check_modifiers_(Env, Rest);
check_modifiers_(_Env, []) -> ok.
-spec check_pragma(env(), aeso_syntax:ann(), aeso_syntax:pragma()) -> ok. -spec check_pragma(env(), aeso_syntax:ann(), aeso_syntax:pragma()) -> ok.
check_pragma(_Env, Ann, {compiler, Op, Ver}) -> check_pragma(_Env, Ann, {compiler, Op, Ver}) ->
@ -2359,6 +2379,10 @@ mk_error({contract_has_no_entrypoints, Con}) ->
"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)]),
mk_t_err(pos(Con), Msg); mk_t_err(pos(Con), Msg);
mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) ->
Msg = "Only the main contract can contain defined functions or entrypoints.\n",
Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({unbound_type, Type}) -> mk_error({unbound_type, Type}) ->
Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]), Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]),
mk_t_err(pos(Type), Msg); mk_t_err(pos(Type), Msg);
@ -2374,6 +2398,10 @@ mk_error({cannot_call_init_function, Ann}) ->
Msg = "The 'init' function is called exclusively by the create contract transaction\n" Msg = "The 'init' function is called exclusively by the create contract transaction\n"
"and cannot be called from the contract code.\n", "and cannot be called from the contract code.\n",
mk_t_err(pos(Ann), Msg); mk_t_err(pos(Ann), Msg);
mk_error({contract_treated_as_namespace, Ann, [Con, Fun] = QName}) ->
Msg = io_lib:format("Invalid call to contract entrypoint '~s'.\n", [string:join(QName, ".")]),
Cxt = io_lib:format("It must be called as 'c.~s' for some c : ~s.\n", [Fun, Con]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({bad_top_level_decl, Decl}) -> mk_error({bad_top_level_decl, Decl}) ->
What = case element(1, Decl) of What = case element(1, Decl) of
letval -> "function or entrypoint"; letval -> "function or entrypoint";

View File

@ -573,6 +573,14 @@ failing_contracts() ->
<<?Pos(2, 1) <<?Pos(2, 1)
"Cannot compile with this version of the compiler,\n" "Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>]) "because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
, ?TYPE_ERROR(multiple_contracts,
[<<?Pos(2, 3)
"Only the main contract can contain defined functions or entrypoints.\n"
"Fix: replace the definition of 'foo' by a type signature.">>])
, ?TYPE_ERROR(contract_as_namespace,
[<<?Pos(5, 28)
"Invalid call to contract entrypoint 'Foo.foo'.\n"
"It must be called as 'c.foo' for some c : Foo.">>])
]. ].
-define(Path(File), "code_errors/" ??File). -define(Path(File), "code_errors/" ??File).

View File

@ -0,0 +1,6 @@
contract Foo =
entrypoint foo : () => int
contract Fail =
entrypoint bad() : int = Foo.foo()

View File

@ -0,0 +1,5 @@
contract ContractOne =
entrypoint foo() = "foo"
contract ContractTwo =
entrypoint bar() = "bar"