From 49f73f8201e630f2ee693759580fa83081c69170 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 15 Dec 2022 14:16:51 +0300 Subject: [PATCH 01/37] Allow compile-time constants as toplevel declarations --- src/aeso_ast_infer_types.erl | 143 +++++++++++++++++++++++++---------- src/aeso_ast_to_fcode.erl | 20 ++++- 2 files changed, 120 insertions(+), 43 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ac0ef42..d8f37df 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -124,15 +124,18 @@ -type variance() :: invariant | covariant | contravariant | bivariant. --type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. --type type_info() :: {aeso_syntax:ann(), typedef()}. --type var_info() :: {aeso_syntax:ann(), utype()}. +-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. +-type type_info() :: {aeso_syntax:ann(), typedef()}. +-type const_info() :: {aeso_syntax:ann(), type()}. +-type var_info() :: {aeso_syntax:ann(), utype()}. --type fun_env() :: [{name(), fun_info()}]. --type type_env() :: [{name(), type_info()}]. +-type fun_env() :: [{name(), fun_info()}]. +-type type_env() :: [{name(), type_info()}]. +-type const_env() :: [{name(), const_info()}]. -record(scope, { funs = [] :: fun_env() , types = [] :: type_env() + , consts = [] :: const_env() , access = public :: access() , kind = namespace :: namespace | contract , ann = [{origin, system}] :: aeso_syntax:ann() @@ -152,6 +155,7 @@ , in_guard = false :: boolean() , stateful = false :: boolean() , unify_throws = true :: boolean() + , current_const = none :: none | aeso_syntax:id() , current_function = none :: none | aeso_syntax:id() , what = top :: top | namespace | contract | contract_interface }). @@ -247,6 +251,37 @@ bind_type(X, Ann, Def, Env) -> Scope#scope{ types = [{X, {Ann, Def}} | Types] } end). +-spec bind_const(name(), aeso_syntax:ann(), type(), env()) -> env(). +bind_const(X, Ann, Type, Env) -> + case lookup_env(Env, term, Ann, [X]) of + false -> + on_current_scope(Env, fun(Scope = #scope{ consts = Consts }) -> + Scope#scope{ consts = [{X, {Ann, Type}} | Consts] } + end); + _ -> + type_error({duplicate_definition, X, [Ann, aeso_syntax:get_ann(Type)]}), + Env + end. + +-spec bind_consts(env(), #{ name() => aeso_syntax:decl() }, [{acyclic, name()} | {cyclic, [name()]}], [aeso_syntax:decl()]) -> + {env(), [aeso_syntax:decl()]}. +bind_consts(Env, _Consts, [], Acc) -> + {Env, lists:reverse(Acc)}; +bind_consts(Env, Consts, [{cyclic, Xs} | _SCCs], _Acc) -> + ConstDecls = [ maps:get(X, Consts) || X <- Xs ], + type_error({mutually_recursive_constants, lists:reverse(ConstDecls)}), + {Env, []}; +bind_consts(Env, Consts, [{acyclic, X} | SCCs], Acc) -> + case maps:get(X, Consts, undefined) of + Const = {letval, Ann, Id, _} -> + NewConst = {letval, _, {typed, _, _, Type}, _} = infer_const(Env, Const), + NewEnv = bind_const(name(Id), Ann, Type, Env), + bind_consts(NewEnv, Consts, SCCs, [NewConst | Acc]); + undefined -> + %% When a used id is not a letval, a type error will be thrown + bind_consts(Env, Consts, SCCs, Acc) + end. + %% Bind state primitives -spec bind_state(env()) -> env(). bind_state(Env) -> @@ -431,10 +466,10 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes %% Get the scope case maps:get(Qual, Scopes, false) of false -> false; %% TODO: return reason for not in scope - #scope{ funs = Funs, types = Types } -> + #scope{ funs = Funs, types = Types, consts = Consts } -> Defs = case Kind of type -> Types; - term -> Funs + term -> Funs ++ Consts end, %% Look up the unqualified name case proplists:get_value(Name, Defs, false) of @@ -1049,12 +1084,13 @@ infer_contract(Env0, What, Defs0, Options) -> end, destroy_and_report_type_errors(Env0), Env = Env0#env{ what = What }, - Kind = fun({type_def, _, _, _, _}) -> type; - ({letfun, _, _, _, _, _}) -> function; - ({fun_clauses, _, _, _, _}) -> function; - ({fun_decl, _, _, _}) -> prototype; - ({using, _, _, _, _}) -> using; - (_) -> unexpected + Kind = fun({type_def, _, _, _, _}) -> type; + ({letfun, _, _, _, _, _}) -> function; + ({fun_clauses, _, _, _, _}) -> function; + ({fun_decl, _, _, _}) -> prototype; + ({using, _, _, _, _}) -> using; + ({letval, _, {id, _, _}, _}) -> constant; + (_) -> unexpected end, Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end, OldUsedNamespaces = Env#env.used_namespaces, @@ -1069,11 +1105,12 @@ infer_contract(Env0, What, Defs0, Options) -> contract_interface -> Env1; contract -> bind_state(Env1) %% bind state and put end, - {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]), + {Env2C, Consts} = check_constants(Env2, Get(constant, Defs)), + {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env2C, Decl) || Decl <- Get(prototype, Defs) ]), [ type_error({missing_definition, Id}) || {fun_decl, _, Id, _} <- Decls, What =:= contract, get_option(no_code, false) =:= false ], - Env3 = bind_funs(ProtoSigs, Env2), + Env3 = bind_funs(ProtoSigs, Env2C), Functions = Get(function, Defs), %% Check for duplicates in Functions (we turn it into a map below) FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; @@ -1093,7 +1130,7 @@ infer_contract(Env0, What, Defs0, Options) -> check_entrypoints(Defs1), destroy_and_report_type_errors(Env4), %% Add inferred types of definitions - {Env5, TypeDefs ++ Decls ++ Defs1}. + {Env5, TypeDefs ++ Decls ++ Consts ++ Defs1}. %% Restructure blocks into multi-clause fundefs (`fun_clauses`). -spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()]. @@ -1243,6 +1280,13 @@ opposite_variance(covariant) -> contravariant; opposite_variance(contravariant) -> covariant; opposite_variance(bivariant) -> bivariant. +-spec check_constants(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. +check_constants(Env, Consts) -> + ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- Consts ]), + DepGraph = maps:map(fun(_, Const) -> aeso_syntax_utils:used_ids(Const) end, ConstMap), + SCCs = aeso_utils:scc(DepGraph), + bind_consts(Env, ConstMap, SCCs, []). + check_usings(Env, []) -> Env; check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) -> @@ -1861,10 +1905,10 @@ infer_expr(Env, {list, As, Elems}) -> ElemType = fresh_uvar(As), NewElems = [check_expr(Env, X, ElemType) || X <- Elems], {typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}}; -infer_expr(Env, {list_comp, As, Yield, []}) -> +infer_expr(Env = #env{ current_const = none }, {list_comp, As, Yield, []}) -> {typed, _, _, Type} = TypedYield = infer_expr(Env, Yield), {typed, As, {list_comp, As, TypedYield, []}, {app_t, As, {id, As, "list"}, [Type]}}; -infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) -> +infer_expr(Env = #env{ current_const = none }, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) -> TypedBind = {typed, As2, _, TypeBExpr} = infer_expr(Env, BExpr), {NewE, TypedPat = {typed, _, _, PatType}} = infer_pattern(Env, Pat), unify( Env @@ -1877,7 +1921,7 @@ infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) , As , {list_comp, As, TypedYield, [{comprehension_bind, TypedPat, TypedBind}|TypedRest]} , ResType}; -infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Rest]}) -> +infer_expr(Env = #env{ current_const = none }, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Rest]}) -> NewCond = check_expr(Env, Cond, {id, AttrsIF, "bool"}), {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = infer_expr(Env, {list_comp, AttrsL, Yield, Rest}), @@ -1885,7 +1929,7 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re , AttrsL , {list_comp, AttrsL, TypedYield, [{comprehension_if, AttrsIF, NewCond}|TypedRest]} , ResType}; -infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> +infer_expr(Env = #env{ current_const = none }, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> NewE = {typed, _, _, PatType} = infer_expr(Env, E), BlockType = fresh_uvar(AsLV), {'case', _, NewPattern, [{guarded, _, [], NewRest}]} = @@ -1901,7 +1945,7 @@ infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> , {list_comp, AsLC, TypedYield, [{letval, AsLV, NewPattern, NewE}|TypedRest]} , ResType }; -infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> +infer_expr(Env = #env{ current_const = none }, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, AsLF, Name}, FunT, Env), @@ -1916,13 +1960,16 @@ infer_expr(Env, {typed, As, Body, Type}) -> Type1 = check_type(Env, Type), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, As, NewBody, NewType}; -infer_expr(Env, {app, Ann, Fun, Args0} = App) -> +infer_expr(Env = #env{ current_const = CurrentConst }, {app, Ann, Fun, Args0} = App) -> {NamedArgs, Args} = split_args(Args0), case aeso_syntax:get_ann(format, Ann) of infix -> infer_op(Env, Ann, Fun, Args, fun infer_infix/1); prefix -> infer_op(Env, Ann, Fun, Args, fun infer_prefix/1); + _ when CurrentConst =/= none -> + type_error({invalid_const_expr, CurrentConst}), + destroy_and_report_type_errors(Env); _ -> NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], @@ -1948,19 +1995,19 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> context = {check_return, App} }), {typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)} end; -infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> +infer_expr(Env = #env{ current_const = none }, {'if', Attrs, Cond, Then, Else}) -> NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}), NewThen = {typed, _, _, ThenType} = infer_expr(Env, Then), NewElse = {typed, _, _, ElseType} = infer_expr(Env, Else), unify(Env, ThenType, ElseType, {if_branches, Then, ThenType, Else, ElseType}), {typed, Attrs, {'if', Attrs, NewCond, NewThen, NewElse}, ThenType}; -infer_expr(Env, {switch, Attrs, Expr, Cases}) -> +infer_expr(Env = #env{ current_const = none }, {switch, Attrs, Expr, Cases}) -> NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr), SwitchType = fresh_uvar(Attrs), NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType) || {'case', As, Pattern, GuardedBranches} <- Cases], {typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType}; -infer_expr(Env, {record, Attrs, Fields}) -> +infer_expr(Env = #env{ current_const = none }, {record, Attrs, Fields}) -> RecordType = fresh_uvar(Attrs), NewFields = [{field, A, FieldName, infer_expr(Env, Expr)} || {field, A, FieldName, Expr} <- Fields], @@ -1979,11 +2026,11 @@ infer_expr(Env, {record, Attrs, Fields}) -> context = Fld} end || {Fld, {field, _, LV, {typed, _, _, T}}} <- lists:zip(Fields, NewFields)]), {typed, Attrs, {record, Attrs, NewFields}, RecordType}; -infer_expr(Env, {record, Attrs, Record, Update}) -> +infer_expr(Env = #env{ current_const = none }, {record, Attrs, Record, Update}) -> NewRecord = {typed, _, _, RecordType} = infer_expr(Env, Record), NewUpdate = [ check_record_update(Env, RecordType, Fld) || Fld <- Update ], {typed, Attrs, {record, Attrs, NewRecord, NewUpdate}, RecordType}; -infer_expr(Env, {proj, Attrs, Record, FieldName}) -> +infer_expr(Env = #env{ current_const = none }, {proj, Attrs, Record, FieldName}) -> NewRecord = {typed, _, _, RecordType} = infer_expr(Env, Record), FieldType = fresh_uvar(Attrs), add_constraint([#field_constraint{ @@ -1994,14 +2041,14 @@ infer_expr(Env, {proj, Attrs, Record, FieldName}) -> context = {proj, Attrs, Record, FieldName} }]), {typed, Attrs, {proj, Attrs, NewRecord, FieldName}, FieldType}; %% Maps -infer_expr(Env, {map_get, Attrs, Map, Key}) -> %% map lookup +infer_expr(Env = #env{ current_const = none }, {map_get, Attrs, Map, Key}) -> %% map lookup KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), Map1 = check_expr(Env, Map, MapType), Key1 = check_expr(Env, Key, KeyType), {typed, Attrs, {map_get, Attrs, Map1, Key1}, ValType}; -infer_expr(Env, {map_get, Attrs, Map, Key, Val}) -> %% map lookup with default +infer_expr(Env = #env{ current_const = none }, {map_get, Attrs, Map, Key, Val}) -> %% map lookup with default KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), @@ -2015,28 +2062,28 @@ infer_expr(Env, {map, Attrs, KVs}) -> %% map construction KVs1 = [ {check_expr(Env, K, KeyType), check_expr(Env, V, ValType)} || {K, V} <- KVs ], {typed, Attrs, {map, Attrs, KVs1}, map_t(Attrs, KeyType, ValType)}; -infer_expr(Env, {map, Attrs, Map, Updates}) -> %% map update +infer_expr(Env = #env{ current_const = none }, {map, Attrs, Map, Updates}) -> %% map update KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), Map1 = check_expr(Env, Map, MapType), Updates1 = [ check_map_update(Env, Upd, KeyType, ValType) || Upd <- Updates ], {typed, Attrs, {map, Attrs, Map1, Updates1}, MapType}; -infer_expr(Env, {block, Attrs, Stmts}) -> +infer_expr(Env = #env{ current_const = none }, {block, Attrs, Stmts}) -> BlockType = fresh_uvar(Attrs), NewStmts = infer_block(Env, Attrs, Stmts, BlockType), {typed, Attrs, {block, Attrs, NewStmts}, BlockType}; -infer_expr(_Env, {record_or_map_error, Attrs, Fields}) -> +infer_expr(#env{ current_const = none }, {record_or_map_error, Attrs, Fields}) -> type_error({mixed_record_and_map, {record, Attrs, Fields}}), Type = fresh_uvar(Attrs), {typed, Attrs, {record, Attrs, []}, Type}; -infer_expr(Env, {record_or_map_error, Attrs, Expr, []}) -> +infer_expr(Env = #env{ current_const = none }, {record_or_map_error, Attrs, Expr, []}) -> type_error({empty_record_or_map_update, {record, Attrs, Expr, []}}), infer_expr(Env, Expr); -infer_expr(Env, {record_or_map_error, Attrs, Expr, Fields}) -> +infer_expr(Env = #env{ current_const = none }, {record_or_map_error, Attrs, Expr, Fields}) -> type_error({mixed_record_and_map, {record, Attrs, Expr, Fields}}), infer_expr(Env, Expr); -infer_expr(Env, {lam, Attrs, Args, Body}) -> +infer_expr(Env = #env{ current_const = none }, {lam, Attrs, Args, Body}) -> ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args], ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args], ResultType = fresh_uvar(Attrs), @@ -2044,15 +2091,18 @@ infer_expr(Env, {lam, Attrs, Args, Body}) -> infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, [{guarded, Attrs, [], Body}], ResultType), NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; -infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> +infer_expr(Env = #env{ current_const = none }, {letpat, Attrs, Id, Pattern}) -> NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern), {typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType}; -infer_expr(Env, Let = {letval, Attrs, _, _}) -> +infer_expr(Env = #env{ current_const = none }, Let = {letval, Attrs, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); -infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> +infer_expr(Env = #env{ current_const = none }, Let = {letfun, Attrs, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), - infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). + infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); +infer_expr(Env = #env{ current_const = Id }, _) -> + type_error({invalid_const_expr, Id}), + destroy_and_report_type_errors(Env). infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> FunType = @@ -2214,6 +2264,12 @@ infer_block(Env, Attrs, [E|Rest], BlockType) -> when_warning(warn_unused_return_value, fun() -> potential_unused_return_value(NewE) end), [NewE|infer_block(Env, Attrs, Rest, BlockType)]. +infer_const(Env, {letval, Ann, Id = {id, AnnId, _}, Expr}) -> + NewExpr = {typed, _, _, Type} = infer_expr(Env#env{ current_const = Id }, Expr), + IdType = setelement(2, Type, AnnId), + NewId = {typed, aeso_syntax:get_ann(Id), Id, IdType}, + {letval, Ann, NewId, NewExpr}. + infer_infix({BoolOp, As}) when BoolOp =:= '&&'; BoolOp =:= '||' -> Bool = {id, As, "bool"}, @@ -3794,6 +3850,15 @@ mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) -> Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`", [KindStr, name(ContractCon), name(InterfaceCon)]), mk_t_err(pos(ContractCon), Msg); +mk_error({mutually_recursive_constants, Consts}) -> + Msg = [ "Mutual recursion detected between the constants", + [ io_lib:format("\n - `~s` at ~s", [name(Id), pp_loc(Ann)]) + || {letval, Ann, Id, _} <- Consts ] ], + [{letval, Ann, _, _} | _] = Consts, + mk_t_err(pos(Ann), Msg); +mk_error({invalid_const_expr, ConstId}) -> + Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]), + mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index f3dd9cc..43b8dd6 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -160,6 +160,7 @@ context => context(), vars => [var_name()], functions := #{ fun_name() => fun_def() }, + consts := #{ var_name() => fexpr() }, saved_fresh_names => #{ var_name() => var_name() } }. @@ -240,7 +241,8 @@ init_env(Options) -> ["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities } }, options => Options, - functions => #{} + functions => #{}, + consts => #{} }. -spec builtins() -> builtins(). @@ -395,7 +397,11 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R return => FRet, body => FBody }, NewFuns = Funs#{ FName => Def }, - Env#{ functions := NewFuns }. + Env#{ functions := NewFuns }; +decl_to_fcode(Env = #{ consts := Consts }, {letval, _, {typed, _, {id, _, X}, _}, Val}) -> + FVal = expr_to_fcode(Env, Val), + NewConsts = Consts#{ qname(Env, X) => FVal }, + Env#{ consts := NewConsts }. -spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env(). typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> @@ -1722,9 +1728,15 @@ bind_var(Env = #{ vars := Vars }, X) -> Env#{ vars := [X | Vars] }. resolve_var(#{ vars := Vars } = Env, [X]) -> case lists:member(X, Vars) of true -> {var, X}; - false -> resolve_fun(Env, [X]) + false -> resolve_const(Env, [X]) end; -resolve_var(Env, Q) -> resolve_fun(Env, Q). +resolve_var(Env, Q) -> resolve_const(Env, Q). + +resolve_const(#{ consts := Consts } = Env, Q) -> + case maps:get(Q, Consts, not_found) of + not_found -> resolve_fun(Env, Q); + Val -> Val + end. resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) -> case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of -- 2.30.2 From 2dfe5eefb01eb81505e6295c3b0fe764bbf6f7cf Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 15 Dec 2022 14:32:51 +0300 Subject: [PATCH 02/37] Remove the test that fails on toplevel consts --- test/aeso_compiler_tests.erl | 4 ---- test/contracts/toplevel_let.aes | 3 --- 2 files changed, 7 deletions(-) delete mode 100644 test/contracts/toplevel_let.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 9eb002d..b91d115 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -658,10 +658,6 @@ failing_contracts() -> [<>]) - , ?TYPE_ERROR(toplevel_let, - [<>]) , ?TYPE_ERROR(empty_typedecl, [< Date: Thu, 15 Dec 2022 16:11:29 +0300 Subject: [PATCH 03/37] Warn when shadowing a constant --- src/aeso_ast_infer_types.erl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index d8f37df..4b9113b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -187,9 +187,13 @@ pop_scope(Env) -> get_scope(#env{ scopes = Scopes }, Name) -> maps:get(Name, Scopes, false). +-spec get_current_scope(env()) -> scope(). +get_current_scope(#env{ namespace = NS, scopes = Scopes }) -> + maps:get(NS, Scopes). + -spec on_current_scope(env(), fun((scope()) -> scope())) -> env(). on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) -> - Scope = maps:get(NS, Scopes), + Scope = get_current_scope(Env), Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }. -spec on_scopes(env(), fun((scope()) -> scope())) -> env(). @@ -197,8 +201,8 @@ on_scopes(Env = #env{ scopes = Scopes }, Fun) -> Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }. -spec bind_var(aeso_syntax:id(), utype(), env()) -> env(). -bind_var({id, Ann, X}, T, Env = #env{ vars = Vars }) -> - when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Ann, X, Vars) end), +bind_var({id, Ann, X}, T, Env) -> + when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Env, Ann, X) end), Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }. -spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env(). @@ -3355,9 +3359,11 @@ destroy_and_report_unused_functions() -> %% Warnings (Shadowing) -warn_potential_shadowing(_, "_", _) -> ok; -warn_potential_shadowing(Ann, Name, Vars) -> - case proplists:get_value(Name, Vars, false) of +warn_potential_shadowing(_, _, "_") -> ok; +warn_potential_shadowing(Env = #env{ vars = Vars }, Ann, Name) -> + CurrentScope = get_current_scope(Env), + Consts = CurrentScope#scope.consts, + case proplists:get_value(Name, Vars ++ Consts, false) of false -> ok; {AnnOld, _} -> ets_insert(warnings, {shadowing, Ann, Name, AnnOld}) end. -- 2.30.2 From cb817d6c769060e0a1c9d6e49b45c41da7cbd1cc Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 12:22:02 +0300 Subject: [PATCH 04/37] Allow records to be used as compile time constants --- src/aeso_ast_infer_types.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 4b9113b..0778735 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2011,7 +2011,7 @@ infer_expr(Env = #env{ current_const = none }, {switch, Attrs, Expr, Cases}) -> NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType) || {'case', As, Pattern, GuardedBranches} <- Cases], {typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType}; -infer_expr(Env = #env{ current_const = none }, {record, Attrs, Fields}) -> +infer_expr(Env, {record, Attrs, Fields}) -> RecordType = fresh_uvar(Attrs), NewFields = [{field, A, FieldName, infer_expr(Env, Expr)} || {field, A, FieldName, Expr} <- Fields], @@ -2269,10 +2269,12 @@ infer_block(Env, Attrs, [E|Rest], BlockType) -> [NewE|infer_block(Env, Attrs, Rest, BlockType)]. infer_const(Env, {letval, Ann, Id = {id, AnnId, _}, Expr}) -> + create_constraints(), NewExpr = {typed, _, _, Type} = infer_expr(Env#env{ current_const = Id }, Expr), + solve_then_destroy_and_report_unsolved_constraints(Env), IdType = setelement(2, Type, AnnId), NewId = {typed, aeso_syntax:get_ann(Id), Id, IdType}, - {letval, Ann, NewId, NewExpr}. + instantiate({letval, Ann, NewId, NewExpr}). infer_infix({BoolOp, As}) when BoolOp =:= '&&'; BoolOp =:= '||' -> -- 2.30.2 From 719dfdf2401d836809d2e431772c84d75471e769 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 12:33:49 +0300 Subject: [PATCH 05/37] Allow data constructors in compile-time constants --- src/aeso_ast_infer_types.erl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0778735..434c764 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1971,10 +1971,18 @@ infer_expr(Env = #env{ current_const = CurrentConst }, {app, Ann, Fun, Args0} = infer_op(Env, Ann, Fun, Args, fun infer_infix/1); prefix -> infer_op(Env, Ann, Fun, Args, fun infer_prefix/1); - _ when CurrentConst =/= none -> - type_error({invalid_const_expr, CurrentConst}), - destroy_and_report_type_errors(Env); _ -> + case CurrentConst of + none -> ok; + Id -> + case Fun of + {con, _, _} -> ok; + {qcon, _, _} -> ok; + _ -> + type_error({invalid_const_expr, Id}), + destroy_and_report_type_errors(Env) + end + end, NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], NewFun0 = infer_expr(Env, Fun), -- 2.30.2 From a9548e77d8b6a7e921423f29ad8dc2c184713a1a Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 12:46:37 +0300 Subject: [PATCH 06/37] Disable some warnings for toplevel constants Since variables and functions cannot be used in the definition of a compile time constants, the following warnings are not going to be reported: * Used/Unused variable * Used/Unused function --- src/aeso_ast_infer_types.erl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 434c764..797c5f0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1735,9 +1735,17 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti type_error({unbound_variable, Id}), {Id, fresh_uvar(As)}; {QId, {_, Ty}} -> - when_warning(warn_unused_variables, fun() -> used_variable(NS, name(CurFn), QId) end), - when_warning(warn_unused_functions, - fun() -> register_function_call(NS ++ qname(CurFn), QId) end), + %% Variables and functions cannot be used when CurFn is `none`. + %% i.e. they cannot be used in toplevel constants + [ begin + when_warning( + warn_unused_variables, + fun() -> used_variable(NS, name(CurFn), QId) end), + when_warning( + warn_unused_functions, + fun() -> register_function_call(NS ++ qname(CurFn), QId) end) + end || CurFn =/= none ], + Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of -- 2.30.2 From 98c521d90aba82d0c2e19e5fcb1c2ad667d489aa Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 12:53:15 +0300 Subject: [PATCH 07/37] Do not reverse constants declarations --- src/aeso_ast_infer_types.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 797c5f0..5b18ca0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -270,7 +270,7 @@ bind_const(X, Ann, Type, Env) -> -spec bind_consts(env(), #{ name() => aeso_syntax:decl() }, [{acyclic, name()} | {cyclic, [name()]}], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. bind_consts(Env, _Consts, [], Acc) -> - {Env, lists:reverse(Acc)}; + {Env, Acc}; bind_consts(Env, Consts, [{cyclic, Xs} | _SCCs], _Acc) -> ConstDecls = [ maps:get(X, Consts) || X <- Xs ], type_error({mutually_recursive_constants, lists:reverse(ConstDecls)}), -- 2.30.2 From c6bdb1569371423f635696a656bf953f0a6b399c Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 13:03:50 +0300 Subject: [PATCH 08/37] Add tests for all valid expressions --- test/aeso_compiler_tests.erl | 1 + test/contracts/toplevel_constants.aes | 49 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 test/contracts/toplevel_constants.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index b91d115..fdb878e 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -222,6 +222,7 @@ compilable_contracts() -> "unapplied_contract_call", "unapplied_named_arg_builtin", "resolve_field_constraint_by_arity", + "toplevel_constants", "test" % Custom general-purpose test file. Keep it last on the list. ]. diff --git a/test/contracts/toplevel_constants.aes b/test/contracts/toplevel_constants.aes new file mode 100644 index 0000000..45f0a68 --- /dev/null +++ b/test/contracts/toplevel_constants.aes @@ -0,0 +1,49 @@ +contract C = + datatype event = EventX(int, string) + + record account = { name : string, + balance : int } + + let c01 = 2425 + let c02 = -5 + let c03 = ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + let c04 = true + let c05 = Bits.none + let c06 = #fedcba9876543210 + let c07 = "str" + let c08 = [1, 2, 3] + let c09 = [(true, 24), (false, 19), (false, -42)] + let c10 = (42, "Foo", true) + let c11 = { name = "str", balance = 100000000 } + let c12 = {["foo"] = 19, ["bar"] = 42} + let c13 = Some(42) + let c14 = 11 : int + let c15 = EventX(0, "Hello") + let c16 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f + let c17 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f + let c18 = RelativeTTL(50) + let c19 = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 + let c20 = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY + let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C + + entrypoint f01() = c01 + entrypoint f02() = c02 + entrypoint f03() = c03 + entrypoint f04() = c04 + entrypoint f05() = c05 + entrypoint f06() = c06 + entrypoint f07() = c07 + entrypoint f08() = c08 + entrypoint f09() = c09 + entrypoint f10() = c10 + entrypoint f11() = c11 + entrypoint f12() = c12 + entrypoint f13() = c13 + entrypoint f14() = c14 + entrypoint f15() = c15 + entrypoint f16() = c16 + entrypoint f17() = c17 + entrypoint f18() = c18 + entrypoint f19() = c19 + entrypoint f20() = c20 + entrypoint f21() = c21 -- 2.30.2 From 112e5338daecc522ea46ef696d620251cff1d582 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 13:08:57 +0300 Subject: [PATCH 09/37] Add test for accessing const from namespace --- test/contracts/toplevel_constants.aes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/contracts/toplevel_constants.aes b/test/contracts/toplevel_constants.aes index 45f0a68..3bafee1 100644 --- a/test/contracts/toplevel_constants.aes +++ b/test/contracts/toplevel_constants.aes @@ -1,3 +1,9 @@ +namespace N0 = + let nsconst = 1 + +namespace N = + let nsconst = N0.nsconst + contract C = datatype event = EventX(int, string) @@ -25,6 +31,7 @@ contract C = let c19 = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 let c20 = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C + let c22 = N.nsconst entrypoint f01() = c01 entrypoint f02() = c02 @@ -47,3 +54,4 @@ contract C = entrypoint f19() = c19 entrypoint f20() = c20 entrypoint f21() = c21 + entrypoint f22() = c22 -- 2.30.2 From aa8ae76aca3daa5544762464f54f9a789387657c Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 13:11:06 +0300 Subject: [PATCH 10/37] Revert "Do not reverse constants declarations" This reverts commit c4647fadacd134866e4be9c2ab4b0d54870a35fd. --- src/aeso_ast_infer_types.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 5b18ca0..797c5f0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -270,7 +270,7 @@ bind_const(X, Ann, Type, Env) -> -spec bind_consts(env(), #{ name() => aeso_syntax:decl() }, [{acyclic, name()} | {cyclic, [name()]}], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. bind_consts(Env, _Consts, [], Acc) -> - {Env, Acc}; + {Env, lists:reverse(Acc)}; bind_consts(Env, Consts, [{cyclic, Xs} | _SCCs], _Acc) -> ConstDecls = [ maps:get(X, Consts) || X <- Xs ], type_error({mutually_recursive_constants, lists:reverse(ConstDecls)}), -- 2.30.2 From 77be5750f39cb66efe2f666fdaff29cb02b9375d Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 13:11:32 +0300 Subject: [PATCH 11/37] Add test for assigining constant to a constant --- test/contracts/toplevel_constants.aes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/contracts/toplevel_constants.aes b/test/contracts/toplevel_constants.aes index 3bafee1..ee3691f 100644 --- a/test/contracts/toplevel_constants.aes +++ b/test/contracts/toplevel_constants.aes @@ -32,6 +32,7 @@ contract C = let c20 = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C let c22 = N.nsconst + let c23 = c01 entrypoint f01() = c01 entrypoint f02() = c02 @@ -55,3 +56,4 @@ contract C = entrypoint f20() = c20 entrypoint f21() = c21 entrypoint f22() = c22 + entrypoint f23() = c23 -- 2.30.2 From e64bdd40d27e32b1db45f59a34826aaf64a8d515 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 13:20:43 +0300 Subject: [PATCH 12/37] Show empty map or record error when assigning to const --- src/aeso_ast_infer_types.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 797c5f0..e320fc0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2093,7 +2093,7 @@ infer_expr(Env = #env{ current_const = none }, {block, Attrs, Stmts}) -> BlockType = fresh_uvar(Attrs), NewStmts = infer_block(Env, Attrs, Stmts, BlockType), {typed, Attrs, {block, Attrs, NewStmts}, BlockType}; -infer_expr(#env{ current_const = none }, {record_or_map_error, Attrs, Fields}) -> +infer_expr(_Env, {record_or_map_error, Attrs, Fields}) -> type_error({mixed_record_and_map, {record, Attrs, Fields}}), Type = fresh_uvar(Attrs), {typed, Attrs, {record, Attrs, []}, Type}; -- 2.30.2 From 7d61d247f71494582f2ba272a58e7f6b71a4d615 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 16:15:06 +0300 Subject: [PATCH 13/37] Report all invalid constant expressions before fail --- src/aeso_ast_infer_types.erl | 73 ++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index e320fc0..2b3bc90 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1917,10 +1917,12 @@ infer_expr(Env, {list, As, Elems}) -> ElemType = fresh_uvar(As), NewElems = [check_expr(Env, X, ElemType) || X <- Elems], {typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}}; -infer_expr(Env = #env{ current_const = none }, {list_comp, As, Yield, []}) -> +infer_expr(Env, {list_comp, As, Yield, []}) -> + ban_when_const(Env), {typed, _, _, Type} = TypedYield = infer_expr(Env, Yield), {typed, As, {list_comp, As, TypedYield, []}, {app_t, As, {id, As, "list"}, [Type]}}; -infer_expr(Env = #env{ current_const = none }, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) -> +infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) -> + ban_when_const(Env), TypedBind = {typed, As2, _, TypeBExpr} = infer_expr(Env, BExpr), {NewE, TypedPat = {typed, _, _, PatType}} = infer_pattern(Env, Pat), unify( Env @@ -1933,7 +1935,8 @@ infer_expr(Env = #env{ current_const = none }, {list_comp, As, Yield, [{comprehe , As , {list_comp, As, TypedYield, [{comprehension_bind, TypedPat, TypedBind}|TypedRest]} , ResType}; -infer_expr(Env = #env{ current_const = none }, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Rest]}) -> +infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Rest]}) -> + ban_when_const(Env), NewCond = check_expr(Env, Cond, {id, AttrsIF, "bool"}), {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = infer_expr(Env, {list_comp, AttrsL, Yield, Rest}), @@ -1941,7 +1944,8 @@ infer_expr(Env = #env{ current_const = none }, {list_comp, AttrsL, Yield, [{comp , AttrsL , {list_comp, AttrsL, TypedYield, [{comprehension_if, AttrsIF, NewCond}|TypedRest]} , ResType}; -infer_expr(Env = #env{ current_const = none }, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> +infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> + ban_when_const(Env), NewE = {typed, _, _, PatType} = infer_expr(Env, E), BlockType = fresh_uvar(AsLV), {'case', _, NewPattern, [{guarded, _, [], NewRest}]} = @@ -1957,7 +1961,8 @@ infer_expr(Env = #env{ current_const = none }, {list_comp, AsLC, Yield, [{letval , {list_comp, AsLC, TypedYield, [{letval, AsLV, NewPattern, NewE}|TypedRest]} , ResType }; -infer_expr(Env = #env{ current_const = none }, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> +infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> + ban_when_const(Env), {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, AsLF, Name}, FunT, Env), @@ -2015,13 +2020,15 @@ infer_expr(Env = #env{ current_const = CurrentConst }, {app, Ann, Fun, Args0} = context = {check_return, App} }), {typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)} end; -infer_expr(Env = #env{ current_const = none }, {'if', Attrs, Cond, Then, Else}) -> +infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> + ban_when_const(Env), NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}), NewThen = {typed, _, _, ThenType} = infer_expr(Env, Then), NewElse = {typed, _, _, ElseType} = infer_expr(Env, Else), unify(Env, ThenType, ElseType, {if_branches, Then, ThenType, Else, ElseType}), {typed, Attrs, {'if', Attrs, NewCond, NewThen, NewElse}, ThenType}; -infer_expr(Env = #env{ current_const = none }, {switch, Attrs, Expr, Cases}) -> +infer_expr(Env, {switch, Attrs, Expr, Cases}) -> + ban_when_const(Env), NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr), SwitchType = fresh_uvar(Attrs), NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType) @@ -2046,11 +2053,13 @@ infer_expr(Env, {record, Attrs, Fields}) -> context = Fld} end || {Fld, {field, _, LV, {typed, _, _, T}}} <- lists:zip(Fields, NewFields)]), {typed, Attrs, {record, Attrs, NewFields}, RecordType}; -infer_expr(Env = #env{ current_const = none }, {record, Attrs, Record, Update}) -> +infer_expr(Env, {record, Attrs, Record, Update}) -> + ban_when_const(Env), NewRecord = {typed, _, _, RecordType} = infer_expr(Env, Record), NewUpdate = [ check_record_update(Env, RecordType, Fld) || Fld <- Update ], {typed, Attrs, {record, Attrs, NewRecord, NewUpdate}, RecordType}; -infer_expr(Env = #env{ current_const = none }, {proj, Attrs, Record, FieldName}) -> +infer_expr(Env, {proj, Attrs, Record, FieldName}) -> + ban_when_const(Env), NewRecord = {typed, _, _, RecordType} = infer_expr(Env, Record), FieldType = fresh_uvar(Attrs), add_constraint([#field_constraint{ @@ -2061,14 +2070,16 @@ infer_expr(Env = #env{ current_const = none }, {proj, Attrs, Record, FieldName}) context = {proj, Attrs, Record, FieldName} }]), {typed, Attrs, {proj, Attrs, NewRecord, FieldName}, FieldType}; %% Maps -infer_expr(Env = #env{ current_const = none }, {map_get, Attrs, Map, Key}) -> %% map lookup +infer_expr(Env, {map_get, Attrs, Map, Key}) -> %% map lookup + ban_when_const(Env), KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), Map1 = check_expr(Env, Map, MapType), Key1 = check_expr(Env, Key, KeyType), {typed, Attrs, {map_get, Attrs, Map1, Key1}, ValType}; -infer_expr(Env = #env{ current_const = none }, {map_get, Attrs, Map, Key, Val}) -> %% map lookup with default +infer_expr(Env, {map_get, Attrs, Map, Key, Val}) -> %% map lookup with default + ban_when_const(Env), KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), @@ -2082,14 +2093,16 @@ infer_expr(Env, {map, Attrs, KVs}) -> %% map construction KVs1 = [ {check_expr(Env, K, KeyType), check_expr(Env, V, ValType)} || {K, V} <- KVs ], {typed, Attrs, {map, Attrs, KVs1}, map_t(Attrs, KeyType, ValType)}; -infer_expr(Env = #env{ current_const = none }, {map, Attrs, Map, Updates}) -> %% map update +infer_expr(Env, {map, Attrs, Map, Updates}) -> %% map update + ban_when_const(Env), KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), Map1 = check_expr(Env, Map, MapType), Updates1 = [ check_map_update(Env, Upd, KeyType, ValType) || Upd <- Updates ], {typed, Attrs, {map, Attrs, Map1, Updates1}, MapType}; -infer_expr(Env = #env{ current_const = none }, {block, Attrs, Stmts}) -> +infer_expr(Env, {block, Attrs, Stmts}) -> + ban_when_const(Env), BlockType = fresh_uvar(Attrs), NewStmts = infer_block(Env, Attrs, Stmts, BlockType), {typed, Attrs, {block, Attrs, NewStmts}, BlockType}; @@ -2097,13 +2110,16 @@ infer_expr(_Env, {record_or_map_error, Attrs, Fields}) -> type_error({mixed_record_and_map, {record, Attrs, Fields}}), Type = fresh_uvar(Attrs), {typed, Attrs, {record, Attrs, []}, Type}; -infer_expr(Env = #env{ current_const = none }, {record_or_map_error, Attrs, Expr, []}) -> +infer_expr(Env, {record_or_map_error, Attrs, Expr, []}) -> + ban_when_const(Env), type_error({empty_record_or_map_update, {record, Attrs, Expr, []}}), infer_expr(Env, Expr); -infer_expr(Env = #env{ current_const = none }, {record_or_map_error, Attrs, Expr, Fields}) -> +infer_expr(Env, {record_or_map_error, Attrs, Expr, Fields}) -> + ban_when_const(Env), type_error({mixed_record_and_map, {record, Attrs, Expr, Fields}}), infer_expr(Env, Expr); -infer_expr(Env = #env{ current_const = none }, {lam, Attrs, Args, Body}) -> +infer_expr(Env, {lam, Attrs, Args, Body}) -> + ban_when_const(Env), ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args], ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args], ResultType = fresh_uvar(Attrs), @@ -2111,18 +2127,21 @@ infer_expr(Env = #env{ current_const = none }, {lam, Attrs, Args, Body}) -> infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, [{guarded, Attrs, [], Body}], ResultType), NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; -infer_expr(Env = #env{ current_const = none }, {letpat, Attrs, Id, Pattern}) -> +infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> + ban_when_const(Env), NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern), {typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType}; -infer_expr(Env = #env{ current_const = none }, Let = {letval, Attrs, _, _}) -> +infer_expr(Env, Let = {letval, Attrs, _, _}) -> + ban_when_const(Env), type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); -infer_expr(Env = #env{ current_const = none }, Let = {letfun, Attrs, _, _, _, _}) -> +infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> + ban_when_const(Env), type_error({missing_body_for_let, Attrs}), - infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); -infer_expr(Env = #env{ current_const = Id }, _) -> - type_error({invalid_const_expr, Id}), - destroy_and_report_type_errors(Env). + infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). + +ban_when_const(#env{ current_const = none }) -> ok; +ban_when_const(#env{ current_const = Id }) -> type_error({invalid_const_expr, Id}). infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> FunType = @@ -2248,9 +2267,13 @@ infer_pattern(Env, Pattern) -> NewPattern = infer_expr(NewEnv, Pattern), {NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}. -infer_case(Env = #env{ namespace = NS, current_function = {id, _, Fun} }, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) -> +infer_case(Env = #env{ namespace = NS, current_function = FunId }, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) -> + %% Make sure we are inside a function before warning about potentially unused var + [ when_warning(warn_unused_variables, + fun() -> potential_unused_variables(NS, Fun, free_vars(Pattern)) end) + || {id, _, Fun} <- [FunId] ], + {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), - when_warning(warn_unused_variables, fun() -> potential_unused_variables(NS, Fun, free_vars(Pattern)) end), InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) -> NewGuards = lists:map(fun(Guard) -> check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}) -- 2.30.2 From 80645222ad965b6d1692755518cf4cdd396c1949 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 16:52:54 +0300 Subject: [PATCH 14/37] Allow accessing records fields in toplevel consts --- src/aeso_ast_infer_types.erl | 1 - test/contracts/toplevel_constants.aes | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 2b3bc90..046e2aa 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2059,7 +2059,6 @@ infer_expr(Env, {record, Attrs, Record, Update}) -> NewUpdate = [ check_record_update(Env, RecordType, Fld) || Fld <- Update ], {typed, Attrs, {record, Attrs, NewRecord, NewUpdate}, RecordType}; infer_expr(Env, {proj, Attrs, Record, FieldName}) -> - ban_when_const(Env), NewRecord = {typed, _, _, RecordType} = infer_expr(Env, Record), FieldType = fresh_uvar(Attrs), add_constraint([#field_constraint{ diff --git a/test/contracts/toplevel_constants.aes b/test/contracts/toplevel_constants.aes index ee3691f..f495fc2 100644 --- a/test/contracts/toplevel_constants.aes +++ b/test/contracts/toplevel_constants.aes @@ -33,6 +33,7 @@ contract C = let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C let c22 = N.nsconst let c23 = c01 + let c24 = c11.name entrypoint f01() = c01 entrypoint f02() = c02 @@ -57,3 +58,4 @@ contract C = entrypoint f21() = c21 entrypoint f22() = c22 entrypoint f23() = c23 + entrypoint f24() = c24 -- 2.30.2 From de34fc11222ab3f1a26dd46cc26b1d212c2f86f6 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 17:51:09 +0300 Subject: [PATCH 15/37] Undo a mistake --- src/aeso_ast_infer_types.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 046e2aa..ff2a8ae 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2267,12 +2267,13 @@ infer_pattern(Env, Pattern) -> {NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}. infer_case(Env = #env{ namespace = NS, current_function = FunId }, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) -> + {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), + %% Make sure we are inside a function before warning about potentially unused var [ when_warning(warn_unused_variables, fun() -> potential_unused_variables(NS, Fun, free_vars(Pattern)) end) || {id, _, Fun} <- [FunId] ], - {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) -> NewGuards = lists:map(fun(Guard) -> check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}) -- 2.30.2 From 28b26ea4cc1ef8077a2d729d4a1756ad697c2ab0 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 18:32:35 +0300 Subject: [PATCH 16/37] Add test for warning on const shadowing --- test/aeso_compiler_tests.erl | 8 ++++++-- test/contracts/warnings.aes | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index fdb878e..ceb8048 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -287,7 +287,9 @@ warnings() -> <>, <> + "The function `dec` is defined but never used.">>, + <> ]). failing_contracts() -> @@ -856,7 +858,9 @@ failing_contracts() -> <>, <> + "The function `dec` is defined but never used.">>, + <> ]) , ?TYPE_ERROR(polymorphism_contract_interface_recursive, [< Date: Fri, 16 Dec 2022 18:54:17 +0300 Subject: [PATCH 17/37] Show error message when using pattern matching for consts --- src/aeso_ast_infer_types.erl | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ff2a8ae..7ed90d0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1088,13 +1088,13 @@ infer_contract(Env0, What, Defs0, Options) -> end, destroy_and_report_type_errors(Env0), Env = Env0#env{ what = What }, - Kind = fun({type_def, _, _, _, _}) -> type; - ({letfun, _, _, _, _, _}) -> function; - ({fun_clauses, _, _, _, _}) -> function; - ({fun_decl, _, _, _}) -> prototype; - ({using, _, _, _, _}) -> using; - ({letval, _, {id, _, _}, _}) -> constant; - (_) -> unexpected + Kind = fun({type_def, _, _, _, _}) -> type; + ({letfun, _, _, _, _, _}) -> function; + ({fun_clauses, _, _, _, _}) -> function; + ({fun_decl, _, _, _}) -> prototype; + ({using, _, _, _, _}) -> using; + ({letval, _, _, _}) -> constant; + (_) -> unexpected end, Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end, OldUsedNamespaces = Env#env.used_namespaces, @@ -1286,7 +1286,12 @@ opposite_variance(bivariant) -> bivariant. -spec check_constants(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. check_constants(Env, Consts) -> - ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- Consts ]), + HasValidId = fun({letval, _, {id, _, _}, _}) -> true; + (_) -> false + end, + {ValidConsts, InvalidConsts} = lists:partition(HasValidId, Consts), + [ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- InvalidConsts ], + ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- ValidConsts ]), DepGraph = maps:map(fun(_, Const) -> aeso_syntax_utils:used_ids(Const) end, ConstMap), SCCs = aeso_utils:scc(DepGraph), bind_consts(Env, ConstMap, SCCs, []). @@ -3903,6 +3908,9 @@ mk_error({mutually_recursive_constants, Consts}) -> || {letval, Ann, Id, _} <- Consts ] ], [{letval, Ann, _, _} | _] = Consts, mk_t_err(pos(Ann), Msg); +mk_error({invalid_const_id, Ann}) -> + Msg = "Pattern matching is not allowed when defining compile-time constants", + mk_t_err(pos(Ann), Msg); mk_error({invalid_const_expr, ConstId}) -> Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]), mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg); -- 2.30.2 From 162ad4667eeaa9c43fbeee4544e6f4243a707692 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Dec 2022 19:02:23 +0300 Subject: [PATCH 18/37] Remove unused error --- src/aeso_ast_infer_types.erl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 7ed90d0..e6ecb5b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -3651,10 +3651,6 @@ mk_error({type_decl, _, {id, Pos, Name}, _}) -> Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition", [Name]), mk_t_err(pos(Pos), Msg); -mk_error({letval, _Pos, {id, Pos, Name}, _Def}) -> - Msg = io_lib:format("Toplevel \"let\" definitions are not supported. Value `~s` could be replaced by 0-argument function.", - [Name]), - mk_t_err(pos(Pos), Msg); mk_error({stateful_not_allowed, Id, Fun}) -> Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.", [pp(Id), pp(Fun)]), -- 2.30.2 From b23d40e1e650d3dd9f86e236331f01d2c8c9f7df Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 17 Dec 2022 18:07:00 +0300 Subject: [PATCH 19/37] Ban toplevel constants in contract interfaces --- src/aeso_ast_infer_types.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index e6ecb5b..7dfd1cb 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1285,12 +1285,13 @@ opposite_variance(contravariant) -> covariant; opposite_variance(bivariant) -> bivariant. -spec check_constants(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -check_constants(Env, Consts) -> +check_constants(Env = #env{ what = What }, Consts) -> HasValidId = fun({letval, _, {id, _, _}, _}) -> true; (_) -> false end, {ValidConsts, InvalidConsts} = lists:partition(HasValidId, Consts), [ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- InvalidConsts ], + [ type_error({illegal_const_in_interface, Ann}) || {letval, Ann, _, _} <- ValidConsts, What == contract_interface ], ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- ValidConsts ]), DepGraph = maps:map(fun(_, Const) -> aeso_syntax_utils:used_ids(Const) end, ConstMap), SCCs = aeso_utils:scc(DepGraph), @@ -3910,6 +3911,9 @@ mk_error({invalid_const_id, Ann}) -> mk_error({invalid_const_expr, ConstId}) -> Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]), mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg); +mk_error({illegal_const_in_interface, Ann}) -> + Msg = "Cannot define toplevel constants inside a contract interface", + mk_t_err(pos(Ann), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p", [Err]), mk_t_err(pos(0, 0), Msg). -- 2.30.2 From 342126b60e897edc4d25e0825bc327e32107c82f Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 17 Dec 2022 18:08:10 +0300 Subject: [PATCH 20/37] Varibles rename --- src/aeso_ast_infer_types.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 7dfd1cb..8b2f0de 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1289,10 +1289,10 @@ check_constants(Env = #env{ what = What }, Consts) -> HasValidId = fun({letval, _, {id, _, _}, _}) -> true; (_) -> false end, - {ValidConsts, InvalidConsts} = lists:partition(HasValidId, Consts), - [ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- InvalidConsts ], - [ type_error({illegal_const_in_interface, Ann}) || {letval, Ann, _, _} <- ValidConsts, What == contract_interface ], - ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- ValidConsts ]), + {Valid, Invalid} = lists:partition(HasValidId, Consts), + [ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- Invalid ], + [ type_error({illegal_const_in_interface, Ann}) || {letval, Ann, _, _} <- Valid, What == contract_interface ], + ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- Valid ]), DepGraph = maps:map(fun(_, Const) -> aeso_syntax_utils:used_ids(Const) end, ConstMap), SCCs = aeso_utils:scc(DepGraph), bind_consts(Env, ConstMap, SCCs, []). -- 2.30.2 From 9174b90986c21eca34b96dc9574d76a37740dfdd Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 17 Dec 2022 19:07:11 +0300 Subject: [PATCH 21/37] Change the error message for invalid_const_id --- src/aeso_ast_infer_types.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 8b2f0de..fcfd747 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -3906,7 +3906,7 @@ mk_error({mutually_recursive_constants, Consts}) -> [{letval, Ann, _, _} | _] = Consts, mk_t_err(pos(Ann), Msg); mk_error({invalid_const_id, Ann}) -> - Msg = "Pattern matching is not allowed when defining compile-time constants", + Msg = "The name of the compile-time constant cannot have pattern matching nor type", mk_t_err(pos(Ann), Msg); mk_error({invalid_const_expr, ConstId}) -> Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]), -- 2.30.2 From ec119a90c5e0e8471a31f12e74bf839de8a8d664 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 17 Dec 2022 19:33:53 +0300 Subject: [PATCH 22/37] Make constants public in namespaces and private in contracts --- src/aeso_ast_infer_types.erl | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index fcfd747..fd9e8b0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -470,21 +470,30 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes %% Get the scope case maps:get(Qual, Scopes, false) of false -> false; %% TODO: return reason for not in scope - #scope{ funs = Funs, types = Types, consts = Consts } -> + #scope{ funs = Funs, types = Types, consts = Consts, kind = ScopeKind } -> Defs = case Kind of type -> Types; - term -> Funs ++ Consts + term -> Funs end, %% Look up the unqualified name case proplists:get_value(Name, Defs, false) of - false -> false; + false -> + case proplists:get_value(Name, Consts, false) of + false -> + false; + Const when AllowPrivate; ScopeKind == namespace -> + {QName, Const}; + Const -> + type_error({contract_treated_as_namespace_constant, Ann, QName}), + {QName, Const} + end; {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} when AllowPrivate orelse QNameIsEvent -> {QName, {Ann1, Type}}; {contract_fun, Ann1, Type} -> - type_error({contract_treated_as_namespace, Ann, QName}), + type_error({contract_treated_as_namespace_entrypoint, Ann, QName}), {QName, {Ann1, Type}}; {Ann1, _} = E -> %% Check that it's not private (or we can see private funs) @@ -3735,10 +3744,14 @@ mk_error({cannot_call_init_function, Ann}) -> Msg = "The 'init' function is called exclusively by the create contract transaction " "and cannot be called from the contract code.", mk_t_err(pos(Ann), Msg); -mk_error({contract_treated_as_namespace, Ann, [Con, Fun] = QName}) -> +mk_error({contract_treated_as_namespace_entrypoint, Ann, [Con, Fun] = QName}) -> Msg = io_lib:format("Invalid call to contract entrypoint `~s`.", [string:join(QName, ".")]), Cxt = io_lib:format("It must be called as `c.~s` for some `c : ~s`.", [Fun, Con]), mk_t_err(pos(Ann), Msg, Cxt); +mk_error({contract_treated_as_namespace_constant, Ann, QName}) -> + Msg = io_lib:format("Invalid use of the contract constant `~s`.", [string:join(QName, ".")]), + Cxt = "Toplevel contract constants can only be used in the contracts where they are defined.", + mk_t_err(pos(Ann), Msg, Cxt); mk_error({bad_top_level_decl, Decl}) -> What = case element(1, Decl) of letval -> "function or entrypoint"; -- 2.30.2 From 42c34ce3d8b8d3fd15dc29e35de83969067e0a6d Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 17 Dec 2022 20:05:18 +0300 Subject: [PATCH 23/37] Add a warning about unused constants in contracts --- src/aeso_ast_infer_types.erl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index fd9e8b0..c1f0768 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1301,6 +1301,7 @@ check_constants(Env = #env{ what = What }, Consts) -> {Valid, Invalid} = lists:partition(HasValidId, Consts), [ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- Invalid ], [ type_error({illegal_const_in_interface, Ann}) || {letval, Ann, _, _} <- Valid, What == contract_interface ], + when_warning(warn_unused_constants, fun() -> potential_unused_constants(Env, Valid) end), ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- Valid ]), DepGraph = maps:map(fun(_, Const) -> aeso_syntax_utils:used_ids(Const) end, ConstMap), SCCs = aeso_utils:scc(DepGraph), @@ -1761,6 +1762,8 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti fun() -> register_function_call(NS ++ qname(CurFn), QId) end) end || CurFn =/= none ], + when_warning(warn_unused_constants, fun() -> used_constant(NS, QId) end), + Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of @@ -3293,6 +3296,7 @@ all_warnings() -> [ warn_unused_includes , warn_unused_stateful , warn_unused_variables + , warn_unused_constants , warn_unused_typedefs , warn_unused_return_value , warn_unused_functions @@ -3370,6 +3374,17 @@ used_variable(Namespace, Fun, [VarName]) -> ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName}); used_variable(_, _, _) -> ok. +%% Warnings (Unused constants) + +potential_unused_constants(#env{ what = namespace }, _Consts) -> + []; +potential_unused_constants(#env{ namespace = Namespace }, Consts) -> + [ ets_insert(warnings, {unused_constant, Ann, Namespace, Name}) || {letval, _, {id, Ann, Name}, _} <- Consts ]. + +used_constant(Namespace = [Contract], [Contract, ConstName]) -> + ets_match_delete(warnings, {unused_constant, '_', Namespace, ConstName}); +used_constant(_, _) -> ok. + %% Warnings (Unused return value) potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" -> @@ -3940,6 +3955,9 @@ mk_warning({unused_stateful, Ann, FunName}) -> mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) -> Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]), aeso_warnings:new(pos(Ann), Msg); +mk_warning({unused_constant, Ann, _Namespace, ConstName}) -> + Msg = io_lib:format("The constant `~s` is defined but never used.", [ConstName]), + aeso_warnings:new(pos(Ann), Msg); mk_warning({unused_typedef, Ann, QName, _Arity}) -> Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]), aeso_warnings:new(pos(Ann), Msg); -- 2.30.2 From 95d372c718653f6b45ef1a1573e0c69e754e1ca1 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 18 Dec 2022 11:04:04 +0300 Subject: [PATCH 24/37] Use ban_when_const for function applications --- src/aeso_ast_infer_types.erl | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index c1f0768..0762109 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1995,7 +1995,7 @@ infer_expr(Env, {typed, As, Body, Type}) -> Type1 = check_type(Env, Type), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, As, NewBody, NewType}; -infer_expr(Env = #env{ current_const = CurrentConst }, {app, Ann, Fun, Args0} = App) -> +infer_expr(Env, {app, Ann, Fun, Args0} = App) -> {NamedArgs, Args} = split_args(Args0), case aeso_syntax:get_ann(format, Ann) of infix -> @@ -2003,17 +2003,9 @@ infer_expr(Env = #env{ current_const = CurrentConst }, {app, Ann, Fun, Args0} = prefix -> infer_op(Env, Ann, Fun, Args, fun infer_prefix/1); _ -> - case CurrentConst of - none -> ok; - Id -> - case Fun of - {con, _, _} -> ok; - {qcon, _, _} -> ok; - _ -> - type_error({invalid_const_expr, Id}), - destroy_and_report_type_errors(Env) - end - end, + %% Applications of data constructors are allowed in constants + lists:member(element(1, Fun), [con, qcon]) orelse ban_when_const(Env), + NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], NewFun0 = infer_expr(Env, Fun), -- 2.30.2 From 0e154f96faa39b3d6e7b264aa8c87e644525afca Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 18 Dec 2022 11:42:37 +0300 Subject: [PATCH 25/37] Test for qualified access of constants in functions --- test/contracts/toplevel_constants.aes | 1 + 1 file changed, 1 insertion(+) diff --git a/test/contracts/toplevel_constants.aes b/test/contracts/toplevel_constants.aes index f495fc2..4e82342 100644 --- a/test/contracts/toplevel_constants.aes +++ b/test/contracts/toplevel_constants.aes @@ -59,3 +59,4 @@ contract C = entrypoint f22() = c22 entrypoint f23() = c23 entrypoint f24() = c24 + entrypoint fqual() = C.c01 -- 2.30.2 From be7133dec7faa7200ecdc28e6b72f70a6cb8e4d6 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 18 Dec 2022 22:32:16 +0300 Subject: [PATCH 26/37] Add failing tests --- test/aeso_compiler_tests.erl | 60 +++++++++++++++++++ ...plevel_constants_contract_as_namespace.aes | 11 ++++ test/contracts/toplevel_constants_cycles.aes | 6 ++ .../toplevel_constants_in_interface.aes | 7 +++ .../toplevel_constants_invalid_expr.aes | 21 +++++++ .../toplevel_constants_invalid_id.aes | 4 ++ 6 files changed, 109 insertions(+) create mode 100644 test/contracts/toplevel_constants_contract_as_namespace.aes create mode 100644 test/contracts/toplevel_constants_cycles.aes create mode 100644 test/contracts/toplevel_constants_in_interface.aes create mode 100644 test/contracts/toplevel_constants_invalid_expr.aes create mode 100644 test/contracts/toplevel_constants_invalid_id.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index ceb8048..53913fb 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1194,6 +1194,66 @@ failing_contracts() -> <> ]) + , ?TYPE_ERROR(toplevel_constants_contract_as_namespace, + [<>, + <>, + <>, + <>, + <> + ]) + , ?TYPE_ERROR(toplevel_constants_cycles, + [<>, + <> + ]) + , ?TYPE_ERROR(toplevel_constants_in_interface, + [<>, + <>, + <> + ]) + , ?TYPE_ERROR(toplevel_constants_invalid_expr, + [<>, + <>, + <>, + <>, + <>, + <>, + <>, + <>, + <>, + <> + ]) + , ?TYPE_ERROR(toplevel_constants_invalid_id, + [<>, + <>, + <> + ]) ]. validation_test_() -> diff --git a/test/contracts/toplevel_constants_contract_as_namespace.aes b/test/contracts/toplevel_constants_contract_as_namespace.aes new file mode 100644 index 0000000..8bd6432 --- /dev/null +++ b/test/contracts/toplevel_constants_contract_as_namespace.aes @@ -0,0 +1,11 @@ +contract G = + let const = 1 + +main contract C = + let c = G.const + + stateful entrypoint f() = + let g = Chain.create() : G + + g.const + g.const() diff --git a/test/contracts/toplevel_constants_cycles.aes b/test/contracts/toplevel_constants_cycles.aes new file mode 100644 index 0000000..3096125 --- /dev/null +++ b/test/contracts/toplevel_constants_cycles.aes @@ -0,0 +1,6 @@ +contract C = + let selfcycle = selfcycle + + let cycle1 = cycle2 + let cycle2 = cycle3 + let cycle3 = cycle1 diff --git a/test/contracts/toplevel_constants_in_interface.aes b/test/contracts/toplevel_constants_in_interface.aes new file mode 100644 index 0000000..56558a7 --- /dev/null +++ b/test/contracts/toplevel_constants_in_interface.aes @@ -0,0 +1,7 @@ +contract interface I = + let (x::y::_) = [1,2,3] + let c = 10 + let d = 10 + +contract C = + entrypoint init() = () diff --git a/test/contracts/toplevel_constants_invalid_expr.aes b/test/contracts/toplevel_constants_invalid_expr.aes new file mode 100644 index 0000000..c9deecd --- /dev/null +++ b/test/contracts/toplevel_constants_invalid_expr.aes @@ -0,0 +1,21 @@ +main contract C = + record account = { name : string, + balance : int } + + let one = 1 + let opt = Some(5) + let acc = { name = "str", balance = 100000 } + let mpp = {["foo"] = 19, ["bar"] = 42} + + let c01 = [x | x <- [1,2,3,4,5]] + let c02 = [x + k | x <- [1,2,3,4,5], let k = x*x] + let c03 = [x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]] + let c04 = if (one > 2) 3 else 4 + let c05 = switch (opt) + Some(x) => x + None => 2 + let c07 = acc{ balance = one } + let c08 = mpp["foo"] + let c09 = mpp["non" = 10] + let c10 = mpp{["foo"] = 20} + let c11 = (x) => x + 1 diff --git a/test/contracts/toplevel_constants_invalid_id.aes b/test/contracts/toplevel_constants_invalid_id.aes new file mode 100644 index 0000000..3799fbb --- /dev/null +++ b/test/contracts/toplevel_constants_invalid_id.aes @@ -0,0 +1,4 @@ +contract C = + let x::_ = [1,2,3,4] + let y::(p = z::_) = [1,2,3,4] + let t : int = 1 -- 2.30.2 From e756f935f2f25b80305faabf277d40445abbf3ee Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 18 Dec 2022 22:43:04 +0300 Subject: [PATCH 27/37] Add test for the unused const warning --- test/aeso_compiler_tests.erl | 8 ++++++-- test/contracts/warnings.aes | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 53913fb..74bc2e8 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -289,7 +289,9 @@ warnings() -> <>, <> + "The definition of `const` shadows an older definition at line 70, column 3.">>, + <> ]). failing_contracts() -> @@ -860,7 +862,9 @@ failing_contracts() -> <>, <> + "The definition of `const` shadows an older definition at line 70, column 3.">>, + <> ]) , ?TYPE_ERROR(polymorphism_contract_interface_recursive, [< Date: Sun, 18 Dec 2022 22:52:13 +0300 Subject: [PATCH 28/37] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed83675..66a4b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Toplevel compile-time constants + ``` + namespace N = + let nc = 1 + contract C = + let cc = 2 + ``` ### Changed ### Removed ### Fixed -- 2.30.2 From 6308f009cab3d16bc3bea09f9282813e206a5d94 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 18 Dec 2022 23:03:35 +0300 Subject: [PATCH 29/37] Update all_syntax test file --- test/contracts/all_syntax.aes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/contracts/all_syntax.aes b/test/contracts/all_syntax.aes index 513f3be..823de77 100644 --- a/test/contracts/all_syntax.aes +++ b/test/contracts/all_syntax.aes @@ -6,6 +6,7 @@ namespace Ns = datatype d('a) = D | S(int) | M('a, list('a), int) private function fff() = 123 + let const = 1 stateful entrypoint f (1, x) = (_) => x @@ -33,6 +34,8 @@ contract AllSyntax = type state = shakespeare(int) + let cc = "str" + entrypoint init() = { johann = 1000, wolfgang = -10, -- 2.30.2 From 51935f83882e21aad6a4962d73fcc59a53dc0abe Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 19 Dec 2022 13:43:45 +0300 Subject: [PATCH 30/37] Treat expr and type inside bound as bound --- src/aeso_syntax_utils.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index e50f2d4..c9ee23a 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -31,11 +31,13 @@ | aeso_syntax:field(aeso_syntax:expr()) | aeso_syntax:stmt(). fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> + ExprKind = if K == bind_expr -> bind_expr; true -> expr end, + TypeKind = if K == bind_type -> bind_type; true -> type end, Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end, Same = fun(A) -> fold(Alg, Fun, K, A) end, Decl = fun(D) -> fold(Alg, Fun, decl, D) end, - Type = fun(T) -> fold(Alg, Fun, type, T) end, - Expr = fun(E) -> fold(Alg, Fun, expr, E) end, + Type = fun(T) -> fold(Alg, Fun, TypeKind, T) end, + Expr = fun(E) -> fold(Alg, Fun, ExprKind, E) end, BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end, BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end, Top = Fun(K, X), @@ -155,4 +157,3 @@ used(D) -> (_, _) -> #{} end, decl, D)), lists:filter(NotBound, Xs). - -- 2.30.2 From d34760b9907cc6787398eeacf9f97d4f401fc06a Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 19 Dec 2022 14:05:17 +0300 Subject: [PATCH 31/37] Allow typed ids to be used for constants --- src/aeso_ast_infer_types.erl | 17 ++++++++++++----- test/aeso_compiler_tests.erl | 11 ++++++----- test/contracts/toplevel_constants.aes | 2 ++ .../contracts/toplevel_constants_invalid_id.aes | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0762109..ce19668 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -534,8 +534,11 @@ qname({qid, _, Xs}) -> Xs; qname({con, _, X}) -> [X]; qname({qcon, _, Xs}) -> Xs. --spec name(aeso_syntax:id() | aeso_syntax:con()) -> name(). -name({_, _, X}) -> X. +-spec name(Named | {typed, _, Named, _}) -> name() when + Named :: aeso_syntax:id() | aeso_syntax:con(). +name({typed, _, X, _}) -> name(X); +name({id, _, X}) -> X; +name({con, _, X}) -> X. -spec qid(aeso_syntax:ann(), qname()) -> aeso_syntax:id() | aeso_syntax:qid(). qid(Ann, [X]) -> {id, Ann, X}; @@ -1295,8 +1298,9 @@ opposite_variance(bivariant) -> bivariant. -spec check_constants(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. check_constants(Env = #env{ what = What }, Consts) -> - HasValidId = fun({letval, _, {id, _, _}, _}) -> true; - (_) -> false + HasValidId = fun({letval, _, {id, _, _}, _}) -> true; + ({letval, _, {typed, _, {id, _, _}, _}, _}) -> true; + (_) -> false end, {Valid, Invalid} = lists:partition(HasValidId, Consts), [ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- Invalid ], @@ -2317,6 +2321,9 @@ infer_block(Env, Attrs, [E|Rest], BlockType) -> when_warning(warn_unused_return_value, fun() -> potential_unused_return_value(NewE) end), [NewE|infer_block(Env, Attrs, Rest, BlockType)]. +infer_const(Env, {letval, Ann, TypedId = {typed, _, Id = {id, _, _}, Type}, Expr}) -> + NewExpr = check_expr(Env#env{ current_const = Id }, Expr, Type), + {letval, Ann, TypedId, NewExpr}; infer_const(Env, {letval, Ann, Id = {id, AnnId, _}, Expr}) -> create_constraints(), NewExpr = {typed, _, _, Type} = infer_expr(Env#env{ current_const = Id }, Expr), @@ -3926,7 +3933,7 @@ mk_error({mutually_recursive_constants, Consts}) -> [{letval, Ann, _, _} | _] = Consts, mk_t_err(pos(Ann), Msg); mk_error({invalid_const_id, Ann}) -> - Msg = "The name of the compile-time constant cannot have pattern matching nor type", + Msg = "The name of the compile-time constant cannot have pattern matching", mk_t_err(pos(Ann), Msg); mk_error({invalid_const_expr, ConstId}) -> Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 74bc2e8..d2953cd 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1222,7 +1222,7 @@ failing_contracts() -> ]) , ?TYPE_ERROR(toplevel_constants_in_interface, [<>, + "The name of the compile-time constant cannot have pattern matching">>, <>, < ]) , ?TYPE_ERROR(toplevel_constants_invalid_id, [<>, + "The name of the compile-time constant cannot have pattern matching">>, <>, - <> + "The name of the compile-time constant cannot have pattern matching">>, + <> ]) ]. diff --git a/test/contracts/toplevel_constants.aes b/test/contracts/toplevel_constants.aes index 4e82342..86ac9be 100644 --- a/test/contracts/toplevel_constants.aes +++ b/test/contracts/toplevel_constants.aes @@ -34,6 +34,7 @@ contract C = let c22 = N.nsconst let c23 = c01 let c24 = c11.name + let c25 : int = 1 entrypoint f01() = c01 entrypoint f02() = c02 @@ -59,4 +60,5 @@ contract C = entrypoint f22() = c22 entrypoint f23() = c23 entrypoint f24() = c24 + entrypoint f25() = c25 entrypoint fqual() = C.c01 diff --git a/test/contracts/toplevel_constants_invalid_id.aes b/test/contracts/toplevel_constants_invalid_id.aes index 3799fbb..719a932 100644 --- a/test/contracts/toplevel_constants_invalid_id.aes +++ b/test/contracts/toplevel_constants_invalid_id.aes @@ -1,4 +1,4 @@ contract C = let x::_ = [1,2,3,4] let y::(p = z::_) = [1,2,3,4] - let t : int = 1 + let q : bool = "str" -- 2.30.2 From ad851854bed14f36d2393b457eb2f893a7c203e0 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 19 Dec 2022 14:18:56 +0300 Subject: [PATCH 32/37] List valid exprs in the error message for invalid exprs --- src/aeso_ast_infer_types.erl | 4 +++- test/aeso_compiler_tests.erl | 30 ++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ce19668..b4b135d 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -3937,7 +3937,9 @@ mk_error({invalid_const_id, Ann}) -> mk_t_err(pos(Ann), Msg); mk_error({invalid_const_expr, ConstId}) -> Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]), - mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg); + Cxt = "You can only use the following expressions as constants: " + "literals, lists, tuples, maps, and other constants", + mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg, Cxt); mk_error({illegal_const_in_interface, Ann}) -> Msg = "Cannot define toplevel constants inside a contract interface", mk_t_err(pos(Ann), Msg); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index d2953cd..1bd84a0 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1230,25 +1230,35 @@ failing_contracts() -> ]) , ?TYPE_ERROR(toplevel_constants_invalid_expr, [<>, + "Invalid expression in the definition of the constant `c01`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c02`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c03`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c04`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c05`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c07`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c08`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c09`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <>, + "Invalid expression in the definition of the constant `c10`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>, <> + "Invalid expression in the definition of the constant `c11`\n" + "You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">> ]) , ?TYPE_ERROR(toplevel_constants_invalid_id, [< Date: Sat, 7 Jan 2023 18:43:20 +0300 Subject: [PATCH 33/37] Fix tests --- test/aeso_compiler_tests.erl | 5 +---- test/contracts/toplevel_constants_invalid_id.aes | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 1bd84a0..ed50821 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1264,10 +1264,7 @@ failing_contracts() -> [<>, <>, - <> + "The name of the compile-time constant cannot have pattern matching">> ]) ]. diff --git a/test/contracts/toplevel_constants_invalid_id.aes b/test/contracts/toplevel_constants_invalid_id.aes index 719a932..3b1820b 100644 --- a/test/contracts/toplevel_constants_invalid_id.aes +++ b/test/contracts/toplevel_constants_invalid_id.aes @@ -1,4 +1,3 @@ contract C = let x::_ = [1,2,3,4] let y::(p = z::_) = [1,2,3,4] - let q : bool = "str" -- 2.30.2 From 442c3a56257cabf46ad6de472605a3406dc1c75f Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 7 Jan 2023 19:01:48 +0300 Subject: [PATCH 34/37] Update the docs about constants --- docs/sophia_features.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 7ff4f0e..e8170c0 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -573,6 +573,32 @@ contract C = A hole expression found in the example above will generate the error `` Found a hole of type `(int) => int` ``. This says that the compiler expects a function from `int` to `int` in place of the `???` placeholder. +## Constants + +Constants in Sophia are contract-level bindings that can be used in either contracts or namespaces. The value of a constant can be a literal, another constant, or arithmetic operations applied to other constants. Lists, tuples, maps, and records can also be used to define a constant as long as their elements are also constants. + +The following visibility rules apply to constants: +* Constants defined inside a contract are private in that contract. Thus, cannot be accessed through instances of their defining contract. +* Constants defined inside a namespace are public. Thus, can be used in other contracts or namespaces. +* Constants cannot be defined inside a contract interface. + +When a constant is shadowed, it can be accessed using its qualified name: + +``` +contract C = + let c = 1 + entrypoint f() = + let c = 2 + c + C.c // the result is 3 +``` + +The name of the constant must be an id; therefore, no pattern matching is allowed when defining a constant: + +``` +contract C + let x::y::_ = [1,2,3] // this will result in an error +``` + ## Arithmetic Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following -- 2.30.2 From da6d4877d6d8967a30c63cfadbdfeeea67e0c628 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 7 Jan 2023 19:05:30 +0300 Subject: [PATCH 35/37] Update syntax docs --- docs/sophia_syntax.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index e57cc84..f0df9e6 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -104,6 +104,7 @@ Implement ::= ':' Sep1(Con, ',') Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias | 'record' Id ['(' TVar* ')'] '=' RecordType | 'datatype' Id ['(' TVar* ')'] '=' DataType + | 'let' Id [':' Type] '=' Expr | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) | Using -- 2.30.2 From 521b382dd77a935ddc1125d13fd9c56db1537898 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 7 Mar 2023 14:58:32 +0300 Subject: [PATCH 36/37] Check validity of const exprs in a separate functions --- src/aeso_ast_infer_types.erl | 99 +++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index b4b135d..9514dea 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1940,11 +1940,9 @@ infer_expr(Env, {list, As, Elems}) -> NewElems = [check_expr(Env, X, ElemType) || X <- Elems], {typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}}; infer_expr(Env, {list_comp, As, Yield, []}) -> - ban_when_const(Env), {typed, _, _, Type} = TypedYield = infer_expr(Env, Yield), {typed, As, {list_comp, As, TypedYield, []}, {app_t, As, {id, As, "list"}, [Type]}}; infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) -> - ban_when_const(Env), TypedBind = {typed, As2, _, TypeBExpr} = infer_expr(Env, BExpr), {NewE, TypedPat = {typed, _, _, PatType}} = infer_pattern(Env, Pat), unify( Env @@ -1958,7 +1956,6 @@ infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Pat, BExpr}|Rest]}) , {list_comp, As, TypedYield, [{comprehension_bind, TypedPat, TypedBind}|TypedRest]} , ResType}; infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Rest]}) -> - ban_when_const(Env), NewCond = check_expr(Env, Cond, {id, AttrsIF, "bool"}), {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = infer_expr(Env, {list_comp, AttrsL, Yield, Rest}), @@ -1967,7 +1964,6 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re , {list_comp, AttrsL, TypedYield, [{comprehension_if, AttrsIF, NewCond}|TypedRest]} , ResType}; infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> - ban_when_const(Env), NewE = {typed, _, _, PatType} = infer_expr(Env, E), BlockType = fresh_uvar(AsLV), {'case', _, NewPattern, [{guarded, _, [], NewRest}]} = @@ -1984,7 +1980,6 @@ infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> , ResType }; infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> - ban_when_const(Env), {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, AsLF, Name}, FunT, Env), @@ -2007,9 +2002,6 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> prefix -> infer_op(Env, Ann, Fun, Args, fun infer_prefix/1); _ -> - %% Applications of data constructors are allowed in constants - lists:member(element(1, Fun), [con, qcon]) orelse ban_when_const(Env), - NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], NewFun0 = infer_expr(Env, Fun), @@ -2035,14 +2027,12 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> {typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)} end; infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> - ban_when_const(Env), NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}), NewThen = {typed, _, _, ThenType} = infer_expr(Env, Then), NewElse = {typed, _, _, ElseType} = infer_expr(Env, Else), unify(Env, ThenType, ElseType, {if_branches, Then, ThenType, Else, ElseType}), {typed, Attrs, {'if', Attrs, NewCond, NewThen, NewElse}, ThenType}; infer_expr(Env, {switch, Attrs, Expr, Cases}) -> - ban_when_const(Env), NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr), SwitchType = fresh_uvar(Attrs), NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType) @@ -2068,7 +2058,6 @@ infer_expr(Env, {record, Attrs, Fields}) -> end || {Fld, {field, _, LV, {typed, _, _, T}}} <- lists:zip(Fields, NewFields)]), {typed, Attrs, {record, Attrs, NewFields}, RecordType}; infer_expr(Env, {record, Attrs, Record, Update}) -> - ban_when_const(Env), NewRecord = {typed, _, _, RecordType} = infer_expr(Env, Record), NewUpdate = [ check_record_update(Env, RecordType, Fld) || Fld <- Update ], {typed, Attrs, {record, Attrs, NewRecord, NewUpdate}, RecordType}; @@ -2084,7 +2073,6 @@ infer_expr(Env, {proj, Attrs, Record, FieldName}) -> {typed, Attrs, {proj, Attrs, NewRecord, FieldName}, FieldType}; %% Maps infer_expr(Env, {map_get, Attrs, Map, Key}) -> %% map lookup - ban_when_const(Env), KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), @@ -2092,7 +2080,6 @@ infer_expr(Env, {map_get, Attrs, Map, Key}) -> %% map lookup Key1 = check_expr(Env, Key, KeyType), {typed, Attrs, {map_get, Attrs, Map1, Key1}, ValType}; infer_expr(Env, {map_get, Attrs, Map, Key, Val}) -> %% map lookup with default - ban_when_const(Env), KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), @@ -2107,7 +2094,6 @@ infer_expr(Env, {map, Attrs, KVs}) -> %% map construction || {K, V} <- KVs ], {typed, Attrs, {map, Attrs, KVs1}, map_t(Attrs, KeyType, ValType)}; infer_expr(Env, {map, Attrs, Map, Updates}) -> %% map update - ban_when_const(Env), KeyType = fresh_uvar(Attrs), ValType = fresh_uvar(Attrs), MapType = map_t(Attrs, KeyType, ValType), @@ -2115,7 +2101,6 @@ infer_expr(Env, {map, Attrs, Map, Updates}) -> %% map update Updates1 = [ check_map_update(Env, Upd, KeyType, ValType) || Upd <- Updates ], {typed, Attrs, {map, Attrs, Map1, Updates1}, MapType}; infer_expr(Env, {block, Attrs, Stmts}) -> - ban_when_const(Env), BlockType = fresh_uvar(Attrs), NewStmts = infer_block(Env, Attrs, Stmts, BlockType), {typed, Attrs, {block, Attrs, NewStmts}, BlockType}; @@ -2124,15 +2109,12 @@ infer_expr(_Env, {record_or_map_error, Attrs, Fields}) -> Type = fresh_uvar(Attrs), {typed, Attrs, {record, Attrs, []}, Type}; infer_expr(Env, {record_or_map_error, Attrs, Expr, []}) -> - ban_when_const(Env), type_error({empty_record_or_map_update, {record, Attrs, Expr, []}}), infer_expr(Env, Expr); infer_expr(Env, {record_or_map_error, Attrs, Expr, Fields}) -> - ban_when_const(Env), type_error({mixed_record_and_map, {record, Attrs, Expr, Fields}}), infer_expr(Env, Expr); infer_expr(Env, {lam, Attrs, Args, Body}) -> - ban_when_const(Env), ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args], ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args], ResultType = fresh_uvar(Attrs), @@ -2141,20 +2123,89 @@ infer_expr(Env, {lam, Attrs, Args, Body}) -> NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> - ban_when_const(Env), NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern), {typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType}; infer_expr(Env, Let = {letval, Attrs, _, _}) -> - ban_when_const(Env), type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> - ban_when_const(Env), type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). -ban_when_const(#env{ current_const = none }) -> ok; -ban_when_const(#env{ current_const = Id }) -> type_error({invalid_const_expr, Id}). +check_valid_const_expr({bool, _, _}) -> + true; +check_valid_const_expr({int, _, _}) -> + true; +check_valid_const_expr({char, _, _}) -> + true; +check_valid_const_expr({string, _, _}) -> + true; +check_valid_const_expr({bytes, _, _}) -> + true; +check_valid_const_expr({account_pubkey, _, _}) -> + true; +check_valid_const_expr({oracle_pubkey, _, _}) -> + true; +check_valid_const_expr({oracle_query_id, _, _}) -> + true; +check_valid_const_expr({contract_pubkey, _, _}) -> + true; +check_valid_const_expr({id, _, "_"}) -> + true; +check_valid_const_expr({Tag, _, _}) when Tag == id; Tag == qid; Tag == con; Tag == qcon -> + true; +check_valid_const_expr({tuple, _, Cpts}) -> + lists:all(fun(X) -> X end, [ check_valid_const_expr(C) || C <- Cpts ]); +check_valid_const_expr({list, _, Elems}) -> + lists:all(fun(X) -> X end, [ check_valid_const_expr(Elem) || Elem <- Elems ]); +check_valid_const_expr({list_comp, _, _, _}) -> + false; +check_valid_const_expr({typed, _, Body, _}) -> + check_valid_const_expr(Body); +check_valid_const_expr({app, Ann, Fun, Args0}) -> + {_, Args} = split_args(Args0), + case aeso_syntax:get_ann(format, Ann) of + infix -> + lists:all(fun(X) -> X end, [ check_valid_const_expr(Arg) || Arg <- Args ]); + prefix -> + lists:all(fun(X) -> X end, [ check_valid_const_expr(Arg) || Arg <- Args ]); + _ -> + %% Applications of data constructors are allowed in constants + lists:member(element(1, Fun), [con, qcon]) + end; +check_valid_const_expr({'if', _, _, _, _}) -> + false; +check_valid_const_expr({switch, _, _, _}) -> + false; +check_valid_const_expr({record, _, Fields}) -> + lists:all(fun(X) -> X end, [ check_valid_const_expr(Expr) || {field, _, _, Expr} <- Fields ]); +check_valid_const_expr({record, _, _, _}) -> + false; +check_valid_const_expr({proj, _, Record, _}) -> + check_valid_const_expr(Record); +% Maps +check_valid_const_expr({map_get, _, _, _}) -> %% map lookup + false; +check_valid_const_expr({map_get, _, _, _, _}) -> %% map lookup with default + false; +check_valid_const_expr({map, _, KVs}) -> %% map construction + lists:all(fun(X) -> X end, [ check_valid_const_expr(K) andalso check_valid_const_expr(V) || {K, V} <- KVs ]); +check_valid_const_expr({map, _, _, _}) -> %% map update + false; +check_valid_const_expr({block, _, _}) -> + false; +check_valid_const_expr({record_or_map_error, _, Fields}) -> + lists:all(fun(X) -> X end, [ check_valid_const_expr(Expr) || {field, _, _, Expr} <- Fields ]); +check_valid_const_expr({record_or_map_error, _, _, _}) -> + false; +check_valid_const_expr({lam, _, _, _}) -> + false; +check_valid_const_expr({letpat, _, _, _}) -> + false; +check_valid_const_expr({letval, _, _, _}) -> + false; +check_valid_const_expr({letfun, _, _, _, _, _}) -> + false. infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> FunType = @@ -2322,9 +2373,11 @@ infer_block(Env, Attrs, [E|Rest], BlockType) -> [NewE|infer_block(Env, Attrs, Rest, BlockType)]. infer_const(Env, {letval, Ann, TypedId = {typed, _, Id = {id, _, _}, Type}, Expr}) -> + check_valid_const_expr(Expr) orelse type_error({invalid_const_expr, Id}), NewExpr = check_expr(Env#env{ current_const = Id }, Expr, Type), {letval, Ann, TypedId, NewExpr}; infer_const(Env, {letval, Ann, Id = {id, AnnId, _}, Expr}) -> + check_valid_const_expr(Expr) orelse type_error({invalid_const_expr, Id}), create_constraints(), NewExpr = {typed, _, _, Type} = infer_expr(Env#env{ current_const = Id }, Expr), solve_then_destroy_and_report_unsolved_constraints(Env), -- 2.30.2 From 274bb4e197efa2d4b025a6346d583e7c6a07795c Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Wed, 8 Mar 2023 11:12:26 +0300 Subject: [PATCH 37/37] Call both resolve_const and resolve_fun from resolve_var --- src/aeso_ast_to_fcode.erl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 43b8dd6..c83d01a 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -1728,13 +1728,21 @@ bind_var(Env = #{ vars := Vars }, X) -> Env#{ vars := [X | Vars] }. resolve_var(#{ vars := Vars } = Env, [X]) -> case lists:member(X, Vars) of true -> {var, X}; - false -> resolve_const(Env, [X]) + false -> + case resolve_const(Env, [X]) of + false -> resolve_fun(Env, [X]); + Const -> Const + end end; -resolve_var(Env, Q) -> resolve_const(Env, Q). +resolve_var(Env, Q) -> + case resolve_const(Env, Q) of + false -> resolve_fun(Env, Q); + Const -> Const + end. -resolve_const(#{ consts := Consts } = Env, Q) -> +resolve_const(#{ consts := Consts }, Q) -> case maps:get(Q, Consts, not_found) of - not_found -> resolve_fun(Env, Q); + not_found -> false; Val -> Val end. -- 2.30.2