Merge pull request #163 from aeternity/fail-on-multiple-contracts
Fail on defined functions in contract prototypes
This commit is contained in:
commit
32d52f0abc
@ -110,7 +110,7 @@
|
||||
, in_pattern = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract
|
||||
, what = top :: top | namespace | contract | main_contract
|
||||
}).
|
||||
|
||||
-type env() :: #env{}.
|
||||
@ -175,12 +175,13 @@ bind_fun(X, Type, Env) ->
|
||||
end.
|
||||
|
||||
-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),
|
||||
NoCode = get_option(no_code, false),
|
||||
Entry = case X == "init" andalso Env#env.what == contract andalso not NoCode of
|
||||
true -> {reserved_init, Ann, Type};
|
||||
false -> {Ann, Type}
|
||||
Entry = if X == "init", What == main_contract, not NoCode ->
|
||||
{reserved_init, Ann, Type};
|
||||
What == contract -> {contract_fun, Ann, Type};
|
||||
true -> {Ann, Type}
|
||||
end,
|
||||
on_current_scope(Env, fun(Scope = #scope{ funs = 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} ->
|
||||
type_error({cannot_call_init_function, Ann}),
|
||||
{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 ->
|
||||
%% Check that it's not private (or we can see private funs)
|
||||
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) ->
|
||||
%% do type inference on each contract independently.
|
||||
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},
|
||||
Env2 = pop_scope(Env1),
|
||||
Env3 = bind_contract(Contract1, Env2),
|
||||
@ -605,7 +610,7 @@ check_scope_name_clash(Env, Kind, Name) ->
|
||||
destroy_and_report_type_errors(Env)
|
||||
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()]}.
|
||||
infer_contract_top(Env, Kind, Defs0, _Options) ->
|
||||
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
|
||||
%% 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) ->
|
||||
Env = Env0#env{ what = What },
|
||||
Kind = fun({type_def, _, _, _, _}) -> type;
|
||||
@ -628,7 +633,8 @@ infer_contract(Env0, What, Defs) ->
|
||||
Env2 =
|
||||
case What of
|
||||
namespace -> Env1;
|
||||
contract -> bind_state(Env1) %% bind state and put
|
||||
contract -> Env1;
|
||||
main_contract -> bind_state(Env1) %% bind state and put
|
||||
end,
|
||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
|
||||
Env3 = bind_funs(ProtoSigs, Env2),
|
||||
@ -707,19 +713,33 @@ check_unexpected(Xs) ->
|
||||
|
||||
check_modifiers(Env, Contracts) ->
|
||||
create_type_errors(),
|
||||
[ case C of
|
||||
{contract, _, Con, Decls} ->
|
||||
check_modifiers_(Env, Contracts),
|
||||
destroy_and_report_type_errors(Env).
|
||||
|
||||
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
|
||||
IsMain = Rest == [],
|
||||
check_modifiers1(contract, Decls),
|
||||
case {lists:keymember(letfun, 1, Decls),
|
||||
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
||||
{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;
|
||||
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls);
|
||||
{pragma, Ann, Pragma} -> check_pragma(Env, Ann, Pragma);
|
||||
Decl -> type_error({bad_top_level_decl, Decl})
|
||||
end || C <- Contracts ],
|
||||
destroy_and_report_type_errors(Env).
|
||||
_ -> ok
|
||||
end,
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [{namespace, _, _, Decls} | Rest]) ->
|
||||
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.
|
||||
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"
|
||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
||||
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}) ->
|
||||
Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]),
|
||||
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"
|
||||
"and cannot be called from the contract code.\n",
|
||||
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}) ->
|
||||
What = case element(1, Decl) of
|
||||
letval -> "function or entrypoint";
|
||||
|
@ -573,6 +573,14 @@ failing_contracts() ->
|
||||
<<?Pos(2, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"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).
|
||||
|
6
test/contracts/contract_as_namespace.aes
Normal file
6
test/contracts/contract_as_namespace.aes
Normal file
@ -0,0 +1,6 @@
|
||||
contract Foo =
|
||||
entrypoint foo : () => int
|
||||
|
||||
contract Fail =
|
||||
entrypoint bad() : int = Foo.foo()
|
||||
|
5
test/contracts/multiple_contracts.aes
Normal file
5
test/contracts/multiple_contracts.aes
Normal file
@ -0,0 +1,5 @@
|
||||
contract ContractOne =
|
||||
entrypoint foo() = "foo"
|
||||
|
||||
contract ContractTwo =
|
||||
entrypoint bar() = "bar"
|
Loading…
x
Reference in New Issue
Block a user