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()
|
, 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;
|
||||||
@ -627,8 +632,9 @@ infer_contract(Env0, What, Defs) ->
|
|||||||
check_unexpected(Get(unexpected)),
|
check_unexpected(Get(unexpected)),
|
||||||
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,20 +713,34 @@ 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} ->
|
|
||||||
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
|
|
||||||
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).
|
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});
|
||||||
|
_ when not IsMain ->
|
||||||
|
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
|
||||||
|
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
|
||||||
|
[] -> ok
|
||||||
|
end;
|
||||||
|
_ -> 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.
|
-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}) ->
|
||||||
case aeso_compiler:numeric_version() of
|
case aeso_compiler:numeric_version() of
|
||||||
@ -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";
|
||||||
|
@ -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).
|
||||||
|
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