From 2d6d506d632c6c29f8ae05bae4b71819165a7ec8 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 1 Oct 2019 13:13:27 +0200 Subject: [PATCH 1/2] Fail on function definitions in contracts other than the main contract --- src/aeso_ast_infer_types.erl | 42 +++++++++++++++++++-------- test/aeso_compiler_tests.erl | 4 +++ test/contracts/multiple_contracts.aes | 5 ++++ 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 test/contracts/multiple_contracts.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index a55ef69..0e6b3e9 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -707,20 +707,34 @@ check_unexpected(Xs) -> check_modifiers(Env, Contracts) -> create_type_errors(), - [ case C of - {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 ], + 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}); + _ 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. check_pragma(_Env, Ann, {compiler, Op, Ver}) -> case aeso_compiler:numeric_version() of @@ -2359,6 +2373,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); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 78d4a96..c2036d2 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -573,6 +573,10 @@ failing_contracts() -> <>]) + , ?TYPE_ERROR(multiple_contracts, + [<>]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/multiple_contracts.aes b/test/contracts/multiple_contracts.aes new file mode 100644 index 0000000..41df9b4 --- /dev/null +++ b/test/contracts/multiple_contracts.aes @@ -0,0 +1,5 @@ +contract ContractOne = + entrypoint foo() = "foo" + +contract ContractTwo = + entrypoint bar() = "bar" From 5e6ff6c9a742d8378522f6fe7c8a54a9b9d701cc Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 1 Oct 2019 13:40:01 +0200 Subject: [PATCH 2/2] Nice type error if contract function is called as from a namespace --- src/aeso_ast_infer_types.erl | 30 ++++++++++++++++-------- test/aeso_compiler_tests.erl | 4 ++++ test/contracts/contract_as_namespace.aes | 6 +++++ 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 test/contracts/contract_as_namespace.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0e6b3e9..bd53284 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -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; @@ -627,8 +632,9 @@ infer_contract(Env0, What, Defs) -> check_unexpected(Get(unexpected)), Env2 = case What of - namespace -> Env1; - contract -> bind_state(Env1) %% bind state and put + namespace -> Env1; + 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), @@ -2392,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"; diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index c2036d2..a2f04ed 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -577,6 +577,10 @@ failing_contracts() -> [<>]) + , ?TYPE_ERROR(contract_as_namespace, + [<>]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/contract_as_namespace.aes b/test/contracts/contract_as_namespace.aes new file mode 100644 index 0000000..489dfda --- /dev/null +++ b/test/contracts/contract_as_namespace.aes @@ -0,0 +1,6 @@ +contract Foo = + entrypoint foo : () => int + +contract Fail = + entrypoint bad() : int = Foo.foo() +