From 79137e058eab4bbbdb147cfdfa24b001a7fcef47 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Thu, 27 Jun 2019 12:10:25 +0200 Subject: [PATCH] Revamp private/public functions Problem: having public as the default makes it very easy to accidentally export local function by forgetting the `private` modifier. Solution: functions are private by default and must be declared as `entrypoint`s to be exported. So `entrypoint foo() = ...` instead of `function foo() = ...`. We still accept the `private` modifier although it is redundant. --- src/aeso_ast_infer_types.erl | 74 +++++++++++++++++++++++++++++++++++- src/aeso_ast_to_fcode.erl | 9 ++--- src/aeso_ast_to_icode.erl | 8 ++-- src/aeso_compiler.erl | 4 +- src/aeso_parser.erl | 21 +++++++--- src/aeso_pretty.erl | 16 ++++++-- src/aeso_scan.erl | 2 +- 7 files changed, 110 insertions(+), 24 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 54da3f8..0eb965e 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -538,6 +538,7 @@ infer(Contracts, Options) -> Env = init_env(Options), create_options(Options), ets_new(type_vars, [set]), + check_modifiers(Env, Contracts), {Env1, Decls} = infer1(Env, Contracts, [], Options), {Env2, Decls2} = case proplists:get_value(dont_unfold, Options, false) of @@ -685,6 +686,40 @@ check_typedef(Env, {variant_t, Cons}) -> check_unexpected(Xs) -> [ type_error(X) || X <- 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) + end || C <- Contracts ], + destroy_and_report_type_errors(Env). + +-spec check_modifiers1(contract | namespace, [aeso_syntax:decl()] | aeso_syntax:decl()) -> ok. +check_modifiers1(What, Decls) when is_list(Decls) -> + _ = [ check_modifiers1(What, Decl) || Decl <- Decls ], + ok; +check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) == fun_decl -> + Public = aeso_syntax:get_ann(public, Decl, false), + Private = aeso_syntax:get_ann(private, Decl, false), + Entrypoint = aeso_syntax:get_ann(entrypoint, Decl, false), + FunDecl = element(1, Decl) == fun_decl, + {id, _, Name} = element(3, Decl), + _ = [ type_error({proto_must_be_entrypoint, Decl}) || FunDecl, Private orelse not Entrypoint, What == contract ], + _ = [ type_error({proto_in_namespace, Decl}) || FunDecl, What == namespace ], + _ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, Name == "init", What == contract ], + _ = [ type_error({public_modifier_in_contract, Decl}) || Public, not Private, not Entrypoint, What == contract ], + _ = [ type_error({entrypoint_in_namespace, Decl}) || Entrypoint, What == namespace ], + _ = [ type_error({private_entrypoint, Decl}) || Private, Entrypoint ], + _ = [ type_error({private_and_public, Decl}) || Private, Public ], + ok; +check_modifiers1(_, _) -> ok. + -spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type(). check_type(Env, T) -> check_type(Env, T, 0). @@ -2070,7 +2105,7 @@ pp_error({duplicate_definition, Name, Locs}) -> pp_error({duplicate_scope, Kind, Name, OtherKind, L}) -> io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]); -pp_error({include, {string, Pos, Name}}) -> +pp_error({include, _, {string, Pos, Name}}) -> io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", [binary_to_list(Name), pp_loc(Pos)]); pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> @@ -2093,9 +2128,46 @@ pp_error({init_depends_on_state, Which, [_Init | Chain]}) -> || {[_, Fun], Ann} <- Chain]]); pp_error({missing_body_for_let, Ann}) -> io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]); +pp_error({public_modifier_in_contract, Decl}) -> + Decl1 = mk_entrypoint(Decl), + io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl), + prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); +pp_error({init_must_be_an_entrypoint, Decl}) -> + Decl1 = mk_entrypoint(Decl), + io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n", + [pp_loc(Decl), + prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); +pp_error({proto_must_be_entrypoint, Decl}) -> + Decl1 = mk_entrypoint(Decl), + io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl), + prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]); +pp_error({proto_in_namespace, Decl}) -> + io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n", + [pp_loc(Decl)]); +pp_error({entrypoint_in_namespace, Decl}) -> + io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n", + [pp_loc(Decl)]); +pp_error({private_entrypoint, Decl}) -> + io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl)]); +pp_error({private_and_public, Decl}) -> + io_lib:format("The function ~s (at ~s) cannot be both public and private.\n", + [pp_expr("", element(3, Decl)), pp_loc(Decl)]); +pp_error({contract_has_no_entrypoints, Con}) -> + io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n" + "contract functions must be declared with the 'entrypoint' keyword instead of\n" + "'function'.\n", [pp_expr("", Con), pp_loc(Con)]); pp_error(Err) -> io_lib:format("Unknown error: ~p\n", [Err]). +mk_entrypoint(Decl) -> + Ann = [entrypoint | lists:keydelete(public, 1, + lists:keydelete(private, 1, + aeso_syntax:get_ann(Decl))) -- [public, private]], + aeso_syntax:set_ann(Ann, Decl). + pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]); pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]); pp_when({check_typesig, Name, Inferred, Given}) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 16b0309..bc195e0 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -1006,13 +1006,12 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> Env#{ fun_env := maps:merge(FunEnv, FunEnv1) }. make_fun_name(#{ context := Context }, Ann, Name) -> - Private = proplists:get_value(private, Ann, false) orelse - proplists:get_value(internal, Ann, false), + Entrypoint = proplists:get_value(entrypoint, Ann, false), case Context of {main_contract, Main} -> - if Private -> {local_fun, [Main, Name]}; - Name == "init" -> init; - true -> {entrypoint, list_to_binary(Name)} + if Name == "init" -> init; + Entrypoint -> {entrypoint, list_to_binary(Name)}; + true -> {local_fun, [Main, Name]} end; {namespace, Lib} -> {local_fun, [Lib, Name]} diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index c495d2f..87dacfb 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -817,13 +817,11 @@ has_maps({list, T}) -> has_maps(T); has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). -%% A function is private if marked 'private' or 'internal', or if it's not -%% defined in the main contract name space. (NOTE: changes when we introduce -%% inheritance). +%% A function is private if not an 'entrypoint', or if it's not defined in the +%% main contract name space. (NOTE: changes when we introduce inheritance). is_private(Ann, #{ contract_name := MainContract } = Icode) -> {_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode), - proplists:get_value(private, Ann, false) orelse - proplists:get_value(internal, Ann, false) orelse + not proplists:get_value(entrypoint, Ann, false) orelse MainContract /= CurrentNamespace. %% ------------------------------------------------------------------- diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index d355570..8f7ec6e 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -253,7 +253,7 @@ insert_call_function(Code, Call, FunName, Args, Options) -> [ Code, "\n\n", lists:duplicate(Ind, " "), - "stateful function ", Call,"() = ", FunName, "(", string:join(Args, ","), ")\n" + "stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n" ]). -spec insert_init_function(string(), options()) -> string(). @@ -263,7 +263,7 @@ insert_init_function(Code, Options) -> lists:flatten( [ Code, "\n\n", - lists:duplicate(Ind, " "), "function init() = ()\n" + lists:duplicate(Ind, " "), "entrypoint init() = ()\n" ]). last_contract_indent(Decls) -> diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 1e1ae01..5e94269 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -47,7 +47,7 @@ decl() -> %% Contract declaration [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) - , ?RULE(keyword(include), str(), {include, _2}) + , ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) %% Type declarations TODO: format annotation for "type bla" vs "type bla()" , ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) @@ -60,13 +60,22 @@ decl() -> , ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5}) %% Function declarations - , ?RULE(modifiers(), keyword(function), id(), tok(':'), type(), add_modifiers(_1, {fun_decl, _2, _3, _5})) - , ?RULE(modifiers(), keyword(function), fundef(), add_modifiers(_1, set_pos(get_pos(_2), _3))) - , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) + , ?RULE(modifiers(), fun_or_entry(), id(), tok(':'), type(), add_modifiers(_1, _2, {fun_decl, get_ann(_2), _3, _5})) + , ?RULE(modifiers(), fun_or_entry(), fundef(), add_modifiers(_1, _2, set_pos(get_pos(get_ann(_2)), _3))) + , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) ])). +fun_or_entry() -> + choice([?RULE(keyword(function), {function, _1}), + ?RULE(keyword(entrypoint), {entrypoint, _1})]). + modifiers() -> - many(choice([token(stateful), token(public), token(private), token(internal)])). + many(choice([token(stateful), token(private), token(public)])). + +add_modifiers(Mods, Entry = {entrypoint, _}, Node) -> + add_modifiers(Mods ++ [Entry], Node); +add_modifiers(Mods, {function, _}, Node) -> + add_modifiers(Mods, Node). add_modifiers([], Node) -> Node; add_modifiers(Mods = [Tok | _], Node) -> @@ -513,7 +522,7 @@ expand_includes(AST, Opts) -> expand_includes([], Acc, _Opts) -> {ok, lists:reverse(Acc)}; -expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) -> +expand_includes([{include, _, S = {string, _, File}} | AST], Acc, Opts) -> case read_file(File, Opts) of {ok, Bin} -> Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 5e6314f..9db524d 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -153,13 +153,21 @@ decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_def, _, T, Vars, Def}) -> Kind = element(1, Def), equals(typedecl(Kind, T, Vars), typedef(Def)); -decl({fun_decl, _, F, T}) -> - hsep(text("function"), typed(name(F), T)); +decl({fun_decl, Ann, F, T}) -> + Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of + true -> text("entrypoint"); + false -> text("function") + end, + hsep(Fun, typed(name(F), T)); decl(D = {letfun, Attrs, _, _, _, _}) -> - Mod = fun({Mod, true}) when Mod == private; Mod == internal; Mod == public; Mod == stateful -> + Mod = fun({Mod, true}) when Mod == private; Mod == stateful -> text(atom_to_list(Mod)); (_) -> empty() end, - hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]); + Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of + true -> "entrypoint"; + false -> "function" + end, + hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]); decl(D = {letval, _, _, _, _}) -> letdecl("let", D). -spec expr(aeso_syntax:expr(), options()) -> doc(). diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index e8dc7e1..400a5cf 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -37,7 +37,7 @@ lexer() -> , {"[^/*]+|[/*]", skip()} ], Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", - "stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"], + "stateful", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], KW = string:join(Keywords, "|"), Rules =