From 7bac15949c436a0de89f0063637ceca00c50ceb8 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 1 Jun 2023 13:23:21 +0200 Subject: [PATCH 01/41] Introduce encode/decode_value to compiler (#457) --- CHANGELOG.md | 1 + src/aeso_compiler.erl | 95 ++++++++++++++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66a4b93..25b0b4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 contract C = let cc = 2 ``` +- API functions for encoding/decoding Sophia values to/from FATE. ### Changed ### Removed ### Fixed diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 31b4ef8..7a0d069 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -12,6 +12,8 @@ , file/2 , from_string/2 , check_call/4 + , decode_value/4 + , encode_value/4 , create_calldata/3 , create_calldata/4 , version/0 @@ -188,30 +190,55 @@ check_call(Source, FunName, Args, Options) -> check_call1(Source, FunName, Args, Options). check_call1(ContractString0, FunName, Args, Options) -> + case add_extra_call(ContractString0, {call, FunName, Args}, Options) of + {ok, CallName, Code} -> + {def, _, FcodeArgs} = get_call_body(CallName, Code), + {ok, FunName, [ aeso_fcode_to_fate:term_to_fate(A) || A <- FcodeArgs ]}; + Err = {error, _} -> + Err + end. + +add_extra_call(Contract0, Call, Options) -> try %% First check the contract without the __call function #{fcode := OrgFcode , fcode_env := #{child_con_env := ChildContracts} - , ast := Ast} = string_to_code(ContractString0, Options), + , ast := Ast} = string_to_code(Contract0, Options), {FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []), %% collect all hashes and compute the first name without hash collision to SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)), CallName = first_none_match(?CALL_NAME, SymbolHashes, lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)), - ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args), - #{fcode := Fcode} = string_to_code(ContractString, Options), - CallArgs = arguments_of_body(CallName, FunName, Fcode), - - {ok, FunName, CallArgs} + Contract = insert_call_function(Ast, Contract0, CallName, Call), + {ok, CallName, string_to_code(Contract, Options)} catch throw:{error, Errors} -> {error, Errors} end. -arguments_of_body(CallName, _FunName, Fcode) -> +get_call_body(CallName, #{fcode := Fcode}) -> #{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)), - {def, _FName, Args} = Body, - %% FName is either {entrypoint, list_to_binary(FunName)} or 'init' - [ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ]. + Body. + +encode_value(Contract0, Type, Value, Options) -> + case add_extra_call(Contract0, {value, Type, Value}, Options) of + {ok, CallName, Code} -> + Body = get_call_body(CallName, Code), + {ok, aeb_fate_encoding:serialize(aeso_fcode_to_fate:term_to_fate(Body))}; + Err = {error, _} -> + Err + end. + +decode_value(Contract0, Type, FateValue, Options) -> + case add_extra_call(Contract0, {type, Type}, Options) of + {ok, CallName, Code} -> + #{ unfolded_typed_ast := TypedAst + , type_env := TypeEnv} = Code, + {ok, _, Type0} = get_decode_type(CallName, TypedAst), + Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), + fate_data_to_sophia_value(Type0, Type1, FateValue); + Err = {error, _} -> + Err + end. first_none_match(_CallName, _Hashes, []) -> error(unable_to_find_unique_call_name); @@ -224,14 +251,31 @@ first_none_match(CallName, Hashes, [Char|Chars]) -> end. %% Add the __call function to a contract. --spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string(). -insert_call_function(Ast, Code, Call, FunName, Args) -> +-spec insert_call_function(aeso_syntax:ast(), string(), string(), + {call, string(), [string()]} | {value, string(), string()} | {type, string()}) -> string(). +insert_call_function(Ast, Code, Call, {call, FunName, Args}) -> Ind = last_contract_indent(Ast), lists:flatten( [ Code, "\n\n", lists:duplicate(Ind, " "), "stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n" + ]); +insert_call_function(Ast, Code, Call, {value, Type, Value}) -> + Ind = last_contract_indent(Ast), + lists:flatten( + [ Code, + "\n\n", + lists:duplicate(Ind, " "), + "entrypoint ", Call, "() : ", Type, " = ", Value, "\n" + ]); +insert_call_function(Ast, Code, Call, {type, Type}) -> + Ind = last_contract_indent(Ast), + lists:flatten( + [ Code, + "\n\n", + lists:duplicate(Ind, " "), + "entrypoint ", Call, "(val : ", Type, ") = val\n" ]). -spec insert_init_function(string(), options()) -> string(). @@ -274,22 +318,25 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> {ok, _, Type0} = get_decode_type(FunName, TypedAst), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), - try - {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} - catch throw:cannot_translate_to_sophia -> - Type1 = prettypr:format(aeso_pretty:type(Type0)), - Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s", - [aeb_fate_encoding:deserialize(Data), Type1]), - {error, [aeso_errors:new(data_error, Msg)]}; - _:_ -> - Type1 = prettypr:format(aeso_pretty:type(Type0)), - Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]), - {error, [aeso_errors:new(data_error, Msg)]} - end + fate_data_to_sophia_value(Type0, Type, Data) catch throw:{error, Errors} -> {error, Errors} end. +fate_data_to_sophia_value(Type, UnfoldedType, FateData) -> + try + {ok, aeso_vm_decode:from_fate(UnfoldedType, aeb_fate_encoding:deserialize(FateData))} + catch throw:cannot_translate_to_sophia -> + Type1 = prettypr:format(aeso_pretty:type(Type)), + Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s", + [aeb_fate_encoding:deserialize(FateData), Type1]), + {error, [aeso_errors:new(data_error, Msg)]}; + _:_ -> + Type1 = prettypr:format(aeso_pretty:type(Type)), + Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]), + {error, [aeso_errors:new(data_error, Msg)]} + end. + -spec create_calldata(string(), string(), [string()]) -> {ok, binary()} | {error, [aeso_errors:error()]}. create_calldata(Code, Fun, Args) -> From c3958496842aa89e63264900359055ff51cec09f Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 13 Jun 2023 14:36:48 +0300 Subject: [PATCH 02/41] Introduce debugging symbols (#424) * Add fann type and to_fann fun * Add fann() to funcall * Add fann() to closure * Add fann() to set_state * Add fann() to remote_u * Add fann() to remote * Add fann() to proj * Add fann() to set_proj * Add fann() to def and def_u * Add fann() to op * Add fann() to let * Add fann() to lam * Add fann() to builtin_u * Add missing functions specs * Dead code removal * Fix the spec for compute_state_layout * Add fann() to var * Add fann() to switch * Add fann() to lit and get_state * Add fann() to builtin * Add fann() to con * Add fann() to tuple * Add fann() to nil * Fix missing fann() in tuple fexpr() * Add dbgloc instruction to fate * Add instructions lines to the debugging result * Fix compiler tests * Fix calldata tests * Rname Ann to FAnn when the type is fann() * Add line to fann() * Change attributes for DBGLOC instruction * Add file to fann() * Add file to aeso_syntax:ann() * Fix dialyzer warning * Remove fann() from fsplit_pat() and fpat() * Fill out empty fann() when possible * Save debug locations for child contracts * Include DBGLOC instructions in the compiler output * Return an empty string instead of no_file atom * Wrap args of DBGLOC in immediate tuple * Upgrade aebytecode ref in rebar.config * Add DBG_DEF and DBG_UNDEF * Do not DBG_DEF vars with % prefix * Do not use DBG_DEF and DBG_UNDEF on args * Fix dbg_undef for args * Rename DBGLOC to DBG_LOC * Remove column from DBG_LOC * Add missing dbg_loc in to_scode1 * Keep a single DBG_LOC instruction per line * Remove col from fann * Add DBG_LOC op to step at function sig * Remove the variable-register map from debug output * Use get_value/3 to handle default * Use lookup instead of lookup_all * List only needed attributes * Make debug ops impure * Split complicated code and add comment * Fix annotations * Fix indenting * Remove dbg_loc before closure * Add dbg_loc in to_scode * Add DBG_CALL and DBG_RETURN * Separate the split at CALL_T and loop * Revert "Separate the split at CALL_T and loop" This reverts commit 4ea823a7ca798c756b20cee32f928f41092c4959. * Revert "Add DBG_CALL and DBG_RETURN" This reverts commit c406c6feb09b6a5bb859c38d634f08208c901e5a. * Disable tail call optimization for better debug call stack * Rename env.debug to env.debug_info * Upgrade aebytecode: Add DBG_CONTRACT * Add DBG_CONTRACT instruction * Check if a var name is fresh in separate function * Add DBG_CONTRACT and DBG_LOC before DBG_DEF * Save fresh names of pattern variables * Implement fsplit_pat_vars for assign * Set fann for switches * Revert "Save fresh names of pattern variables" This reverts commit d2473f982996336131477df2b2115c04a55a62cb. * Add DBG_DEF for switch pattern vars * Fix the inability to pattern match constructors * Upgrade aebytecode dep * Upgrade aebytecode dep * Update the lock file * Add annotations to fexpr var * Fix issues with pretty-printing of fexprs * Use FAnn instead of get_fann(Body) * Upgrade aebytecode version * Fix pp_fpat * Fix pattern matching on fpat * Update rename when a new rename comes up * Upgrade aebytecode * Remove the getopt dep * Fix calldata tests * Remove file committed by mistake * Remove location anns from contract call type --- CHANGELOG.md | 1 + docs/aeso_compiler.md | 2 - rebar.config | 3 +- rebar.lock | 6 +- src/aeso_ast_infer_types.erl | 4 +- src/aeso_ast_to_fcode.erl | 1193 ++++++++++++++++++++-------------- src/aeso_compiler.erl | 14 +- src/aeso_fcode_to_fate.erl | 310 +++++---- src/aeso_syntax.erl | 5 +- 9 files changed, 896 insertions(+), 642 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25b0b4c..c5cf36f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - API functions for encoding/decoding Sophia values to/from FATE. ### Changed ### Removed +- Remove the mapping from variables to FATE registers from the compilation output. ### Fixed - Warning about unused include when there is no include. diff --git a/docs/aeso_compiler.md b/docs/aeso_compiler.md index 61ee7aa..4798ad4 100644 --- a/docs/aeso_compiler.md +++ b/docs/aeso_compiler.md @@ -53,8 +53,6 @@ The **pp_** options all print to standard output the following: The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain. -The option `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers. - #### Options to control which compiler optimizations should run: By default all optimizations are turned on, to disable an optimization, it should be diff --git a/rebar.config b/rebar.config index d22de4b..e07433a 100644 --- a/rebar.config +++ b/rebar.config @@ -2,8 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.2.0"}}} - , {getopt, "1.0.1"} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.3.0"}}} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} ]}. diff --git a/rebar.lock b/rebar.lock index 85a9709..7f13b93 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,11 +1,11 @@ {"1.2.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"2a0a397afad6b45da52572170f718194018bf33c"}}, + {ref,"b38349274fc2bed98d7fe86877e6e1a2df302109"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", - {ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}}, + {ref,"177bf604b2a05e940f92cf00e96e6e269e708245"}}, 1}, {<<"base58">>, {git,"https://github.com/aeternity/erl-base58.git", @@ -16,7 +16,7 @@ {git,"https://github.com/aeternity/enacl.git", {ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}}, 2}, - {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1}, {<<"jsx">>, {git,"https://github.com/talentdeficit/jsx.git", {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 9514dea..6a71813 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -351,11 +351,11 @@ bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env) Sys = [{origin, system}], TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end, Fields = - [ {field_t, AnnF, Entrypoint, contract_call_type(Type)} + [ {field_t, AnnF, Entrypoint, contract_call_type(aeso_syntax:set_ann(Sys, Type))} || {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- Contents ] ++ [ {field_t, AnnF, Entrypoint, contract_call_type( - {fun_t, AnnF, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)}) + {fun_t, Sys, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)}) } || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents, Name =/= "init" diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index c83d01a..6890926 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -58,34 +58,36 @@ | {contract_code, string()} %% for CREATE, by name | {typerep, ftype()}. --type fexpr() :: {lit, flit()} - | nil - | {var, var_name()} - | {def, fun_name(), [fexpr()]} - | {remote, [ftype()], ftype(), fexpr(), fun_name(), [fexpr()]} - | {builtin, builtin(), [fexpr()]} - | {con, arities(), tag(), [fexpr()]} - | {tuple, [fexpr()]} - | {proj, fexpr(), integer()} - | {set_proj, fexpr(), integer(), fexpr()} %% tuple, field, new_value - | {op, op(), [fexpr()]} - | {'let', var_name(), fexpr(), fexpr()} - | {funcall, fexpr(), [fexpr()]} %% Call to unknown function - | {closure, fun_name(), fexpr()} - | {switch, fsplit()} - | {set_state, state_reg(), fexpr()} - | {get_state, state_reg()} +-type fann() :: [ {file, aeso_syntax:ann_file()} | {line, aeso_syntax:ann_line()} ]. + +-type fexpr() :: {lit, fann(), flit()} + | {nil, fann()} + | {var, fann(), var_name()} + | {def, fann(), fun_name(), [fexpr()]} + | {remote, fann(), [ftype()], ftype(), fexpr(), fun_name(), [fexpr()]} + | {builtin, fann(), builtin(), [fexpr()]} + | {con, fann(), arities(), tag(), [fexpr()]} + | {tuple, fann(), [fexpr()]} + | {proj, fann(), fexpr(), integer()} + | {set_proj, fann(), fexpr(), integer(), fexpr()} %% tuple, field, new_value + | {op, fann(), op(), [fexpr()]} + | {'let', fann(), var_name(), fexpr(), fexpr()} + | {funcall, fann(), fexpr(), [fexpr()]} %% Call to unknown function + | {closure, fann(), fun_name(), fexpr()} + | {switch, fann(), fsplit()} + | {set_state, fann(), state_reg(), fexpr()} + | {get_state, fann(), state_reg()} %% The following (unapplied top-level functions/builtins and %% lambdas) are generated by the fcode compiler, but translated %% to closures by the lambda lifter. - | {def_u, fun_name(), arity()} - | {remote_u, [ftype()], ftype(), fexpr(), fun_name()} - | {builtin_u, builtin(), arity()} - | {builtin_u, builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args. - | {lam, [var_name()], fexpr()}. + | {def_u, fann(), fun_name(), arity()} + | {remote_u, fann(), [ftype()], ftype(), fexpr(), fun_name()} + | {builtin_u, fann(), builtin(), arity()} + | {builtin_u, fann(), builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args. + | {lam, fann(), [var_name()], fexpr()}. -type fsplit() :: {split, ftype(), var_name(), [fcase()]} - | {nosplit, fexpr()}. + | {nosplit, [rename()], fexpr()}. %% Renames are needed to add DBG_DEF for switch pattern vars -type fcase() :: {'case', fsplit_pat(), fsplit()}. @@ -115,18 +117,21 @@ | bits | {variant, [[ftype()]]} | {function, [ftype()], ftype()} - | any | {tvar, var_name()}. + | any + | {tvar, var_name()}. --type fun_def() :: #{ attrs := [attribute()], +-type fun_def() :: #{ attrs := [attribute() | fann()], args := [{var_name(), ftype()}], return := ftype(), body := fexpr() }. +-type functions() :: #{ fun_name() => fun_def() }. + -type fcode() :: #{ contract_name := string(), state_type := ftype(), state_layout := state_layout(), event_type := ftype() | none, - functions := #{ fun_name() => fun_def() }, + functions := functions(), payable := boolean() }. -type type_def() :: fun(([ftype()]) -> ftype()). @@ -137,11 +142,14 @@ -record(con_tag, { tag :: tag(), arities :: arities() }). -type con_tag() :: #con_tag{}. --type type_env() :: #{ sophia_name() => type_def() }. --type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. --type con_env() :: #{ sophia_name() => con_tag() }. --type child_con_env() :: #{sophia_name() => fcode()}. --type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }. +-type expr_env() :: #{ var_name() => fexpr() }. +-type type_env() :: #{ sophia_name() => type_def() }. +-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. +-type con_env() :: #{ sophia_name() => con_tag() }. +-type child_con_env() :: #{ sophia_name() => fcode() }. +-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }. + +-type rename() :: [{var_name(), var_name()}]. -type context() :: {contract_def, string()} | {namespace, string()} @@ -188,6 +196,7 @@ ast_to_fcode(Code, Options) -> clear_fresh_names(Options), {Env3, FCode2}. +-spec optimize(fcode(), [option()]) -> fcode(). optimize(FCode1, Options) -> Verbose = lists:member(pp_fcode, Options), [io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose], @@ -286,6 +295,7 @@ builtins() -> || {NS, Funs} <- Scopes, {Fun, Arity} <- Funs ]). +-spec state_layout(env()) -> state_layout(). state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}). -define(type(T), fun([]) -> T end). @@ -322,12 +332,15 @@ init_type_env() -> ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) }. +-spec is_no_code(env()) -> boolean(). is_no_code(Env) -> get_option(no_code, Env). +-spec get_option(atom(), env()) -> option(). get_option(Opt, Env) -> get_option(Opt, Env, false). +-spec get_option(atom(), env(), option()) -> option(). get_option(Opt, Env, Default) -> proplists:get_value(Opt, maps:get(options, Env, []), Default). @@ -374,6 +387,15 @@ to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) -> Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls), to_fcode(Env1, Code). +-spec to_fann(aeso_syntax:ann()) -> fann(). +to_fann(Ann) -> + File = proplists:lookup(file, Ann), + Line = proplists:lookup(line, Ann), + [ X || X <- [File, Line], X =/= none ]. + +-spec get_fann(fexpr()) -> fann(). +get_fann(FExpr) -> element(2, FExpr). + -spec decls_to_fcode(env(), [aeso_syntax:decl()]) -> env(). decls_to_fcode(Env, Decls) -> %% First compute mapping from Sophia names to fun_names and add it to the @@ -386,8 +408,11 @@ decls_to_fcode(Env, Decls) -> decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) -> typedef_to_fcode(Env, Name, Args, Def); -decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) -> - Attrs = get_attributes(Ann), +decl_to_fcode(Env = #{ functions := Funs, options := Options }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) -> + Attrs = case proplists:get_value(debug_info, Options, false) of + true -> get_attributes_debug(Ann); + false -> get_attributes(Ann) + end, FName = lookup_fun(Env, qname(Env, Name)), FArgs = args_to_fcode(Env, Args), FRet = type_to_fcode(Env, Ret), @@ -442,6 +467,7 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> Env3 = compute_state_layout(Env2, Name, FDef), bind_type(Env3, Q, FDef). +-spec compute_state_layout(env(), string(), type_def()) -> env(). compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) -> NoLayout = get_option(no_flatten_state, Env), Layout = @@ -454,6 +480,7 @@ compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) -> Env#{ state_layout => Layout }; compute_state_layout(Env, _, _) -> Env. +-spec compute_state_layout(state_reg(), ftype() | [ftype()]) -> {state_reg(), state_layout() | [state_layout()]}. compute_state_layout(R, {tuple, [T]}) -> compute_state_layout(R, T); compute_state_layout(R, {tuple, Ts}) -> @@ -508,19 +535,23 @@ args_to_fcode(Env, Args) -> -define(make_let(X, Expr, Body), make_let(Expr, fun(X) -> Body end)). +-spec make_let(fexpr(), fun((fexpr()) -> fexpr())) -> fexpr(). make_let(Expr, Body) -> case Expr of - {var, _} -> Body(Expr); - {lit, {int, _}} -> Body(Expr); - {lit, {bool, _}} -> Body(Expr); + {var, _, _} -> Body(Expr); + {lit, _, {int, _}} -> Body(Expr); + {lit, _, {bool, _}} -> Body(Expr); _ -> X = fresh_name(), - {'let', X, Expr, Body({var, X})} + FAnn = get_fann(Expr), + {'let', FAnn, X, Expr, Body({var, FAnn, X})} end. -let_bind(X, {var, Y}, Body) -> rename([{X, Y}], Body); -let_bind(X, Expr, Body) -> {'let', X, Expr, Body}. +-spec let_bind(var_name(), fexpr(), fexpr()) -> fexpr(). +let_bind(X, {var, _, Y}, Body) -> rename([{X, Y}], Body); +let_bind(X, Expr, Body) -> {'let', get_fann(Expr), X, Expr, Body}. +-spec let_bind([{var_name(), fexpr()}], fexpr()) -> fexpr(). let_bind(Binds, Body) -> lists:foldr(fun({X, E}, Rest) -> let_bind(X, E, Rest) end, Body, Binds). @@ -534,50 +565,50 @@ expr_to_fcode(Env, Expr) -> -spec expr_to_fcode(env(), aeso_syntax:type() | no_type, aeso_syntax:expr()) -> fexpr(). %% Literals -expr_to_fcode(_Env, _Type, {int, _, N}) -> {lit, {int, N}}; -expr_to_fcode(_Env, _Type, {char, _, N}) -> {lit, {int, N}}; -expr_to_fcode(_Env, _Type, {bool, _, B}) -> {lit, {bool, B}}; -expr_to_fcode(_Env, _Type, {string, _, S}) -> {lit, {string, S}}; -expr_to_fcode(_Env, _Type, {account_pubkey, _, K}) -> {lit, {account_pubkey, K}}; -expr_to_fcode(_Env, _Type, {contract_pubkey, _, K}) -> {lit, {contract_pubkey, K}}; -expr_to_fcode(_Env, _Type, {oracle_pubkey, _, K}) -> {lit, {oracle_pubkey, K}}; -expr_to_fcode(_Env, _Type, {oracle_query_id, _, K}) -> {lit, {oracle_query_id, K}}; -expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}}; +expr_to_fcode(_Env, _Type, {int, Ann, N}) -> {lit, to_fann(Ann), {int, N}}; +expr_to_fcode(_Env, _Type, {char, Ann, N}) -> {lit, to_fann(Ann), {int, N}}; +expr_to_fcode(_Env, _Type, {bool, Ann, B}) -> {lit, to_fann(Ann), {bool, B}}; +expr_to_fcode(_Env, _Type, {string, Ann, S}) -> {lit, to_fann(Ann), {string, S}}; +expr_to_fcode(_Env, _Type, {account_pubkey, Ann, K}) -> {lit, to_fann(Ann), {account_pubkey, K}}; +expr_to_fcode(_Env, _Type, {contract_pubkey, Ann, K}) -> {lit, to_fann(Ann), {contract_pubkey, K}}; +expr_to_fcode(_Env, _Type, {oracle_pubkey, Ann, K}) -> {lit, to_fann(Ann), {oracle_pubkey, K}}; +expr_to_fcode(_Env, _Type, {oracle_query_id, Ann, K}) -> {lit, to_fann(Ann), {oracle_query_id, K}}; +expr_to_fcode(_Env, _Type, {bytes, Ann, B}) -> {lit, to_fann(Ann), {bytes, B}}; %% Variables -expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]); -expr_to_fcode(Env, Type, {qid, _, X}) -> - case resolve_var(Env, X) of - {builtin_u, B, Ar} when B =:= oracle_query; - B =:= oracle_get_question; - B =:= oracle_get_answer; - B =:= oracle_respond; - B =:= oracle_register; - B =:= oracle_check; - B =:= oracle_check_query -> +expr_to_fcode(Env, _Type, {id, Ann, X}) -> resolve_var(Env, Ann, [X]); +expr_to_fcode(Env, Type, {qid, Ann, X}) -> + case resolve_var(Env, Ann, X) of + {builtin_u, FAnn, B, Ar} when B =:= oracle_query; + B =:= oracle_get_question; + B =:= oracle_get_answer; + B =:= oracle_respond; + B =:= oracle_register; + B =:= oracle_check; + B =:= oracle_check_query -> OType = get_oracle_type(B, Type), {oracle, QType, RType} = type_to_fcode(Env, OType), - TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}], - {builtin_u, B, Ar, TypeArgs}; - {builtin_u, B = aens_resolve, Ar} -> + TypeArgs = [{lit, FAnn, {typerep, QType}}, {lit, FAnn, {typerep, RType}}], + {builtin_u, FAnn, B, Ar, TypeArgs}; + {builtin_u, FAnn, B = aens_resolve, Ar} -> {fun_t, _, _, _, ResType} = Type, AensType = type_to_fcode(Env, ResType), - TypeArgs = [{lit, {typerep, AensType}}], - {builtin_u, B, Ar, TypeArgs}; - {builtin_u, B = bytes_split, Ar} -> + TypeArgs = [{lit, FAnn, {typerep, AensType}}], + {builtin_u, FAnn, B, Ar, TypeArgs}; + {builtin_u, FAnn, B = bytes_split, Ar} -> {fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type, - {builtin_u, B, Ar, [{lit, {int, N}}]}; + {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; Other -> Other end; %% Constructors expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon -> expr_to_fcode(Env, Type, {app, [], {typed, [], Con, Type}, []}); -expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Args}) when C == con; C == qcon -> +expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, Ann, _} = Con, _}, Args}) when C == con; C == qcon -> #con_tag{ tag = I, arities = Arities } = lookup_con(Env, Con), Arity = lists:nth(I + 1, Arities), case length(Args) == Arity of - true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; + true -> {con, to_fann(Ann), Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; false -> internal_error({constructor_arity_mismatch, Con, length(Args), Arity}) end; @@ -586,18 +617,18 @@ expr_to_fcode(Env, _Type, {tuple, _, Es}) -> make_tuple([expr_to_fcode(Env, E) || E <- Es]); %% Records -expr_to_fcode(Env, Type, {proj, _Ann, Rec = {typed, _, _, RecType}, {id, _, X}}) -> +expr_to_fcode(Env, Type, {proj, Ann, Rec = {typed, _, _, RecType}, {id, _, X}}) -> case RecType of {con, _, _} when X == "address" -> - {op, contract_to_address, [expr_to_fcode(Env, Rec)]}; + {op, to_fann(Ann), contract_to_address, [expr_to_fcode(Env, Rec)]}; {con, _, _} -> {fun_t, _, _, Args, Ret} = Type, FArgs = [type_to_fcode(Env, Arg) || Arg <- Args], - {remote_u, FArgs, type_to_fcode(Env, Ret), expr_to_fcode(Env, Rec), + {remote_u, to_fann(Ann), FArgs, type_to_fcode(Env, Ret), expr_to_fcode(Env, Rec), {entrypoint, list_to_binary(X)}}; {record_t, [_]} -> expr_to_fcode(Env, Rec); %% Singleton record {record_t, _} -> - {proj, expr_to_fcode(Env, Rec), field_index(Rec, X)} + {proj, to_fann(Ann), expr_to_fcode(Env, Rec), field_index(RecType, X)} end; expr_to_fcode(Env, {record_t, [FieldT]}, {record, _Ann, [_] = Fields}) -> @@ -611,55 +642,56 @@ expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Fields}) -> end, make_tuple(lists:map(FVal, FieldTypes)); -expr_to_fcode(Env, {record_t, [FieldT]}, {record, _Ann, Rec, Fields}) -> +expr_to_fcode(Env, {record_t, [FieldT]}, {record, Ann, Rec, Fields}) -> case field_value(FieldT, Fields) of false -> expr_to_fcode(Env, Rec); {set, E} -> expr_to_fcode(Env, E); - {upd, Z, E} -> {'let', Z, expr_to_fcode(Env, Rec), expr_to_fcode(bind_var(Env, Z), E)} + {upd, Z, E} -> {'let', to_fann(Ann), Z, expr_to_fcode(Env, Rec), expr_to_fcode(bind_var(Env, Z), E)} end; -expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Rec, Fields}) -> +expr_to_fcode(Env, {record_t, FieldTypes}, {record, Ann, Rec, Fields}) -> X = fresh_name(), - Proj = fun(I) -> {proj, {var, X}, I - 1} end, + FAnn = to_fann(Ann), + Proj = fun(I) -> {proj, FAnn, {var, FAnn, X}, I - 1} end, Comp = fun({I, false}) -> Proj(I); ({_, {set, E}}) -> expr_to_fcode(Env, E); - ({I, {upd, Z, E}}) -> {'let', Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)} + ({I, {upd, Z, E}}) -> {'let', FAnn, Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)} end, Set = fun({_, false}, R) -> R; - ({I, {set, E}}, R) -> {set_proj, R, I - 1, expr_to_fcode(Env, E)}; - ({I, {upd, Z, E}}, R) -> {set_proj, R, I - 1, - {'let', Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)}} + ({I, {set, E}}, R) -> {set_proj, FAnn, R, I - 1, expr_to_fcode(Env, E)}; + ({I, {upd, Z, E}}, R) -> {set_proj, FAnn, R, I - 1, + {'let', FAnn, Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)}} end, Expand = length(Fields) == length(FieldTypes), Updates = [ {I, field_value(FT, Fields)} || {I, FT} <- indexed(FieldTypes) ], Body = case Expand of - true -> {tuple, lists:map(Comp, Updates)}; - false -> lists:foldr(Set, {var, X}, Updates) + true -> {tuple, FAnn, lists:map(Comp, Updates)}; + false -> lists:foldr(Set, {var, FAnn, X}, Updates) end, - {'let', X, expr_to_fcode(Env, Rec), Body}; + {'let', FAnn, X, expr_to_fcode(Env, Rec), Body}; %% Lists -expr_to_fcode(Env, _Type, {list, _, Es}) -> - lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end, - nil, Es); +expr_to_fcode(Env, _Type, {list, Ann, Es}) -> + lists:foldr(fun(E, L) -> {op, to_fann(aeso_syntax:get_ann(E)), '::', [expr_to_fcode(Env, E), L]} end, + {nil, to_fann(Ann)}, Es); -expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) -> - {def_u, FromTo, _} = resolve_fun(Env, ["ListInternal", "from_to"]), - {def, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}; +expr_to_fcode(Env, _Type, {app, As, {'..', _}, [A, B]}) -> + {def_u, FAnn, FromTo, _} = resolve_fun(Env, As, ["ListInternal", "from_to"]), + {def, FAnn, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}; -expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) -> - {op, '::', [expr_to_fcode(Env, Yield), nil]}; +expr_to_fcode(Env, _Type, {list_comp, As, Yield, []}) -> + {op, to_fann(As), '::', [expr_to_fcode(Env, Yield), {nil, to_fann(As)}]}; expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) -> Arg = fresh_name(), Env1 = bind_var(Env, Arg), - Bind = {lam, [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType}, - [{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]}, - {'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})}, - {def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]), - {def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]}; + Bind = {lam, to_fann(As), [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType}, + [{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]}, + {'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})}, + {def_u, FAnn, FlatMap, _} = resolve_fun(Env, As, ["ListInternal", "flat_map"]), + {def, FAnn, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]}; expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) -> make_if(expr_to_fcode(Env, Cond), expr_to_fcode(Env, Type, {list_comp, As, Yield, Rest}), - nil + {nil, to_fann(As)} ); expr_to_fcode(Env, Type, {list_comp, As, Yield, [LV = {letval, _, _, _}|Rest]}) -> expr_to_fcode(Env, Type, {block, As, [LV, {list_comp, As, Yield, Rest}]}); @@ -673,15 +705,15 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) -> expr_to_fcode(Env, Else)); %% Switch -expr_to_fcode(Env, _, S = {switch, _, Expr = {typed, _, E, Type}, Alts}) -> +expr_to_fcode(Env, _, S = {switch, Ann, Expr = {typed, _, E, Type}, Alts}) -> Switch = fun(X) -> - {switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)} + {switch, to_fann(Ann), alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)} end, case E of {id, _, X} -> Switch(X); _ -> X = fresh_name(), - {'let', X, expr_to_fcode(Env, Expr), + {'let', to_fann(Ann), X, expr_to_fcode(Env, Expr), Switch(X)} end; @@ -696,54 +728,56 @@ expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) -> case Op of '|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]}); - _ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]} + _ -> {op, to_fann(Ann), Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]} end; -expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> +expr_to_fcode(Env, _Type, {app, Ann, {Op, _}, [A]}) when is_atom(Op) -> + FAnn = to_fann(Ann), case Op of - '-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]}; - '!' -> {op, '!', [expr_to_fcode(Env, A)]} + '-' -> {op, FAnn, '-', [{lit, FAnn, {int, 0}}, expr_to_fcode(Env, A)]}; + '!' -> {op, FAnn, '!', [expr_to_fcode(Env, A)]} end; %% Function calls -expr_to_fcode(Env, _, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, Args}) -> +expr_to_fcode(Env, _, {app, _, Fun = {typed, Ann, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, Args}) -> Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of - {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs); - {builtin_u, chain_clone, _Ar} -> + {builtin_u, FAnn, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), FAnn, B, FArgs ++ TypeArgs); + {builtin_u, FAnn, chain_clone, _Ar} -> case ArgsT of var_args -> fcode_error({var_args_not_set, FunE}); _ -> %% Here we little cheat on the typechecker, but this inconsistency %% is to be solved in `aeso_fcode_to_fate:type_to_scode/1` FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]), - builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs]) + builtin_to_fcode(state_layout(Env), FAnn, chain_clone, [{lit, FAnn, FInitArgsT}|FArgs]) end; - {builtin_u, chain_create, _Ar} -> + {builtin_u, FAnn, chain_create, _Ar} -> case {ArgsT, Type} of {var_args, _} -> fcode_error({var_args_not_set, FunE}); {_, {con, _, Contract}} -> FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]), - builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); + builtin_to_fcode(state_layout(Env), FAnn, chain_create, [{lit, FAnn, {contract_code, Contract}}, {lit, FAnn, FInitArgsT}|FArgs]); {_, _} -> fcode_error({not_a_contract_type, Type}) end; - {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); - {def_u, F, _Ar} -> {def, F, FArgs}; - {remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs}; + {builtin_u, FAnn, B, _Ar} -> builtin_to_fcode(state_layout(Env), FAnn, B, FArgs); + {def_u, FAnn, F, _Ar} -> {def, FAnn, F, FArgs}; + {remote_u, FAnn, RArgsT, RRetT, Ct, RFun} -> {remote, FAnn, RArgsT, RRetT, Ct, RFun, FArgs}; FFun -> %% FFun is a closure, with first component the function name and %% second component the environment - Call = fun(X) -> {funcall, {proj, {var, X}, 0}, [{proj, {var, X}, 1} | FArgs]} end, + FAnn = to_fann(Ann), + Call = fun(X) -> {funcall, FAnn, {proj, FAnn, {var, FAnn, X}, 0}, [{proj, FAnn, {var, FAnn, X}, 1} | FArgs]} end, case FFun of - {var, X} -> Call(X); - _ -> X = fresh_name(), - {'let', X, FFun, Call(X)} + {var, _, X} -> Call(X); + _ -> X = fresh_name(), + {'let', FAnn, X, FFun, Call(X)} end end; %% Maps -expr_to_fcode(_Env, _Type, {map, _, []}) -> - {builtin, map_empty, []}; +expr_to_fcode(_Env, _Type, {map, Ann, []}) -> + {builtin, to_fann(Ann), map_empty, []}; expr_to_fcode(Env, Type, {map, Ann, KVs}) -> %% Cheaper to do incremental map_update than building the list and doing %% map_from_list (I think). @@ -753,59 +787,72 @@ expr_to_fcode(Env, _Type, {map, _, Map, KVs}) -> ?make_let(Map1, expr_to_fcode(Env, Map), lists:foldr(fun(Fld, M) -> case Fld of - {field, _, [{map_get, _, K}], V} -> - {op, map_set, [M, expr_to_fcode(Env, K), expr_to_fcode(Env, V)]}; - {field_upd, _, [MapGet], {typed, _, {lam, _, [{arg, _, {id, _, Z}, _}], V}, _}} when element(1, MapGet) == map_get -> + {field, Ann, [{map_get, _, K}], V} -> + {op, to_fann(Ann), map_set, [M, expr_to_fcode(Env, K), expr_to_fcode(Env, V)]}; + {field_upd, Ann, [MapGet], {typed, _, {lam, _, [{arg, _, {id, _, Z}, _}], V}, _}} when element(1, MapGet) == map_get -> [map_get, _, K | Default] = tuple_to_list(MapGet), ?make_let(Key, expr_to_fcode(Env, K), begin %% Z might shadow Map1 or Key Z1 = fresh_name(), + FAnn = to_fann(Ann), GetExpr = case Default of - [] -> {op, map_get, [Map1, Key]}; - [D] -> {op, map_get_d, [Map1, Key, expr_to_fcode(Env, D)]} + [] -> {op, FAnn, map_get, [Map1, Key]}; + [D] -> {op, FAnn, map_get_d, [Map1, Key, expr_to_fcode(Env, D)]} end, - {'let', Z1, GetExpr, - {op, map_set, [M, Key, rename([{Z, Z1}], expr_to_fcode(bind_var(Env, Z), V))]}} + {'let', FAnn, Z1, GetExpr, + {op, FAnn, map_set, [M, Key, rename([{Z, Z1}], expr_to_fcode(bind_var(Env, Z), V))]}} end) end end, Map1, KVs)); -expr_to_fcode(Env, _Type, {map_get, _, Map, Key}) -> - {op, map_get, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key)]}; -expr_to_fcode(Env, _Type, {map_get, _, Map, Key, Def}) -> - {op, map_get_d, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key), expr_to_fcode(Env, Def)]}; +expr_to_fcode(Env, _Type, {map_get, Ann, Map, Key}) -> + {op, to_fann(Ann), map_get, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key)]}; +expr_to_fcode(Env, _Type, {map_get, Ann, Map, Key, Def}) -> + {op, to_fann(Ann), map_get_d, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key), expr_to_fcode(Env, Def)]}; -expr_to_fcode(Env, _Type, {lam, _, Args, Body}) -> +expr_to_fcode(Env, _Type, {lam, Ann, Args, Body}) -> GetArg = fun({arg, _, {id, _, X}, _}) -> X end, Xs = lists:map(GetArg, Args), - {lam, Xs, expr_to_fcode(bind_vars(Env, Xs), Body)}; + {lam, to_fann(Ann), Xs, expr_to_fcode(bind_vars(Env, Xs), Body)}; expr_to_fcode(_Env, Type, Expr) -> error({todo, {Expr, ':', Type}}). -make_if({var, X}, Then, Else) -> - {switch, {split, boolean, X, - [{'case', {bool, false}, {nosplit, Else}}, - {'case', {bool, true}, {nosplit, Then}}]}}; +-spec make_if(fexpr(), fexpr(), fexpr()) -> fexpr(). +make_if({var, FAnn, X}, Then, Else) -> + {switch, FAnn, {split, boolean, X, + [{'case', {bool, false}, {nosplit, [], Else}}, + {'case', {bool, true}, {nosplit, [], Then}}]}}; make_if(Cond, Then, Else) -> X = fresh_name(), - {'let', X, Cond, make_if({var, X}, Then, Else)}. + FAnn = get_fann(Cond), + {'let', FAnn, X, Cond, make_if({var, FAnn, X}, Then, Else)}. -make_if_no_else({var, X}, Then) -> - {switch, {split, boolean, X, - [{'case', {bool, true}, {nosplit, Then}}]}}; +-spec make_if_no_else(fexpr(), fexpr()) -> fexpr(). +make_if_no_else({var, FAnn, X}, Then) -> + {switch, FAnn, {split, boolean, X, + [{'case', {bool, true}, {nosplit, [], Then}}]}}; make_if_no_else(Cond, Then) -> X = fresh_name(), - {'let', X, Cond, make_if_no_else({var, X}, Then)}. + FAnn = get_fann(Cond), + {'let', FAnn, X, Cond, make_if_no_else({var, FAnn, X}, Then)}. -spec make_tuple([fexpr()]) -> fexpr(). make_tuple([E]) -> E; -make_tuple(Es) -> {tuple, Es}. +make_tuple(Es) -> {tuple, [], Es}. + +-spec make_tuple_fpat([fpat()]) -> fpat(). +make_tuple_fpat([P]) -> P; +make_tuple_fpat(Ps) -> {tuple, Ps}. -spec strip_singleton_tuples(ftype()) -> ftype(). -strip_singleton_tuples({tuple, [T]}) -> strip_singleton_tuples(T); +strip_singleton_tuples({tuple, _, [T]}) -> strip_singleton_tuples(T); strip_singleton_tuples(T) -> T. +-spec get_oracle_type(OracleFun, FunT) -> OracleType when + OracleFun :: atom(), + FunT :: aeso_syntax:type(), + OracleType :: aeso_syntax:type(). get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType; get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType; get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType; @@ -827,11 +874,13 @@ alts_to_fcode(Env, Type, X, Alts, Switch) -> | {bool, false | true} | {int, integer()} | {string, binary()} - | nil | {'::', fpat(), fpat()} + | nil + | {'::', fpat(), fpat()} | {tuple, [fpat()]} | {con, arities(), tag(), [fpat()]} | {assign, fpat(), fpat()}. +-spec remove_guards(env(), [aeso_syntax:alt()], aeso_syntax:expr()) -> [falt()]. remove_guards(_Env, [], _Switch) -> []; remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) -> @@ -878,7 +927,7 @@ split_tree(Env, Vars, Alts = [{'case', Pats, Body} | _]) -> Ys = [ Y || {var, Y} <- Pats ], Ren = [ {Y, X} || {Y, X} <- lists:zip(Ys, Xs), X /= Y, Y /= "_" ], %% TODO: Unreachable clauses error - {nosplit, rename(Ren, Body)}; + {nosplit, Ren, rename(Ren, Body)}; I when is_integer(I) -> {Vars0, [{X, Type} | Vars1]} = lists:split(I - 1, Vars), Type1 = strip_singleton_tuples(Type), @@ -890,10 +939,13 @@ split_tree(Env, Vars, Alts = [{'case', Pats, Body} | _]) -> {split, Type1, X, Cases} end. --spec merge_alts(integer(), var_name(), [{fsplit_pat(), falt()}]) -> [{fsplit_pat(), [falt()]}]. +-spec merge_alts(integer(), var_name(), Alts) -> [{fsplit_pat(), [falt()]}] when + Alts :: [{fsplit_pat(), falt()}]. merge_alts(I, X, Alts) -> merge_alts(I, X, Alts, []). +-spec merge_alts(integer(), var_name(), Alts, Alts) -> [{fsplit_pat(), [falt()]}] when + Alts :: [{fsplit_pat(), falt()}]. merge_alts(I, X, Alts, Alts1) -> lists:foldr(fun(A, As) -> merge_alt(I, X, A, As) end, Alts1, Alts). @@ -907,7 +959,7 @@ merge_alt(I, X, {P, A}, [{Q, As} | Rest]) -> ({bool, B}, {bool, B}) -> match; ({int, N}, {int, N}) -> match; ({string, S}, {string, S}) -> match; - (nil, nil) -> match; + (nil, nil) -> match; ({'::', _, _}, {'::', _, _}) -> match; ({con, _, C, _}, {con, _, C, _}) -> match; ({con, _, _, _}, {con, _, _, _}) -> mismatch; @@ -924,6 +976,7 @@ merge_alt(I, X, {P, A}, [{Q, As} | Rest]) -> insert -> [{P, [A]}, {Q, As} | Rest] end. +-spec expand(integer(), var_name(), fsplit_pat(), fsplit_pat(), falt()) -> term(). expand(I, X, P, Q, Case = {'case', Ps, E}) -> {Ps0, [{var, Y} | Ps1]} = lists:split(I - 1, Ps), {Ps0r, Ren1} = rename_fpats([{Y, X} || Y /= X], Ps0), @@ -956,11 +1009,11 @@ split_alt(I, {'case', Pats, Body}) -> {SPat, {'case', Pats0 ++ InnerPats ++ Pats1, Body}}. -spec split_pat(fpat()) -> {fsplit_pat(), [fpat()]}. -split_pat(P = {var, _}) -> {{var, fresh_name()}, [P]}; -split_pat({bool, B}) -> {{bool, B}, []}; -split_pat({int, N}) -> {{int, N}, []}; -split_pat({string, N}) -> {{string, N}, []}; -split_pat(nil) -> {nil, []}; +split_pat(P = {var, _}) -> {{var, fresh_name()}, [P]}; +split_pat({bool, B}) -> {{bool, B}, []}; +split_pat({int, N}) -> {{int, N}, []}; +split_pat({string, N}) -> {{string, N}, []}; +split_pat(nil) -> {nil, []}; split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]}; split_pat({con, As, I, Pats}) -> Xs = [fresh_name() || _ <- Pats], @@ -975,7 +1028,7 @@ split_pat({tuple, Pats}) -> split_vars({bool, _}, boolean) -> []; split_vars({int, _}, integer) -> []; split_vars({string, _}, string) -> []; -split_vars(nil, {list, _}) -> []; +split_vars(nil, {list, _}) -> []; split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}]; split_vars({assign, X, P}, T) -> [{X, T}, {P, T}]; split_vars({con, _, I, Xs}, {variant, Cons}) -> @@ -1013,7 +1066,7 @@ pat_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Pats}) when C #con_tag{tag = I, arities = As} = lookup_con(Env, Con), {con, As, I, [pat_to_fcode(Env, Pat) || Pat <- Pats]}; pat_to_fcode(Env, _Type, {tuple, _, Pats}) -> - make_tuple([ pat_to_fcode(Env, Pat) || Pat <- Pats ]); + make_tuple_fpat([ pat_to_fcode(Env, Pat) || Pat <- Pats ]); pat_to_fcode(_Env, _Type, {bool, _, B}) -> {bool, B}; pat_to_fcode(_Env, _Type, {int, _, N}) -> {int, N}; pat_to_fcode(_Env, _Type, {char, _, N}) -> {int, N}; @@ -1031,7 +1084,7 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) -> {set, Pat} -> Pat %% {upd, _, _} is impossible in patterns end end, - make_tuple([pat_to_fcode(Env, FieldPat(Field)) + make_tuple_fpat([pat_to_fcode(Env, FieldPat(Field)) || Field <- Fields]); pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) -> {assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)}; @@ -1041,6 +1094,12 @@ pat_to_fcode(_Env, Type, Pat) -> %% -- Decision trees for boolean operators -- +-type decision_tree() :: false + | true + | {atom, fexpr()} + | {'if', fexpr(), decision_tree(), decision_tree()}. + +-spec decision_op(aeso_syntax:op(), decision_tree(), decision_tree()) -> decision_tree(). decision_op('&&', {atom, A}, B) -> {'if', A, B, false}; decision_op('&&', false, _) -> false; decision_op('&&', true, B) -> B; @@ -1050,26 +1109,28 @@ decision_op('||', true, _) -> true; decision_op(Op, {'if', A, Then, Else}, B) -> {'if', A, decision_op(Op, Then, B), decision_op(Op, Else, B)}. +-spec expr_to_decision_tree(env(), aeso_syntax:expr()) -> decision_tree(). expr_to_decision_tree(Env, {app, _Ann, {Op, _}, [A, B]}) when Op == '&&'; Op == '||' -> decision_op(Op, expr_to_decision_tree(Env, A), expr_to_decision_tree(Env, B)); expr_to_decision_tree(Env, {typed, _, Expr, _}) -> expr_to_decision_tree(Env, Expr); expr_to_decision_tree(Env, Expr) -> {atom, expr_to_fcode(Env, Expr)}. -decision_tree_to_fcode(false) -> {lit, {bool, false}}; -decision_tree_to_fcode(true) -> {lit, {bool, true}}; +-spec decision_tree_to_fcode(decision_tree()) -> fexpr(). +decision_tree_to_fcode(false) -> {lit, [], {bool, false}}; +decision_tree_to_fcode(true) -> {lit, [], {bool, true}}; decision_tree_to_fcode({atom, B}) -> B; decision_tree_to_fcode({'if', A, Then, Else}) -> X = fresh_name(), - {'let', X, A, - {switch, {split, boolean, X, [{'case', {bool, false}, {nosplit, decision_tree_to_fcode(Else)}}, - {'case', {bool, true}, {nosplit, decision_tree_to_fcode(Then)}}]}}}. + {'let', get_fann(A), X, A, + {switch, get_fann(A), {split, boolean, X, [{'case', {bool, false}, {nosplit, [], decision_tree_to_fcode(Else)}}, + {'case', {bool, true}, {nosplit, [], decision_tree_to_fcode(Then)}}]}}}. %% -- Statements -- -spec stmts_to_fcode(env(), [aeso_syntax:stmt()]) -> fexpr(). -stmts_to_fcode(Env, [{letval, _, {typed, _, {id, _, X}, _}, Expr} | Stmts]) -> - {'let', X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)}; +stmts_to_fcode(Env, [{letval, Ann, {typed, _, {id, _, X}, _}, Expr} | Stmts]) -> + {'let', to_fann(Ann), X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)}; stmts_to_fcode(Env, [{letval, Ann, Pat, Expr} | Stmts]) -> expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [{guarded, Ann, [], {block, Ann, Stmts}}]}]}); stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Expr}]} | Stmts]) -> @@ -1077,15 +1138,17 @@ stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Ex {typed, Ann1, Id, T} -> {arg, Ann1, Id, T}; _ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared end || Arg <- Args ], - {'let', X, expr_to_fcode(Env, {lam, Ann, LamArgs, Expr}), + {'let', to_fann(Ann), X, expr_to_fcode(Env, {lam, Ann, LamArgs, Expr}), stmts_to_fcode(bind_var(Env, X), Stmts)}; stmts_to_fcode(Env, [Expr]) -> expr_to_fcode(Env, Expr); stmts_to_fcode(Env, [Expr | Stmts]) -> - {'let', "_", expr_to_fcode(Env, Expr), stmts_to_fcode(Env, Stmts)}. + {'let', to_fann(aeso_syntax:get_ann(Expr)), "_", expr_to_fcode(Env, Expr), stmts_to_fcode(Env, Stmts)}. %% -- Builtins -- +-spec op_builtins() -> [BuiltinFun] when + BuiltinFun :: atom(). op_builtins() -> [map_from_list, map_to_list, map_delete, map_member, map_size, stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list, @@ -1105,47 +1168,52 @@ op_builtins() -> mcl_bls12_381_int_to_fr, mcl_bls12_381_int_to_fp, mcl_bls12_381_fr_to_int, mcl_bls12_381_fp_to_int ]. -set_state({reg, R}, Val) -> - {set_state, R, Val}; -set_state({tuple, Ls}, Val) -> +-spec set_state(state_layout(), fann(), fexpr()) -> fexpr(). +set_state({reg, R}, FAnn, Val) -> + {set_state, FAnn, R, Val}; +set_state({tuple, Ls}, FAnn, Val) -> ?make_let(X, Val, lists:foldr(fun({I, L}, Code) -> - {'let', "_", set_state(L, {proj, X, I - 1}), Code} - end, {tuple, []}, indexed(Ls))). + {'let', FAnn, "_", set_state(L, FAnn, {proj, FAnn, X, I - 1}), Code} + end, {tuple, FAnn, []}, indexed(Ls))). -get_state({reg, R}) -> - {get_state, R}; -get_state({tuple, Ls}) -> - {tuple, [get_state(L) || L <- Ls]}. +-spec get_state(state_layout(), fann()) -> fexpr(). +get_state({reg, R}, FAnn) -> + {get_state, FAnn, R}; +get_state({tuple, Ls}, FAnn) -> + {tuple, FAnn, [get_state(L, FAnn) || L <- Ls]}. -builtin_to_fcode(Layout, set_state, [Val]) -> - set_state(Layout, Val); -builtin_to_fcode(Layout, get_state, []) -> - get_state(Layout); -builtin_to_fcode(_Layout, require, [Cond, Msg]) -> - make_if(Cond, {tuple, []}, {builtin, abort, [Msg]}); -builtin_to_fcode(_Layout, chain_event, [Event]) -> - {def, event, [Event]}; -builtin_to_fcode(_Layout, map_delete, [Key, Map]) -> - {op, map_delete, [Map, Key]}; -builtin_to_fcode(_Layout, map_member, [Key, Map]) -> - {op, map_member, [Map, Key]}; -builtin_to_fcode(_Layout, map_lookup, [Key0, Map0]) -> +-spec builtin_to_fcode(state_layout(), fann(), BuiltinFun, [fexpr()]) -> fexpr() when + BuiltinFun :: atom(). %% No need to mention all of them +builtin_to_fcode(Layout, FAnn, set_state, [Val]) -> + set_state(Layout, FAnn, Val); +builtin_to_fcode(Layout, FAnn, get_state, []) -> + get_state(Layout, FAnn); +builtin_to_fcode(_Layout, FAnn, require, [Cond, Msg]) -> + make_if(Cond, {tuple, FAnn, []}, {builtin, FAnn, abort, [Msg]}); +builtin_to_fcode(_Layout, FAnn, chain_event, [Event]) -> + {def, FAnn, event, [Event]}; +builtin_to_fcode(_Layout, FAnn, map_delete, [Key, Map]) -> + {op, FAnn, map_delete, [Map, Key]}; +builtin_to_fcode(_Layout, FAnn, map_member, [Key, Map]) -> + {op, FAnn, map_member, [Map, Key]}; +builtin_to_fcode(_Layout, FAnn, map_lookup, [Key0, Map0]) -> ?make_let(Key, Key0, ?make_let(Map, Map0, - make_if({op, map_member, [Map, Key]}, - {con, [0, 1], 1, [{op, map_get, [Map, Key]}]}, - {con, [0, 1], 0, []}))); -builtin_to_fcode(_Layout, map_lookup_default, [Key, Map, Def]) -> - {op, map_get_d, [Map, Key, Def]}; -builtin_to_fcode(_Layout, Builtin, Args) -> + make_if({op, FAnn, map_member, [Map, Key]}, + {con, FAnn, [0, 1], 1, [{op, FAnn, map_get, [Map, Key]}]}, + {con, FAnn, [0, 1], 0, []}))); +builtin_to_fcode(_Layout, FAnn, map_lookup_default, [Key, Map, Def]) -> + {op, FAnn, map_get_d, [Map, Key, Def]}; +builtin_to_fcode(_Layout, FAnn, Builtin, Args) -> case lists:member(Builtin, op_builtins()) of - true -> {op, Builtin, Args}; - false -> {builtin, Builtin, Args} + true -> {op, FAnn, Builtin, Args}; + false -> {builtin, FAnn, Builtin, Args} end. %% -- Init function -- +-spec add_init_function(env(), functions()) -> functions(). add_init_function(Env, Funs0) -> case is_no_code(Env) of true -> Funs0; @@ -1154,10 +1222,11 @@ add_init_function(Env, Funs0) -> InitName = {entrypoint, <<"init">>}, InitFun = #{ body := InitBody} = maps:get(InitName, Funs), Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []}, - body => builtin_to_fcode(state_layout(Env), set_state, [InitBody]) } }, + body => builtin_to_fcode(state_layout(Env), [], set_state, [InitBody]) } }, Funs1 end. +-spec add_default_init_function(env(), functions()) -> functions(). add_default_init_function(_Env, Funs) -> InitName = {entrypoint, <<"init">>}, case maps:get(InitName, Funs, none) of @@ -1165,38 +1234,40 @@ add_default_init_function(_Env, Funs) -> Funs#{ InitName => #{attrs => [], args => [], return => {tuple, []}, - body => {tuple, []}} }; + body => {tuple, [], []}} }; _ -> Funs end. %% -- Event function -- +-spec add_event_function(env(), ftype() | none, functions()) -> functions(). add_event_function(_Env, none, Funs) -> Funs; add_event_function(Env, EventFType, Funs) -> Funs#{ event => event_function(Env, EventFType) }. +-spec event_function(env(), ftype()) -> fun_def(). event_function(_Env = #{event_type := {variant_t, EventCons}}, EventType = {variant, FCons}) -> Cons = [ {Name, I - 1, proplists:get_value(indices, Ann)} || {I, {constr_t, Ann, {con, _, Name}, _}} <- indexed(EventCons) ], Arities = [length(Ts) || Ts <- FCons], Case = fun({Name, Tag, Ixs}) -> {ok, HashValue} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Name)), - Hash = {lit, {bytes, HashValue}}, + Hash = {lit, [], {bytes, HashValue}}, Vars = [ "arg" ++ integer_to_list(I) || I <- lists:seq(1, length(Ixs)) ], IVars = lists:zip(Ixs, Vars), Payload = case [ V || {notindexed, V} <- IVars ] of - [] -> {lit, {string, <<>>}}; - [V] -> {var, V} + [] -> {lit, [], {string, <<>>}}; + [V] -> {var, [], V} end, - Indices = [ {var, V} || {indexed, V} <- IVars ], - Body = {builtin, chain_event, [Payload, Hash | Indices]}, - {'case', {con, Arities, Tag, Vars}, {nosplit, Body}} + Indices = [ {var, [], V} || {indexed, V} <- IVars ], + Body = {builtin, [], chain_event, [Payload, Hash | Indices]}, + {'case', {con, [], Arities, Tag, Vars}, {nosplit, [], Body}} end, #{ attrs => [private], args => [{"e", EventType}], return => {tuple, []}, - body => {switch, {split, EventType, "e", lists:map(Case, Cons)}} }. + body => {switch, [], {split, EventType, "e", lists:map(Case, Cons)}} }. %% -- Lambda lifting --------------------------------------------------------- %% The expr_to_fcode compiler lambda expressions to {lam, Xs, Body}, but in @@ -1211,18 +1282,25 @@ lambda_lift(FCode = #{ functions := Funs, state_layout := StateLayout }) -> FCode#{ functions := maps:merge(Funs1, NewFuns) }. -define(lambda_key, '%lambdalifted'). + +-spec init_lambda_funs() -> term(). init_lambda_funs() -> put(?lambda_key, #{}). + +-spec get_lambda_funs() -> term(). get_lambda_funs() -> erase(?lambda_key). +-spec add_lambda_fun(fun_def()) -> fun_name(). add_lambda_fun(Def) -> Name = fresh_fun(), Funs = get(?lambda_key), put(?lambda_key, Funs#{ Name => Def }), Name. +-spec lambda_lift_fun(state_layout(), fun_def()) -> fun_def(). lambda_lift_fun(Layout, Def = #{ body := Body }) -> Def#{ body := lambda_lift_expr(Layout, Body) }. +-spec lifted_fun([var_name()], [var_name()], fexpr()) -> fun_def(). lifted_fun([Z], Xs, Body) -> #{ attrs => [private], args => [{Z, any} | [{X, any} || X <- Xs]], @@ -1230,65 +1308,73 @@ lifted_fun([Z], Xs, Body) -> body => Body }; lifted_fun(FVs, Xs, Body) -> Z = "%env", - Proj = fun({I, Y}, E) -> {'let', Y, {proj, {var, Z}, I - 1}, E} end, + FAnn = get_fann(Body), + Proj = fun({I, Y}, E) -> {'let', FAnn, Y, {proj, FAnn, {var, FAnn, Z}, I - 1}, E} end, #{ attrs => [private], args => [{Z, any} | [{X, any} || X <- Xs]], return => any, body => lists:foldr(Proj, Body, indexed(FVs)) }. +-spec make_closure([var_name()], [var_name()], fexpr()) -> Closure when + Closure :: fexpr(). make_closure(FVs, Xs, Body) -> Fun = add_lambda_fun(lifted_fun(FVs, Xs, Body)), - Tup = fun([Y]) -> Y; (Ys) -> {tuple, Ys} end, - {closure, Fun, Tup([{var, Y} || Y <- FVs])}. + FAnn = get_fann(Body), + Tup = fun([Y]) -> Y; (Ys) -> {tuple, FAnn, Ys} end, + {closure, FAnn, Fun, Tup([{var, FAnn, Y} || Y <- FVs])}. -lambda_lift_expr(Layout, {lam, Xs, Body}) -> - FVs = free_vars({lam, Xs, Body}), +-spec lambda_lift_expr(state_layout(), fexpr()) -> Closure when + Closure :: fexpr(). +lambda_lift_expr(Layout, L = {lam, _, Xs, Body}) -> + FVs = free_vars(L), make_closure(FVs, Xs, lambda_lift_expr(Layout, Body)); lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u -> - [Tag, F, Ar | _] = tuple_to_list(UExpr), + [Tag, _, F, Ar | _] = tuple_to_list(UExpr), ExtraArgs = case UExpr of - {builtin_u, _, _, TypeArgs} -> TypeArgs; - _ -> [] + {builtin_u, _, _, _, TypeArgs} -> TypeArgs; + _ -> [] end, Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], - Args = [{var, X} || X <- Xs] ++ ExtraArgs, + Args = [{var, get_fann(UExpr), X} || X <- Xs] ++ ExtraArgs, Body = case Tag of - builtin_u -> builtin_to_fcode(Layout, F, Args); - def_u -> {def, F, Args} + builtin_u -> builtin_to_fcode(Layout, get_fann(UExpr), F, Args); + def_u -> {def, get_fann(UExpr), F, Args} end, make_closure([], Xs, Body); -lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) -> +lambda_lift_expr(Layout, {remote_u, FAnn, ArgsT, RetT, Ct, F}) -> FVs = free_vars(Ct), Ct1 = lambda_lift_expr(Layout, Ct), NamedArgCount = 3, Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ], - Args = [{var, X} || X <- Xs], - make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args}); + Args = [{var, [], X} || X <- Xs], + make_closure(FVs, Xs, {remote, FAnn, ArgsT, RetT, Ct1, F, Args}); lambda_lift_expr(Layout, Expr) -> case Expr of - {lit, _} -> Expr; - nil -> Expr; - {var, _} -> Expr; - {closure, _, _} -> Expr; - {def, D, As} -> {def, D, lambda_lift_exprs(Layout, As)}; - {builtin, B, As} -> {builtin, B, lambda_lift_exprs(Layout, As)}; - {remote, ArgsT, RetT, Ct, F, As} -> {remote, ArgsT, RetT, lambda_lift_expr(Layout, Ct), F, lambda_lift_exprs(Layout, As)}; - {con, Ar, C, As} -> {con, Ar, C, lambda_lift_exprs(Layout, As)}; - {tuple, As} -> {tuple, lambda_lift_exprs(Layout, As)}; - {proj, A, I} -> {proj, lambda_lift_expr(Layout, A), I}; - {set_proj, A, I, B} -> {set_proj, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)}; - {op, Op, As} -> {op, Op, lambda_lift_exprs(Layout, As)}; - {'let', X, A, B} -> {'let', X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)}; - {funcall, A, Bs} -> {funcall, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)}; - {set_state, R, A} -> {set_state, R, lambda_lift_expr(Layout, A)}; - {get_state, _} -> Expr; - {switch, S} -> {switch, lambda_lift_expr(Layout, S)}; - {split, Type, X, Alts} -> {split, Type, X, lambda_lift_exprs(Layout, Alts)}; - {nosplit, A} -> {nosplit, lambda_lift_expr(Layout, A)}; - {'case', P, S} -> {'case', P, lambda_lift_expr(Layout, S)} + {lit, _, _} -> Expr; + {nil, _} -> Expr; + {var, _, _} -> Expr; + {closure, _, _, _} -> Expr; + {def, FAnn, D, As} -> {def, FAnn, D, lambda_lift_exprs(Layout, As)}; + {builtin, FAnn, B, As} -> {builtin, FAnn, B, lambda_lift_exprs(Layout, As)}; + {remote, FAnn, ArgsT, RetT, Ct, F, As} -> {remote, FAnn, ArgsT, RetT, lambda_lift_expr(Layout, Ct), F, lambda_lift_exprs(Layout, As)}; + {con, FAnn, Ar, C, As} -> {con, FAnn, Ar, C, lambda_lift_exprs(Layout, As)}; + {tuple, FAnn, As} -> {tuple, FAnn, lambda_lift_exprs(Layout, As)}; + {proj, FAnn, A, I} -> {proj, FAnn, lambda_lift_expr(Layout, A), I}; + {set_proj, FAnn, A, I, B} -> {set_proj, FAnn, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)}; + {op, FAnn, Op, As} -> {op, FAnn, Op, lambda_lift_exprs(Layout, As)}; + {'let', FAnn, X, A, B} -> {'let', FAnn, X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)}; + {funcall, FAnn, A, Bs} -> {funcall, FAnn, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)}; + {set_state, FAnn, R, A} -> {set_state, FAnn, R, lambda_lift_expr(Layout, A)}; + {get_state, _, _} -> Expr; + {switch, FAnn, S} -> {switch, FAnn, lambda_lift_expr(Layout, S)}; + {split, Type, X, Alts} -> {split, Type, X, lambda_lift_exprs(Layout, Alts)}; + {nosplit, Rens, A} -> {nosplit, Rens, lambda_lift_expr(Layout, A)}; + {'case', P, S} -> {'case', P, lambda_lift_expr(Layout, S)} end. +-spec lambda_lift_exprs(state_layout(), [fexpr()]) -> [Closure] when + Closure :: fexpr(). lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As]. %% -- Optimisations ---------------------------------------------------------- @@ -1325,68 +1411,82 @@ optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) -> %% --- Inlining --- -spec inliner(fcode(), fun_name(), fexpr()) -> fexpr(). -inliner(Fcode, Fun, {def, Fun1, Args} = E) when Fun1 /= Fun -> +inliner(Fcode, Fun, {def, _, Fun1, Args} = E) when Fun1 /= Fun -> case should_inline(Fcode, Fun1) of false -> E; true -> inline(Fcode, Fun1, Args) end; inliner(_Fcode, _Fun, E) -> E. +-spec should_inline(fcode(), fun_name()) -> boolean(). should_inline(_Fcode, _Fun1) -> false == list_to_atom("true"). %% Dialyzer -inline(_Fcode, Fun, Args) -> {def, Fun, Args}. %% TODO +-spec inline(fcode(), fun_name(), Args) -> Def when + Args :: [fexpr()], + Def :: fexpr(). +inline(_Fcode, Fun, Args) -> {def, [], Fun, Args}. %% TODO %% --- Bind subexpressions --- -define(make_lets(Xs, Es, Body), make_lets(Es, fun(Xs) -> Body end)). +-spec bind_subexpressions(fexpr()) -> fexpr(). bind_subexpressions(Expr) -> bottom_up(fun bind_subexpressions/2, Expr). -bind_subexpressions(_, {tuple, Es}) -> - ?make_lets(Xs, Es, {tuple, Xs}); -bind_subexpressions(_, {set_proj, A, I, B}) -> - ?make_lets([X, Y], [A, B], {set_proj, X, I, Y}); +-spec bind_subexpressions(expr_env(), fexpr()) -> fexpr(). +bind_subexpressions(_, {tuple, FAnn, Es}) -> + ?make_lets(Xs, Es, {tuple, FAnn, Xs}); +bind_subexpressions(_, {set_proj, FAnn, A, I, B}) -> + ?make_lets([X, Y], [A, B], {set_proj, FAnn, X, I, Y}); bind_subexpressions(_, E) -> E. +-spec make_lets([fexpr()], fun(([fexpr()]) -> fexpr())) -> fexpr(). make_lets(Es, Body) -> make_lets(Es, [], Body). +-spec make_lets([fexpr()], [fexpr()], fun(([fexpr()]) -> fexpr())) -> fexpr(). make_lets([], Xs, Body) -> Body(lists:reverse(Xs)); -make_lets([{var, _} = E | Es], Xs, Body) -> +make_lets([{var, _, _} = E | Es], Xs, Body) -> make_lets(Es, [E | Xs], Body); -make_lets([{lit, _} = E | Es], Xs, Body) -> +make_lets([{lit, _, _} = E | Es], Xs, Body) -> make_lets(Es, [E | Xs], Body); make_lets([E | Es], Xs, Body) -> ?make_let(X, E, make_lets(Es, [X | Xs], Body)). %% --- Inline local functions --- +-spec inline_local_functions(fexpr()) -> fexpr(). inline_local_functions(Expr) -> bottom_up(fun inline_local_functions/2, Expr). -inline_local_functions(Env, {funcall, {proj, {var, Y}, 0}, [{proj, {var, Y}, 1} | Args]} = Expr) -> +-spec inline_local_functions(expr_env(), fexpr()) -> fexpr(). +inline_local_functions(Env, {funcall, _, {proj, _, {var, _, Y}, 0}, [{proj, _, {var, _, Y}, 1} | Args]} = Expr) -> %% TODO: Don't always inline local funs? case maps:get(Y, Env, free) of - {lam, Xs, Body} -> let_bind(lists:zip(Xs, Args), Body); - _ -> Expr + {lam, _, Xs, Body} -> let_bind(lists:zip(Xs, Args), Body); + _ -> Expr end; inline_local_functions(_, Expr) -> Expr. %% --- Let-floating --- +-spec let_floating(fexpr()) -> fexpr(). let_floating(Expr) -> bottom_up(fun let_float/2, Expr). -let_float(_, {'let', X, E, Body}) -> - pull_out_let({'let', X, {here, E}, Body}); -let_float(_, {proj, E, I}) -> - pull_out_let({proj, {here, E}, I}); -let_float(_, {set_proj, E, I, V}) -> - pull_out_let({set_proj, {here, E}, I, {here, V}}); -let_float(_, {op, Op, Es}) -> +-spec let_float(expr_env(), fexpr()) -> fexpr(). +let_float(_, {'let', FAnn, X, E, Body}) -> + pull_out_let({'let', FAnn, X, {here, E}, Body}); +let_float(_, {proj, FAnn, E, I}) -> + pull_out_let({proj, FAnn, {here, E}, I}); +let_float(_, {set_proj, FAnn, E, I, V}) -> + pull_out_let({set_proj, FAnn, {here, E}, I, {here, V}}); +let_float(_, {op, FAnn, Op, Es}) -> {Lets, Es1} = pull_out_let([{here, E} || E <- Es]), - let_bind(Lets, {op, Op, Es1}); + let_bind(Lets, {op, FAnn, Op, Es1}); let_float(_, E) -> E. +-spec pull_out_let(fexpr() | [fexpr()]) -> fexpr() | {Lets, [fexpr()]} when + Lets :: [{var_name(), fexpr()}]. pull_out_let(Expr) when is_tuple(Expr) -> {Lets, Es} = pull_out_let(tuple_to_list(Expr)), Inner = list_to_tuple(Es), @@ -1406,9 +1506,13 @@ pull_out_let(Es) when is_list(Es) -> end. %% Also renames the variables to fresh names +-spec let_view(fexpr()) -> {Lets, fexpr()} when + Lets :: [{var_name(), fexpr()}]. let_view(E) -> let_view(E, [], []). -let_view({'let', X, E, Rest}, Ren, Lets) -> +-spec let_view(fexpr(), rename(), Lets) -> {Lets, fexpr()} when + Lets :: [{var_name(), fexpr()}]. +let_view({'let', _, X, E, Rest}, Ren, Lets) -> Z = fresh_name(), let_view(Rest, [{X, Z} | Ren], [{Z, rename(Ren, E)} | Lets]); let_view(E, Ren, Lets) -> @@ -1420,62 +1524,63 @@ let_view(E, Ren, Lets) -> simplifier(Expr) -> bottom_up(fun simplify/2, Expr). --spec simplify(#{var_name() => fexpr()}, fexpr()) -> fexpr(). +-spec simplify(expr_env(), fexpr()) -> fexpr(). %% (e₀, .., en).i -> %% let _ = e₀ in .. let x = ei in .. let _ = en in x -simplify(_Env, {proj, {tuple, Es}, I}) -> +simplify(_Env, {proj, FAnn, {tuple, _, Es}, I}) -> It = lists:nth(I + 1, Es), X = fresh_name(), Dup = safe_to_duplicate(It), - Val = if Dup -> It; true -> {var, X} end, + Val = if Dup -> It; true -> {var, FAnn, X} end, lists:foldr( fun({J, E}, Rest) when I == J -> case Dup of true -> Rest; - false -> {'let', X, E, Rest} + false -> {'let', FAnn, X, E, Rest} end; ({_, E}, Rest) -> case read_only(E) of true -> Rest; - false -> {'let', "_", E, Rest} + false -> {'let', FAnn, "_", E, Rest} end end, Val, indexed(Es)); %% let x = e in .. x.i .. -simplify(Env, {proj, {var, X}, I} = Expr) -> - case simpl_proj(Env, I, {var, X}) of +simplify(Env, {proj, _, Var = {var, _, _}, I} = Expr) -> + case simpl_proj(Env, I, Var) of false -> Expr; E -> E end; -simplify(Env, {switch, Split}) -> - case simpl_switch(Env, [], Split) of - nomatch -> {builtin, abort, [{lit, {string, <<"Incomplete patterns">>}}]}; - stuck -> {switch, Split}; +simplify(Env, {switch, FAnn, Split}) -> + case simpl_switch(Env, FAnn, [], Split) of + nomatch -> {builtin, FAnn, abort, [{lit, FAnn, {string, <<"Incomplete patterns">>}}]}; Expr -> Expr end; simplify(_, E) -> E. +-spec simpl_proj(expr_env(), integer(), fexpr()) -> fexpr() | false. simpl_proj(Env, I, Expr) -> IfSafe = fun(E) -> case safe_to_duplicate(E) of true -> E; false -> false end end, case Expr of - false -> false; - {var, X} -> simpl_proj(Env, I, maps:get(X, Env, false)); - {tuple, Es} -> IfSafe(lists:nth(I + 1, Es)); - {set_proj, _, I, Val} -> IfSafe(Val); - {set_proj, E, _, _} -> simpl_proj(Env, I, E); - {proj, E, J} -> simpl_proj(Env, I, simpl_proj(Env, J, E)); - _ -> false + false -> false; + {var, _, X} -> simpl_proj(Env, I, maps:get(X, Env, false)); + {tuple, _, Es} -> IfSafe(lists:nth(I + 1, Es)); + {set_proj, _, _, I, Val} -> IfSafe(Val); + {set_proj, _, E, _, _} -> simpl_proj(Env, I, E); + {proj, _, E, J} -> simpl_proj(Env, I, simpl_proj(Env, J, E)); + _ -> false end. +-spec get_catchalls([fcase()]) -> [fcase()]. get_catchalls(Alts) -> - [ C || C = {'case', {var, _}, _} <- Alts ]. + [ C || C = {'case', {var, _, _}, _} <- Alts ]. %% The scode compiler can't handle multiple catch-alls, so we need to nest them %% inside each other. Instead of @@ -1485,6 +1590,7 @@ get_catchalls(Alts) -> %% _ => switch(x) %% .. %% _ => e +-spec add_catchalls([fcase()], [fcase()]) -> [fcase()]. add_catchalls(Alts, []) -> Alts; add_catchalls(Alts, Catchalls) -> case lists:splitwith(fun({'case', {var, _}, _}) -> false; (_) -> true end, @@ -1494,118 +1600,120 @@ add_catchalls(Alts, Catchalls) -> %% NOTE: relies on catchalls always being at the end end. -nest_catchalls([C = {'case', {var, _}, {nosplit, _}} | _]) -> C; +-spec nest_catchalls([fcase()]) -> fcase(). +nest_catchalls([C = {'case', {var, _}, {nosplit, _, _}} | _]) -> C; nest_catchalls([{'case', P = {var, _}, {split, Type, X, Alts}} | Catchalls]) -> {'case', P, {split, Type, X, add_catchalls(Alts, Catchalls)}}. -simpl_switch(_Env, _, {nosplit, E}) -> E; -simpl_switch(Env, Catchalls, {split, Type, X, Alts}) -> +-spec simpl_switch(expr_env(), fann(), [fcase()], fsplit()) -> fexpr() | nomatch. +simpl_switch(_Env, _FAnn, _, {nosplit, _, E}) -> E; +simpl_switch(Env, FAnn, Catchalls, {split, Type, X, Alts}) -> Alts1 = add_catchalls(Alts, Catchalls), - Stuck = {switch, {split, Type, X, Alts1}}, - case constructor_form(Env, {var, X}) of + Stuck = {switch, FAnn, {split, Type, X, Alts1}}, + case constructor_form(Env, {var, [], X}) of false -> Stuck; - E -> - case simpl_case(Env, E, Alts1) of - stuck -> Stuck; - Res -> Res - end + E -> simpl_case(Env, E, Alts1) end. +-spec simpl_case(expr_env(), fexpr(), [fcase()]) -> fexpr() | nomatch. simpl_case(_, _, []) -> nomatch; simpl_case(Env, E, [{'case', Pat, Body} | Alts]) -> case match_pat(Pat, E) of false -> simpl_case(Env, E, Alts); Binds -> Env1 = maps:merge(Env, maps:from_list(Binds)), - case simpl_switch(Env1, get_catchalls(Alts), Body) of + case simpl_switch(Env1, get_fann(E), get_catchalls(Alts), Body) of nomatch -> simpl_case(Env, E, Alts); - stuck -> stuck; Body1 -> let_bind(Binds, Body1) end end. -spec match_pat(fsplit_pat(), fexpr()) -> false | [{var_name(), fexpr()}]. -match_pat({tuple, Xs}, {tuple, Es}) -> lists:zip(Xs, Es); -match_pat({con, _, C, Xs}, {con, _, C, Es}) -> lists:zip(Xs, Es); -match_pat(L, {lit, L}) -> []; -match_pat(nil, nil) -> []; -match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}]; -match_pat({var, X}, E) -> [{X, E}]; -match_pat({assign, X, P}, E) -> [{X, E}, {P, E}]; -match_pat(_, _) -> false. +match_pat({tuple, Xs}, {tuple, _, Es}) -> lists:zip(Xs, Es); +match_pat({con, _, C, Xs}, {con, _, _, C, Es}) -> lists:zip(Xs, Es); +match_pat(L, {lit, _, L}) -> []; +match_pat(nil, {nil, _}) -> []; +match_pat({'::', X, Y}, {op, _, '::', [A, B]}) -> [{X, A}, {Y, B}]; +match_pat({var, X}, E) -> [{X, E}]; +match_pat({assign, X, P}, E) -> [{X, E}, {P, E}]; +match_pat(_, _) -> false. +-spec constructor_form(expr_env(), fexpr()) -> fexpr() | false. constructor_form(Env, Expr) -> case Expr of - {var, X} -> + {var, _, X} -> case maps:get(X, Env, free) of free -> false; E -> constructor_form(Env, E) %% TODO: shadowing? end; - {set_proj, E, I, V} -> + {set_proj, _, E, I, V} -> case constructor_form(Env, E) of - {tuple, Es} -> {tuple, setnth(I + 1, V, Es)}; - _ -> false + {tuple, FAnn, Es} -> {tuple, FAnn, setnth(I + 1, V, Es)}; + _ -> false end; - {proj, E, I} -> + {proj, _, E, I} -> case constructor_form(Env, E) of - {tuple, Es} -> constructor_form(Env, lists:nth(I + 1, Es)); - _ -> false + {tuple, _, Es} -> constructor_form(Env, lists:nth(I + 1, Es)); + _ -> false end; - {con, _, _, _} -> Expr; - {tuple, _} -> Expr; - {lit, _} -> Expr; - nil -> Expr; - {op, '::', _} -> Expr; - _ -> false + {con, _, _, _, _} -> Expr; + {tuple, _, _} -> Expr; + {lit, _, _} -> Expr; + {nil, _} -> Expr; + {op, _, '::', _} -> Expr; + _ -> false end. %% --- Drop unused lets --- +-spec drop_unused_lets(fexpr()) -> fexpr(). drop_unused_lets(Expr) -> bottom_up(fun drop_unused_lets/2, Expr). -drop_unused_lets(_, {'let', X, E, Body} = Expr) -> +-spec drop_unused_lets(expr_env(), fexpr()) -> fexpr(). +drop_unused_lets(_, {'let', FAnn, X, E, Body} = Expr) -> case {read_only(E), not lists:member(X, free_vars(Body))} of {true, true} -> Body; - {false, true} -> {'let', "_", E, Body}; + {false, true} -> {'let', FAnn, "_", E, Body}; _ -> Expr end; drop_unused_lets(_, Expr) -> Expr. %% -- Static analysis -------------------------------------------------------- -safe_to_duplicate({lit, _}) -> true; -safe_to_duplicate({var, _}) -> true; -safe_to_duplicate(nil) -> true; -safe_to_duplicate({tuple, []}) -> true; -safe_to_duplicate(_) -> false. +-spec safe_to_duplicate(fexpr()) -> boolean(). +safe_to_duplicate({lit, _, _}) -> true; +safe_to_duplicate({var, _, _}) -> true; +safe_to_duplicate({nil, _}) -> true; +safe_to_duplicate({tuple, _, []}) -> true; +safe_to_duplicate(_) -> false. -spec read_only(fexpr() | fsplit() | fcase() | [fexpr()] | [fcase()]) -> boolean(). -read_only({lit, _}) -> true; -read_only({var, _}) -> true; -read_only(nil) -> true; -read_only({con, _, _, Es}) -> read_only(Es); -read_only({tuple, Es}) -> read_only(Es); -read_only({proj, E, _}) -> read_only(E); -read_only({set_proj, A, _, B}) -> read_only([A, B]); -read_only({op, _, Es}) -> read_only(Es); -read_only({get_state, _}) -> true; -read_only({set_state, _, _}) -> false; -read_only({def_u, _, _}) -> true; -read_only({remote_u, _, _, _, _}) -> true; -read_only({builtin_u, _, _}) -> true; -read_only({builtin_u, _, _, _}) -> true; -read_only({lam, _, _}) -> true; -read_only({def, _, _}) -> false; %% TODO: purity analysis -read_only({remote, _, _, _, _, _}) -> false; -read_only({builtin, _, _}) -> false; %% TODO: some builtins are -read_only({switch, Split}) -> read_only(Split); -read_only({split, _, _, Cases}) -> read_only(Cases); -read_only({nosplit, E}) -> read_only(E); -read_only({'case', _, Split}) -> read_only(Split); -read_only({'let', _, A, B}) -> read_only([A, B]); -read_only({funcall, _, _}) -> false; -read_only({closure, _, _}) -> internal_error(no_closures_here); -read_only(Es) when is_list(Es) -> lists:all(fun read_only/1, Es). +read_only({lit, _, _}) -> true; +read_only({var, _, _}) -> true; +read_only({nil, _}) -> true; +read_only({con, _, _, _, Es}) -> read_only(Es); +read_only({tuple, _, Es}) -> read_only(Es); +read_only({proj, _, E, _}) -> read_only(E); +read_only({set_proj, _, A, _, B}) -> read_only([A, B]); +read_only({op, _, _, Es}) -> read_only(Es); +read_only({get_state, _, _}) -> true; +read_only({set_state, _, _, _}) -> false; +read_only({def_u, _, _, _}) -> true; +read_only({remote_u, _, _, _, _, _}) -> true; +read_only({builtin_u, _, _, _}) -> true; +read_only({builtin_u, _, _, _, _}) -> true; +read_only({lam, _, _, _}) -> true; +read_only({def, _, _, _}) -> false; %% TODO: purity analysis +read_only({remote, _, _, _, _, _, _}) -> false; +read_only({builtin, _, _, _}) -> false; %% TODO: some builtins are +read_only({switch, _, Split}) -> read_only(Split); +read_only({split, _, _, Cases}) -> read_only(Cases); +read_only({nosplit, _, E}) -> read_only(E); +read_only({'case', _, Split}) -> read_only(Split); +read_only({'let', _, _, A, B}) -> read_only([A, B]); +read_only({funcall, _, _, _}) -> false; +read_only({closure, _, _, _}) -> internal_error(no_closures_here); +read_only(Es) when is_list(Es) -> lists:all(fun read_only/1, Es). %% --- Deadcode elimination --- @@ -1615,12 +1723,15 @@ eliminate_dead_code(Code = #{ functions := Funs }) -> Code#{ functions := maps:filter(fun(Name, _) -> maps:is_key(Name, UsedFuns) end, Funs) }. --spec used_functions(#{ fun_name() => fun_def() }) -> #{ fun_name() => true }. +-spec used_functions(functions()) -> Used when + Used :: #{ fun_name() => true }. used_functions(Funs) -> Exported = [ Fun || {Fun, #{ attrs := Attrs }} <- maps:to_list(Funs), not lists:member(private, Attrs) ], used_functions(#{}, Exported, Funs). +-spec used_functions(Used, [fun_name()], functions()) -> Used when + Used :: #{ fun_name() => true }. used_functions(Used, [], _) -> Used; used_functions(Used, [Name | Rest], Defs) -> case maps:is_key(Name, Used) of @@ -1681,6 +1792,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> FunEnv1 = maps:from_list(lists:flatmap(Entry, Decls)), Env#{ fun_env := maps:merge(FunEnv, FunEnv1) }. +-spec make_fun_name(env(), aeso_syntax:ann(), aeso_syntax:name()) -> fun_name(). make_fun_name(#{ context := Context }, Ann, Name) -> Entrypoint = proplists:get_value(entrypoint, Ann, false), case Context of @@ -1720,23 +1832,26 @@ lookup_con(#{ con_env := ConEnv }, Con) -> Tag -> Tag end. +-spec bind_vars(env(), [var_name()]) -> env(). bind_vars(Env, Xs) -> lists:foldl(fun(X, E) -> bind_var(E, X) end, Env, Xs). +-spec bind_var(env(), var_name()) -> env(). bind_var(Env = #{ vars := Vars }, X) -> Env#{ vars := [X | Vars] }. -resolve_var(#{ vars := Vars } = Env, [X]) -> +-spec resolve_var(env(), aeso_syntax:ann(), [aeso_syntax:name()]) -> fexpr(). +resolve_var(#{ vars := Vars } = Env, Ann, [X]) -> case lists:member(X, Vars) of - true -> {var, X}; + true -> {var, to_fann(Ann), X}; false -> case resolve_const(Env, [X]) of - false -> resolve_fun(Env, [X]); + false -> resolve_fun(Env, Ann, [X]); Const -> Const end end; -resolve_var(Env, Q) -> +resolve_var(Env, Ann, Q) -> case resolve_const(Env, Q) of - false -> resolve_fun(Env, Q); + false -> resolve_fun(Env, Ann, Q); Const -> Const end. @@ -1746,25 +1861,30 @@ resolve_const(#{ consts := Consts }, Q) -> Val -> Val end. -resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) -> +-spec resolve_fun(env(), aeso_syntax:ann(), [aeso_syntax:name()]) -> fexpr(). +resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Ann, Q) -> case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of {not_found, not_found} -> internal_error({unbound_variable, Q}); - {_, {B, none}} -> builtin_to_fcode(state_layout(Env), B, []); - {_, {B, Ar}} -> {builtin_u, B, Ar}; - {{Fun, Ar}, _} -> {def_u, Fun, Ar} + {_, {B, none}} -> builtin_to_fcode(state_layout(Env), to_fann(Ann), B, []); + {_, {B, Ar}} -> {builtin_u, to_fann(Ann), B, Ar}; + {{Fun, Ar}, _} -> {def_u, to_fann(Ann), Fun, Ar} end. +-spec init_fresh_names([option()]) -> term(). init_fresh_names(Options) -> proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(), put('%fresh', 0). +-spec clear_fresh_names([option()]) -> term(). clear_fresh_names(Options) -> proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(), erase('%fresh'). +-spec init_saved_fresh_names() -> term(). init_saved_fresh_names() -> put(saved_fresh_names, #{}). +-spec clear_saved_fresh_names() -> term(). clear_saved_fresh_names() -> erase(saved_fresh_names). @@ -1809,96 +1929,103 @@ fsplit_pat_vars({string, _}) -> []; fsplit_pat_vars(nil) -> []; fsplit_pat_vars({'::', P, Q}) -> [P, Q]; fsplit_pat_vars({tuple, Ps}) -> Ps; -fsplit_pat_vars({con, _, _, Ps}) -> Ps. +fsplit_pat_vars({con, _, _, Ps}) -> Ps; +fsplit_pat_vars({assign, X, P}) -> [X, P]. +-spec free_vars(fexpr() | [fexpr()]) -> [var_name()]. free_vars(Xs) when is_list(Xs) -> lists:umerge([ free_vars(X) || X <- Xs ]); free_vars(Expr) -> case Expr of - {var, X} -> [X]; - {lit, _} -> []; - nil -> []; - {def, _, As} -> free_vars(As); - {def_u, _, _} -> []; - {remote, _, _, Ct, _, As} -> free_vars([Ct | As]); - {remote_u, _, _, Ct, _} -> free_vars(Ct); - {builtin, _, As} -> free_vars(As); - {builtin_u, _, _} -> []; - {builtin_u, _, _, _} -> []; %% Typereps are always literals - {con, _, _, As} -> free_vars(As); - {tuple, As} -> free_vars(As); - {proj, A, _} -> free_vars(A); - {set_proj, A, _, B} -> free_vars([A, B]); - {op, _, As} -> free_vars(As); - {'let', X, A, B} -> free_vars([A, {lam, [X], B}]); - {funcall, A, Bs} -> free_vars([A | Bs]); - {set_state, _, A} -> free_vars(A); - {get_state, _} -> []; - {lam, Xs, B} -> free_vars(B) -- lists:sort(Xs); - {closure, _, A} -> free_vars(A); - {switch, A} -> free_vars(A); - {split, _, X, As} -> free_vars([{var, X} | As]); - {nosplit, A} -> free_vars(A); - {'case', P, A} -> free_vars(A) -- lists:sort(fsplit_pat_vars(P)) + {var, _, X} -> [X]; + {lit, _, _} -> []; + {nil, _} -> []; + {def, _, _, As} -> free_vars(As); + {def_u, _, _, _} -> []; + {remote, _, _, _, Ct, _, As} -> free_vars([Ct | As]); + {remote_u, _, _, _, Ct, _} -> free_vars(Ct); + {builtin, _, _, As} -> free_vars(As); + {builtin_u, _, _, _} -> []; + {builtin_u, _, _, _, _} -> []; %% Typereps are always literals + {con, _, _, _, As} -> free_vars(As); + {tuple, _, As} -> free_vars(As); + {proj, _, A, _} -> free_vars(A); + {set_proj, _, A, _, B} -> free_vars([A, B]); + {op, _, _, As} -> free_vars(As); + {'let', FAnn, X, A, B} -> free_vars([A, {lam, FAnn, [X], B}]); + {funcall, _, A, Bs} -> free_vars([A | Bs]); + {set_state, _, _, A} -> free_vars(A); + {get_state, _, _} -> []; + {lam, _, Xs, B} -> free_vars(B) -- lists:sort(Xs); + {closure, _, _, A} -> free_vars(A); + {switch, _, A} -> free_vars(A); + {split, _, X, As} -> free_vars([{var, [], X} | As]); + {nosplit, _, A} -> free_vars(A); + {'case', P, A} -> free_vars(A) -- lists:sort(fsplit_pat_vars(P)) end. +-spec used_defs(fexpr() | [fexpr()]) -> [fun_name()]. used_defs(Xs) when is_list(Xs) -> lists:umerge([ used_defs(X) || X <- Xs ]); used_defs(Expr) -> case Expr of - {var, _} -> []; - {lit, _} -> []; - nil -> []; - {def, F, As} -> lists:umerge([F], used_defs(As)); - {def_u, F, _} -> [F]; - {remote, _, _, Ct, _, As} -> used_defs([Ct | As]); - {remote_u, _, _, Ct, _} -> used_defs(Ct); - {builtin, _, As} -> used_defs(As); - {builtin_u, _, _} -> []; - {builtin_u, _, _, _} -> []; - {con, _, _, As} -> used_defs(As); - {tuple, As} -> used_defs(As); - {proj, A, _} -> used_defs(A); - {set_proj, A, _, B} -> used_defs([A, B]); - {op, _, As} -> used_defs(As); - {'let', _, A, B} -> used_defs([A, B]); - {funcall, A, Bs} -> used_defs([A | Bs]); - {set_state, _, A} -> used_defs(A); - {get_state, _} -> []; - {lam, _, B} -> used_defs(B); - {closure, F, A} -> lists:umerge([F], used_defs(A)); - {switch, A} -> used_defs(A); - {split, _, _, As} -> used_defs(As); - {nosplit, A} -> used_defs(A); - {'case', _, A} -> used_defs(A) + {var, _, _} -> []; + {lit, _, _} -> []; + {nil, _} -> []; + {def, _, F, As} -> lists:umerge([F], used_defs(As)); + {def_u, _, F, _} -> [F]; + {remote, _, _, _, Ct, _, As} -> used_defs([Ct | As]); + {remote_u, _, _, _, Ct, _} -> used_defs(Ct); + {builtin, _, _, As} -> used_defs(As); + {builtin_u, _, _, _} -> []; + {builtin_u, _, _, _, _} -> []; + {con, _, _, _, As} -> used_defs(As); + {tuple, _, As} -> used_defs(As); + {proj, _, A, _} -> used_defs(A); + {set_proj, _, A, _, B} -> used_defs([A, B]); + {op, _, _, As} -> used_defs(As); + {'let', _, _, A, B} -> used_defs([A, B]); + {funcall, _, A, Bs} -> used_defs([A | Bs]); + {set_state, _, _, A} -> used_defs(A); + {get_state, _, _} -> []; + {lam, _, _, B} -> used_defs(B); + {closure, _, F, A} -> lists:umerge([F], used_defs(A)); + {switch, _, A} -> used_defs(A); + {split, _, _, As} -> used_defs(As); + {nosplit, _, A} -> used_defs(A); + {'case', _, A} -> used_defs(A) end. +-spec bottom_up(Fun, fexpr()) -> fexpr() when + Fun :: fun((expr_env(), fexpr()) -> fexpr()). bottom_up(F, Expr) -> bottom_up(F, #{}, Expr). +-spec bottom_up(Fun, expr_env(), fexpr()) -> fexpr() when + Fun :: fun((expr_env(), fexpr()) -> fexpr()). bottom_up(F, Env, Expr) -> F(Env, case Expr of - {lit, _} -> Expr; - nil -> Expr; - {var, _} -> Expr; - {def, D, Es} -> {def, D, [bottom_up(F, Env, E) || E <- Es]}; - {def_u, _, _} -> Expr; - {builtin, B, Es} -> {builtin, B, [bottom_up(F, Env, E) || E <- Es]}; - {builtin_u, _, _} -> Expr; + {lit, _, _} -> Expr; + {nil, _} -> Expr; + {var, _, _} -> Expr; + {def, FAnn, D, Es} -> {def, FAnn, D, [bottom_up(F, Env, E) || E <- Es]}; + {def_u, _, _, _} -> Expr; + {builtin, FAnn, B, Es} -> {builtin, FAnn, B, [bottom_up(F, Env, E) || E <- Es]}; {builtin_u, _, _, _} -> Expr; - {remote, ArgsT, RetT, Ct, Fun, Es} -> {remote, ArgsT, RetT, bottom_up(F, Env, Ct), Fun, [bottom_up(F, Env, E) || E <- Es]}; - {remote_u, ArgsT, RetT, Ct, Fun} -> {remote_u, ArgsT, RetT, bottom_up(F, Env, Ct), Fun}; - {con, Ar, I, Es} -> {con, Ar, I, [bottom_up(F, Env, E) || E <- Es]}; - {tuple, Es} -> {tuple, [bottom_up(F, Env, E) || E <- Es]}; - {proj, E, I} -> {proj, bottom_up(F, Env, E), I}; - {set_proj, R, I, E} -> {set_proj, bottom_up(F, Env, R), I, bottom_up(F, Env, E)}; - {op, Op, Es} -> {op, Op, [bottom_up(F, Env, E) || E <- Es]}; - {funcall, Fun, Es} -> {funcall, bottom_up(F, Env, Fun), [bottom_up(F, Env, E) || E <- Es]}; - {set_state, R, E} -> {set_state, R, bottom_up(F, Env, E)}; - {get_state, _} -> Expr; - {closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)}; - {switch, Split} -> {switch, bottom_up(F, Env, Split)}; - {lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)}; - {'let', X, E, Body} -> + {builtin_u, _, _, _, _} -> Expr; + {remote, FAnn, ArgsT, RetT, Ct, Fun, Es} -> {remote, FAnn, ArgsT, RetT, bottom_up(F, Env, Ct), Fun, [bottom_up(F, Env, E) || E <- Es]}; + {remote_u, FAnn, ArgsT, RetT, Ct, Fun} -> {remote_u, FAnn, ArgsT, RetT, bottom_up(F, Env, Ct), Fun}; + {con, FAnn, Ar, I, Es} -> {con, FAnn, Ar, I, [bottom_up(F, Env, E) || E <- Es]}; + {tuple, FAnn, Es} -> {tuple, FAnn, [bottom_up(F, Env, E) || E <- Es]}; + {proj, FAnn, E, I} -> {proj, FAnn, bottom_up(F, Env, E), I}; + {set_proj, FAnn, R, I, E} -> {set_proj, FAnn, bottom_up(F, Env, R), I, bottom_up(F, Env, E)}; + {op, FAnn, Op, Es} -> {op, FAnn, Op, [bottom_up(F, Env, E) || E <- Es]}; + {funcall, FAnn, Fun, Es} -> {funcall, FAnn, bottom_up(F, Env, Fun), [bottom_up(F, Env, E) || E <- Es]}; + {set_state, FAnn, R, E} -> {set_state, FAnn, R, bottom_up(F, Env, E)}; + {get_state, _, _} -> Expr; + {closure, FAnn, F, CEnv} -> {closure, FAnn, F, bottom_up(F, Env, CEnv)}; + {switch, FAnn, Split} -> {switch, FAnn, bottom_up(F, Env, Split)}; + {lam, FAnn, Xs, B} -> {lam, FAnn, Xs, bottom_up(F, Env, B)}; + {'let', FAnn, X, E, Body} -> E1 = bottom_up(F, Env, E), %% Always freshen user variables to avoid shadowing issues. ShouldFreshen = fun(Y = "%" ++ _) -> maps:is_key(Y, Env); @@ -1907,16 +2034,17 @@ bottom_up(F, Env, Expr) -> true -> Z = fresh_name_save(X), Env1 = Env#{ Z => E1 }, - {'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))}; + {'let', FAnn, Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))}; false -> Env1 = Env#{ X => E1 }, - {'let', X, E1, bottom_up(F, Env1, Body)} + {'let', FAnn, X, E1, bottom_up(F, Env1, Body)} end; {split, Type, X, Cases} -> {split, Type, X, [bottom_up(F, Env, Case) || Case <- Cases]}; - {nosplit, E} -> {nosplit, bottom_up(F, Env, E)}; + {nosplit, Rens, E} -> {nosplit, Rens, bottom_up(F, Env, E)}; {'case', Pat, Split} -> {'case', Pat, bottom_up(F, Env, Split)} end). +-spec get_named_args([aeso_syntax:named_arg_t()], [aeso_syntax:arg_expr()]) -> [aeso_syntax:expr()]. get_named_args(NamedArgsT, Args) -> IsNamed = fun({named_arg, _, _, _}) -> true; (_) -> false end, @@ -1924,6 +2052,7 @@ get_named_args(NamedArgsT, Args) -> NamedArgs = [get_named_arg(NamedArg, Named) || NamedArg <- NamedArgsT], NamedArgs ++ NotNamed. +-spec get_named_arg(aeso_syntax:named_arg_t(), [aeso_syntax:arg_expr()]) -> aeso_syntax:expr(). get_named_arg({named_arg_t, _, {id, _, Name}, _, Default}, Args) -> case [ Val || {named_arg, _, {id, _, X}, Val} <- Args, X == Name ] of [Val] -> Val; @@ -1932,38 +2061,41 @@ get_named_arg({named_arg_t, _, {id, _, Name}, _, Default}, Args) -> %% -- Renaming -- --spec rename([{var_name(), var_name()}], fexpr()) -> fexpr(). +-spec rename(rename(), fexpr()) -> fexpr(). rename(Ren, Expr) -> case Expr of - {lit, _} -> Expr; - nil -> nil; - {var, X} -> {var, rename_var(Ren, X)}; - {def, D, Es} -> {def, D, [rename(Ren, E) || E <- Es]}; - {def_u, _, _} -> Expr; - {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; - {builtin_u, _, _} -> Expr; - {builtin_u, _, _, _} -> Expr; - {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; - {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F}; - {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; - {tuple, Es} -> {tuple, [rename(Ren, E) || E <- Es]}; - {proj, E, I} -> {proj, rename(Ren, E), I}; - {set_proj, R, I, E} -> {set_proj, rename(Ren, R), I, rename(Ren, E)}; - {op, Op, Es} -> {op, Op, [rename(Ren, E) || E <- Es]}; - {funcall, Fun, Es} -> {funcall, rename(Ren, Fun), [rename(Ren, E) || E <- Es]}; - {set_state, R, E} -> {set_state, R, rename(Ren, E)}; - {get_state, _} -> Expr; - {closure, F, Env} -> {closure, F, rename(Ren, Env)}; - {switch, Split} -> {switch, rename_split(Ren, Split)}; - {lam, Xs, B} -> + {lit, _, _} -> Expr; + {nil, FAnn} -> {nil, FAnn}; + {var, FAnn, X} -> {var, FAnn, rename_var(Ren, X)}; + {def, FAnn, D, Es} -> {def, FAnn, D, [rename(Ren, E) || E <- Es]}; + {def_u, _, _, _} -> Expr; + {builtin, FAnn, B, Es} -> {builtin, FAnn, B, [rename(Ren, E) || E <- Es]}; + {builtin_u, _, _, _} -> Expr; + {builtin_u, _, _, _, _} -> Expr; + {remote, FAnn, ArgsT, RetT, Ct, F, Es} -> {remote, FAnn, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; + {remote_u, FAnn, ArgsT, RetT, Ct, F} -> {remote_u, FAnn, ArgsT, RetT, rename(Ren, Ct), F}; + {con, FAnn, Ar, I, Es} -> {con, FAnn, Ar, I, [rename(Ren, E) || E <- Es]}; + {tuple, FAnn, Es} -> {tuple, FAnn, [rename(Ren, E) || E <- Es]}; + {proj, FAnn, E, I} -> {proj, FAnn, rename(Ren, E), I}; + {set_proj, FAnn, R, I, E} -> {set_proj, FAnn, rename(Ren, R), I, rename(Ren, E)}; + {op, FAnn, Op, Es} -> {op, FAnn, Op, [rename(Ren, E) || E <- Es]}; + {funcall, FAnn, Fun, Es} -> {funcall, FAnn, rename(Ren, Fun), [rename(Ren, E) || E <- Es]}; + {set_state, FAnn, R, E} -> {set_state, FAnn, R, rename(Ren, E)}; + {get_state, _, _} -> Expr; + {closure, FAnn, F, Env} -> {closure, FAnn, F, rename(Ren, Env)}; + {switch, FAnn, Split} -> {switch, FAnn, rename_split(Ren, Split)}; + {lam, FAnn, Xs, B} -> {Zs, Ren1} = rename_bindings(Ren, Xs), - {lam, Zs, rename(Ren1, B)}; - {'let', X, E, Body} -> + {lam, FAnn, Zs, rename(Ren1, B)}; + {'let', FAnn, X, E, Body} -> {Z, Ren1} = rename_binding(Ren, X), - {'let', Z, rename(Ren, E), rename(Ren1, Body)} + {'let', FAnn, Z, rename(Ren, E), rename(Ren1, Body)} end. +-spec rename_var(rename(), var_name()) -> var_name(). rename_var(Ren, X) -> proplists:get_value(X, Ren, X). + +-spec rename_binding(rename(), var_name()) -> {var_name(), rename()}. rename_binding(Ren, X) -> Ren1 = lists:keydelete(X, 1, Ren), case lists:keymember(X, 2, Ren) of @@ -1973,18 +2105,21 @@ rename_binding(Ren, X) -> {Z, [{X, Z} | Ren1]} end. +-spec rename_bindings(rename(), [var_name()]) -> {[var_name()], rename()}. rename_bindings(Ren, []) -> {[], Ren}; rename_bindings(Ren, [X | Xs]) -> {Z, Ren1} = rename_binding(Ren, X), {Zs, Ren2} = rename_bindings(Ren1, Xs), {[Z | Zs], Ren2}. +-spec rename_fpats(rename(), [fpat()]) -> {[fpat()], rename()}. rename_fpats(Ren, []) -> {[], Ren}; rename_fpats(Ren, [P | Ps]) -> {Q, Ren1} = rename_fpat(Ren, P), {Qs, Ren2} = rename_fpats(Ren1, Ps), {[Q | Qs], Ren2}. +-spec rename_fpat(rename(), fpat()) -> {fpat(), rename()}. rename_fpat(Ren, P = {bool, _}) -> {P, Ren}; rename_fpat(Ren, P = {int, _}) -> {P, Ren}; rename_fpat(Ren, P = {string, _}) -> {P, Ren}; @@ -2003,6 +2138,7 @@ rename_fpat(Ren, {tuple, Ps}) -> {Ps1, Ren1} = rename_fpats(Ren, Ps), {{tuple, Ps1}, Ren1}. +-spec rename_spat(rename(), fsplit_pat()) -> {fsplit_pat(), rename()}. rename_spat(Ren, P = {bool, _}) -> {P, Ren}; rename_spat(Ren, P = {int, _}) -> {P, Ren}; rename_spat(Ren, P = {string, _}) -> {P, Ren}; @@ -2025,23 +2161,32 @@ rename_spat(Ren, {assign, X, P}) -> {P1, Ren2} = rename_binding(Ren1, P), {{assign, X1, P1}, Ren2}. +-spec rename_split(rename(), fsplit()) -> fsplit(). rename_split(Ren, {split, Type, X, Cases}) -> {split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]}; -rename_split(Ren, {nosplit, E}) -> {nosplit, rename(Ren, E)}. +rename_split(Ren, {nosplit, Rens, E}) -> + {nosplit, update_rename(Rens, Ren), rename(Ren, E)}. +-spec rename_case(rename(), fcase()) -> fcase(). rename_case(Ren, {'case', Pat, Split}) -> {Pat1, Ren1} = rename_spat(Ren, Pat), {'case', Pat1, rename_split(Ren1, Split)}. +-spec update_rename(rename(), rename()) -> rename(). +update_rename(OldRen, NewRen) -> + [{Name, proplists:get_value(Rename, NewRen, Rename)} || {Name, Rename} <- OldRen]. + %% -- Records -- -field_index({typed, _, _, RecTy}, X) -> - field_index(RecTy, X); +-spec field_index(aeso_syntax:typedef(), aeso_syntax:name()) -> integer(). field_index({record_t, Fields}, X) -> IsX = fun({field_t, _, {id, _, Y}, _}) -> X == Y end, [I] = [ I || {I, Field} <- indexed(Fields), IsX(Field) ], I - 1. %% Tuples are 0-indexed +-spec field_value(aeso_syntax:field_t(), [aeso_syntax:field(aeso_syntax:pat())]) -> Res when + Res :: {upd, aeso_syntax:name(), Expr} | {set, Expr} | false, + Expr :: aeso_syntax:expr(). field_value({field_t, _, {id, _, X}, _}, Fields) -> View = fun({field, _, [{proj, _, {id, _, Y}}], E}) -> {Y, {set, E}}; ({field_upd, _, [{proj, _, {id, _, Y}}], @@ -2053,43 +2198,58 @@ field_value({field_t, _, {id, _, X}, _}, Fields) -> %% -- Attributes -- +-spec get_attributes(aeso_syntax:ann()) -> [stateful | payable | private]. get_attributes(Ann) -> [stateful || proplists:get_value(stateful, Ann, false)] ++ [payable || proplists:get_value(payable, Ann, false)] ++ [private || not proplists:get_value(entrypoint, Ann, false)]. +-spec get_attributes_debug(aeso_syntax:ann()) -> [stateful | payable | private | fann()]. +get_attributes_debug(Ann) -> + get_attributes(Ann) ++ to_fann(Ann). + %% -- Basic utilities -- +-spec indexed([term()]) -> [{integer(), term()}]. indexed(Xs) -> lists:zip(lists:seq(1, length(Xs)), Xs). +-spec setnth(integer(), Val, Vals) -> Vals when + Val :: term(), + Vals :: [Val]. setnth(I, X, Xs) -> {Ys, [_ | Zs]} = lists:split(I - 1, Xs), Ys ++ [X] ++ Zs. -dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}). +-spec fcode_error(string()) -> no_return(). fcode_error(Error) -> Pos = aeso_errors:pos(0, 0), Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Error])), aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)). +-spec internal_error(string()) -> no_return(). internal_error(Error) -> Msg = lists:flatten(io_lib:format("~p\n", [Error])), aeso_errors:throw(aeso_errors:new(internal_error, aeso_errors:pos(0, 0), Msg)). %% -- Pretty printing -------------------------------------------------------- +-spec format_fcode(fcode()) -> string(). format_fcode(#{ functions := Funs }) -> prettypr:format(format_funs(Funs)). +-spec format_funs(functions()) -> prettypr:document(). format_funs(Funs) -> pp_above( [ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]). +-spec format_fexpr(fexpr()) -> string(). format_fexpr(E) -> prettypr:format(pp_fexpr(E)). +-spec pp_fun(fun_name(), fun_def()) -> prettypr:document(). pp_fun(Name, #{ args := Args, return := Return, body := Body }) -> PPArg = fun({X, T}) -> pp_beside([pp_text(X), pp_text(" : "), pp_ftype(T)]) end, pp_above(pp_beside([pp_text("function "), pp_fun_name(Name), @@ -2097,85 +2257,101 @@ pp_fun(Name, #{ args := Args, return := Return, body := Body }) -> pp_text(" : "), pp_ftype(Return), pp_text(" =")]), prettypr:nest(2, pp_fexpr(Body))). +-spec pp_fun_name(fun_name()) -> prettypr:document(). pp_fun_name(event) -> pp_text(event); pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E)); pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")). +-spec pp_text(binary() | string() | atom() | integer()) -> prettypr:document(). pp_text(<<>>) -> prettypr:text("\"\""); pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)]))); pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S])); pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)); pp_text(N) when is_integer(N) -> prettypr:text(integer_to_list(N)). +-spec pp_int(integer()) -> prettypr:document(). pp_int(I) -> prettypr:text(integer_to_list(I)). +-spec pp_beside([prettypr:document()]) -> prettypr:document(). pp_beside([]) -> prettypr:empty(); pp_beside([X]) -> X; pp_beside([X | Xs]) -> pp_beside(X, pp_beside(Xs)). +-spec pp_beside(prettypr:document(), prettypr:document()) -> prettypr:document(). pp_beside(A, B) -> prettypr:beside(A, B). +-spec pp_above([prettypr:document()]) -> prettypr:document(). pp_above([]) -> prettypr:empty(); pp_above([X]) -> X; pp_above([X | Xs]) -> pp_above(X, pp_above(Xs)). +-spec pp_above(prettypr:document(), prettypr:document()) -> prettypr:document(). pp_above(A, B) -> prettypr:above(A, B). +-spec pp_parens(prettypr:document()) -> prettypr:document(). pp_parens(Doc) -> pp_beside([pp_text("("), Doc, pp_text(")")]). + +-spec pp_braces(prettypr:document()) -> prettypr:document(). pp_braces(Doc) -> pp_beside([pp_text("{"), Doc, pp_text("}")]). +-spec pp_punctuate(prettypr:document(), [prettypr:document()]) -> [prettypr:document()]. pp_punctuate(_Sep, []) -> []; pp_punctuate(_Sep, [X]) -> [X]; pp_punctuate(Sep, [X | Xs]) -> [pp_beside(X, Sep) | pp_punctuate(Sep, Xs)]. +-spec pp_par([prettypr:document()]) -> prettypr:document(). pp_par([]) -> prettypr:empty(); pp_par(Xs) -> prettypr:par(Xs). -pp_fexpr({lit, {typerep, T}}) -> + +-spec pp_fexpr(fexpr()) -> prettypr:document(). +pp_fexpr({lit, _, {typerep, T}}) -> pp_ftype(T); -pp_fexpr({lit, {Tag, Lit}}) -> +pp_fexpr({lit, _, {contract_code, Contract}}) -> + pp_beside(pp_text("contract "), pp_text(Contract)); +pp_fexpr({lit, _, {Tag, Lit}}) -> aeso_pretty:expr({Tag, [], Lit}); -pp_fexpr(nil) -> +pp_fexpr({nil, _}) -> pp_text("[]"); -pp_fexpr({var, X}) -> pp_text(X); +pp_fexpr({var, _, X}) -> pp_text(X); pp_fexpr({def, Fun}) -> pp_fun_name(Fun); -pp_fexpr({def_u, Fun, Ar}) -> +pp_fexpr({def_u, _, Fun, Ar}) -> pp_beside([pp_fun_name(Fun), pp_text("/"), pp_int(Ar)]); -pp_fexpr({def, Fun, Args}) -> +pp_fexpr({def, _, Fun, Args}) -> pp_call(pp_fun_name(Fun), Args); -pp_fexpr({con, _, I, []}) -> +pp_fexpr({con, _, _, I, []}) -> pp_beside(pp_text("C"), pp_int(I)); -pp_fexpr({con, _, I, Es}) -> - pp_beside(pp_fexpr({con, [], I, []}), - pp_fexpr({tuple, Es})); -pp_fexpr({tuple, Es}) -> +pp_fexpr({con, FAnn, _, I, Es}) -> + pp_beside(pp_fexpr({con, FAnn, [], I, []}), + pp_fexpr({tuple, FAnn, Es})); +pp_fexpr({tuple, _, Es}) -> pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es]))); -pp_fexpr({proj, E, I}) -> +pp_fexpr({proj, _, E, I}) -> pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]); -pp_fexpr({lam, Xs, A}) -> - pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"), +pp_fexpr({lam, FAnn, Xs, A}) -> + pp_par([pp_fexpr({tuple, FAnn, [{var, FAnn, X} || X <- Xs]}), pp_text("=>"), prettypr:nest(2, pp_fexpr(A))]); -pp_fexpr({closure, Fun, ClEnv}) -> +pp_fexpr({closure, _, Fun, ClEnv}) -> FVs = case ClEnv of - {tuple, Xs} -> Xs; - {var, _} -> [ClEnv] + {tuple, _, Xs} -> Xs; + {var, _, _} -> [ClEnv] end, pp_call(pp_text("__CLOSURE__"), [{def, Fun} | FVs]); -pp_fexpr({set_proj, E, I, A}) -> +pp_fexpr({set_proj, _, E, I, A}) -> pp_beside(pp_fexpr(E), pp_braces(pp_beside([pp_int(I), pp_text(" = "), pp_fexpr(A)]))); -pp_fexpr({op, Op, [A, B] = Args}) -> +pp_fexpr({op, _, Op, [A, B] = Args}) -> case is_infix(Op) of false -> pp_call(pp_text(Op), Args); true -> pp_parens(pp_par([pp_fexpr(A), pp_text(Op), pp_fexpr(B)])) end; -pp_fexpr({op, Op, [A] = Args}) -> +pp_fexpr({op, _, Op, [A] = Args}) -> case is_infix(Op) of false -> pp_call(pp_text(Op), Args); true -> pp_parens(pp_par([pp_text(Op), pp_fexpr(A)])) end; -pp_fexpr({op, Op, As}) -> - pp_beside(pp_text(Op), pp_fexpr({tuple, As})); -pp_fexpr({'let', _, _, _} = Expr) -> - Lets = fun Lets({'let', Y, C, D}) -> +pp_fexpr({op, FAnn, Op, As}) -> + pp_beside(pp_text(Op), pp_fexpr({tuple, FAnn, As})); +pp_fexpr({'let', _, _, _, _} = Expr) -> + Lets = fun Lets({'let', _, Y, C, D}) -> {Ls, E} = Lets(D), {[{Y, C} | Ls], E}; Lets(E) -> {[], E} end, @@ -2186,29 +2362,29 @@ pp_fexpr({'let', _, _, _} = Expr) -> pp_above([ pp_par([pp_text(X), pp_text("="), prettypr:nest(2, pp_fexpr(A))]) || {X, A} <- Ls ]), pp_text(" in ") ]), pp_fexpr(Body) ])); -pp_fexpr({builtin_u, B, N}) -> +pp_fexpr({builtin_u, _, B, N}) -> pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); -pp_fexpr({builtin_u, B, N, TypeArgs}) -> - pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, TypeArgs}), pp_text("/"), pp_text(N)]); -pp_fexpr({builtin, B, As}) -> +pp_fexpr({builtin_u, FAnn, B, N, TypeArgs}) -> + pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, FAnn, TypeArgs}), pp_text("/"), pp_text(N)]); +pp_fexpr({builtin, _, B, As}) -> pp_call(pp_text(B), As); -pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) -> +pp_fexpr({remote_u, _, ArgsT, RetT, Ct, Fun}) -> pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text(" : "), pp_ftype({function, ArgsT, RetT})]); -pp_fexpr({remote, ArgsT, RetT, Ct, Fun, As}) -> +pp_fexpr({remote, _, ArgsT, RetT, Ct, Fun, As}) -> pp_call(pp_parens(pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text(" : "), pp_ftype({function, ArgsT, RetT})])), As); -pp_fexpr({funcall, Fun, As}) -> +pp_fexpr({funcall, _, Fun, As}) -> pp_call(pp_fexpr(Fun), As); -pp_fexpr({set_state, R, A}) -> - pp_call(pp_text("set_state"), [{lit, {int, R}}, A]); -pp_fexpr({get_state, R}) -> - pp_call(pp_text("get_state"), [{lit, {int, R}}]); -pp_fexpr({switch, Split}) -> pp_split(Split); -pp_fexpr({contract_code, Contract}) -> - pp_beside(pp_text("contract "), pp_text(Contract)). +pp_fexpr({set_state, FAnn, R, A}) -> + pp_call(pp_text("set_state"), [{lit, FAnn, {int, R}}, A]); +pp_fexpr({get_state, FAnn, R}) -> + pp_call(pp_text("get_state"), [{lit, FAnn, {int, R}}]); +pp_fexpr({switch, _, Split}) -> pp_split(Split). +-spec pp_call(prettypr:document(), [fexpr()]) -> prettypr:document(). pp_call(Fun, Args) -> - pp_beside(Fun, pp_fexpr({tuple, Args})). + pp_beside(Fun, pp_fexpr({tuple, [], Args})). +-spec pp_call_t(string(), [ftype()]) -> prettypr:document(). pp_call_t(Fun, Args) -> pp_beside(pp_text(Fun), pp_ftype({tuple, Args})). @@ -2216,7 +2392,7 @@ pp_call_t(Fun, Args) -> pp_ftype(T) when is_atom(T) -> pp_text(T); pp_ftype(any) -> pp_text("_"); pp_ftype({tvar, X}) -> pp_text(X); -pp_ftype({bytes, N}) -> pp_call(pp_text("bytes"), [{lit, {int, N}}]); +pp_ftype({bytes, N}) -> pp_call(pp_text("bytes"), [{lit, [], {int, N}}]); pp_ftype({oracle, Q, R}) -> pp_call_t("oracle", [Q, R]); pp_ftype({tuple, Ts}) -> pp_parens(pp_par(pp_punctuate(pp_text(" *"), [pp_ftype(T) || T <- Ts]))); @@ -2230,27 +2406,36 @@ pp_ftype({variant, Cons}) -> pp_par( pp_punctuate(pp_text(" |"), [ case Args of - [] -> pp_fexpr({con, [], I - 1, []}); - _ -> pp_beside(pp_fexpr({con, [], I - 1, []}), pp_ftype({tuple, Args})) - end || {I, Args} <- indexed(Cons)])). + [] -> pp_fexpr({con, [], [], I - 1, []}); + _ -> pp_beside(pp_fexpr({con, [], [], I - 1, []}), pp_ftype({tuple, Args})) + end || {I, Args} <- indexed(Cons)])); +pp_ftype([]) -> + %% NOTE: This could happen with `{typerep, []}` since `[]` is not a ftype(). + %% TODO: It would be better to make sure that `{typerep, []}` does not arrive here. + pp_text("[]"). -pp_split({nosplit, E}) -> pp_fexpr(E); +-spec pp_split(fsplit()) -> prettypr:document(). +pp_split({nosplit, _, E}) -> pp_fexpr(E); pp_split({split, Type, X, Alts}) -> pp_above([pp_beside([pp_text("switch("), pp_text(X), pp_text(" : "), pp_ftype(Type), pp_text(")")])] ++ [prettypr:nest(2, pp_case(Alt)) || Alt <- Alts]). +-spec pp_case(fcase()) -> prettypr:document(). pp_case({'case', Pat, Split}) -> prettypr:sep([pp_beside(pp_pat(Pat), pp_text(" =>")), prettypr:nest(2, pp_split(Split))]). -pp_pat({tuple, Xs}) -> pp_fexpr({tuple, [{var, X} || X <- Xs]}); -pp_pat({'::', X, Xs}) -> pp_fexpr({op, '::', [{var, X}, {var, Xs}]}); -pp_pat({con, As, I, Xs}) -> pp_fexpr({con, As, I, [{var, X} || X <- Xs]}); -pp_pat({var, X}) -> pp_fexpr({var, X}); +-spec pp_pat(fsplit_pat()) -> prettypr:document(). +pp_pat({tuple, Xs}) -> pp_fexpr({tuple, [], [{var, [], X} || X <- Xs]}); +pp_pat({'::', X, Xs}) -> pp_fexpr({op, [], '::', [{var, [], X}, {var, [], Xs}]}); +pp_pat({con, As, I, Xs}) -> pp_fexpr({con, [], As, I, [{var, [], X} || X <- Xs]}); +pp_pat({var, X}) -> pp_fexpr({var, [], X}); pp_pat(P = {Tag, _}) when Tag == bool; Tag == int; Tag == string - -> pp_fexpr({lit, P}); -pp_pat(Pat) -> pp_fexpr(Pat). + -> pp_fexpr({lit, [], P}); +pp_pat(nil) -> pp_fexpr({nil, []}); +pp_pat({assign, X, Y}) -> pp_beside([pp_text(X), pp_text(" = "), pp_text(Y)]). +-spec is_infix(op()) -> boolean(). is_infix(Op) -> C = hd(atom_to_list(Op)), C < $a orelse C > $z. diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 7a0d069..1ff6ec3 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -119,7 +119,7 @@ from_string1(ContractString, Options) -> , warnings := Warnings } = string_to_code(ContractString, Options), #{ child_con_env := ChildContracts } = FCodeEnv, SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}), - {FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options), + FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options), pp_assembler(FateCode, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = version(), @@ -132,13 +132,7 @@ from_string1(ContractString, Options) -> payable => maps:get(payable, FCode), warnings => Warnings }, - ResDbg = Res#{variables_registers => VarsRegs}, - FinalRes = - case proplists:get_value(debug_info, Options, false) of - true -> ResDbg; - false -> Res - end, - {ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}. + {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}. maybe_generate_aci(Result, FoldedTypedAst, Options) -> case proplists:get_value(aci, Options) of @@ -192,7 +186,7 @@ check_call(Source, FunName, Args, Options) -> check_call1(ContractString0, FunName, Args, Options) -> case add_extra_call(ContractString0, {call, FunName, Args}, Options) of {ok, CallName, Code} -> - {def, _, FcodeArgs} = get_call_body(CallName, Code), + {def, _, _, FcodeArgs} = get_call_body(CallName, Code), {ok, FunName, [ aeso_fcode_to_fate:term_to_fate(A) || A <- FcodeArgs ]}; Err = {error, _} -> Err @@ -204,7 +198,7 @@ add_extra_call(Contract0, Call, Options) -> #{fcode := OrgFcode , fcode_env := #{child_con_env := ChildContracts} , ast := Ast} = string_to_code(Contract0, Options), - {FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []), + FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []), %% collect all hashes and compute the first name without hash collision to SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)), CallName = first_none_match(?CALL_NAME, SymbolHashes, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index ebc4ebf..5c99d70 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -52,7 +52,8 @@ tailpos = true, child_contracts = #{}, saved_fresh_names = #{}, - options = [] }). + options = [], + debug_info = false }). %% -- Debugging -------------------------------------------------------------- @@ -81,24 +82,16 @@ code_error(Err) -> compile(FCode, SavedFreshNames, Options) -> compile(#{}, FCode, SavedFreshNames, Options). compile(ChildContracts, FCode, SavedFreshNames, Options) -> - try - compile1(ChildContracts, FCode, SavedFreshNames, Options) - after - put(variables_registers, undefined) - end. - -compile1(ChildContracts, FCode, SavedFreshNames, Options) -> #{ contract_name := ContractName, functions := Functions } = FCode, SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options), SFuns1 = optimize_scode(SFuns, Options), FateCode = to_basic_blocks(SFuns1), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), - FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of - false -> FateCode; - true -> add_child_symbols(ChildContracts, FateCode) - end, - {FateCode1, get_variables_registers()}. + case proplists:get_value(include_child_contract_symbols, Options, false) of + false -> FateCode; + true -> add_child_symbols(ChildContracts, FateCode) + end. make_function_id(X) -> aeb_fate_code:symbol_identifier(make_function_name(X)). @@ -123,31 +116,15 @@ functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Opt function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, Options) -> {ArgTypes, ResType1} = typesig_to_scode(Args, ResType), - Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. + Attrs = [ A || A <- Attrs0, A == private orelse A == payable ], Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options), - [ add_variables_register(Env, Arg, Register) || - proplists:get_value(debug_info, Options, false), - {Arg, Register} <- Env#env.vars ], + ArgsNames = [ X || {X, _} <- lists:reverse(Env#env.vars) ], + + %% DBG_LOC is added before the function body to make it possible to break + %% at the function signature SCode = to_scode(Env, Body), - {Attrs, {ArgTypes, ResType1}, SCode}. - -get_variables_registers() -> - case get(variables_registers) of - undefined -> #{}; - Vs -> Vs - end. - -add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) -> - Olds = get_variables_registers(), - RealName = maps:get(Name, SavedFreshNames, Name), - FunName = - case Env#env.current_function of - event -> "Chain.event"; - {entrypoint, BinName} -> binary_to_list(BinName); - {local_fun, QualName} -> lists:last(QualName) - end, - New = {Env#env.contract, FunName, RealName}, - put(variables_registers, Olds#{New => Register}). + DbgSCode = dbg_contract(Env) ++ dbg_loc(Env, Attrs0) ++ dbg_scoped_vars(Env, ArgsNames, SCode), + {Attrs, {ArgTypes, ResType1}, DbgSCode}. -define(tvars, '$tvars'). @@ -194,20 +171,20 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts). %% -- Environment functions -- init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) -> - #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], - contract = ContractName, - child_contracts = ChildContracts, - locals = FunNames, - current_function = Name, - options = Options, - tailpos = true, - saved_fresh_names = SavedFreshNames }. + #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], + contract = ContractName, + child_contracts = ChildContracts, + locals = FunNames, + current_function = Name, + options = Options, + tailpos = true, + saved_fresh_names = SavedFreshNames, + debug_info = proplists:get_value(debug_info, Options, false) }. next_var(#env{ vars = Vars }) -> 1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]). bind_var(Name, Var, Env = #env{ vars = Vars }) -> - proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var), Env#env{ vars = [{Name, Var} | Vars] }. bind_local(Name, Env) -> @@ -234,7 +211,7 @@ serialize_contract_code(Env, C) -> Options = Env#env.options, SavedFreshNames = Env#env.saved_fresh_names, FCode = maps:get(C, Env#env.child_contracts), - {FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options), + FateCode = compile(Env#env.child_contracts, FCode, SavedFreshNames, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = aeso_compiler:version(), OriginalSourceCode = proplists:get_value(original_src, Options, ""), @@ -268,44 +245,44 @@ lit_to_fate(Env, L) -> term_to_fate(E) -> term_to_fate(#env{}, #{}, E). term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E). -term_to_fate(GlobEnv, _Env, {lit, L}) -> +term_to_fate(GlobEnv, _Env, {lit, _, L}) -> lit_to_fate(GlobEnv, L); %% negative literals are parsed as 0 - N -term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) -> +term_to_fate(_GlobEnv, _Env, {op, _, '-', [{lit, _, {int, 0}}, {lit, _, {int, N}}]}) -> aeb_fate_data:make_integer(-N); -term_to_fate(_GlobEnv, _Env, nil) -> +term_to_fate(_GlobEnv, _Env, {nil, _}) -> aeb_fate_data:make_list([]); -term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) -> +term_to_fate(GlobEnv, Env, {op, _, '::', [Hd, Tl]}) -> %% The Tl will translate into a list, because FATE lists are just lists [term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)]; -term_to_fate(GlobEnv, Env, {tuple, As}) -> +term_to_fate(GlobEnv, Env, {tuple, _, As}) -> aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As])); -term_to_fate(GlobEnv, Env, {con, Ar, I, As}) -> +term_to_fate(GlobEnv, Env, {con, _, Ar, I, As}) -> FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ], aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); -term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, _, bits_all, []}) -> aeb_fate_data:make_bits(-1); -term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, _, bits_none, []}) -> aeb_fate_data:make_bits(0); -term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) -> +term_to_fate(GlobEnv, _Env, {op, _, bits_set, [B, I]}) -> {bits, N} = term_to_fate(GlobEnv, B), J = term_to_fate(GlobEnv, I), {bits, N bor (1 bsl J)}; -term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) -> +term_to_fate(GlobEnv, _Env, {op, _, bits_clear, [B, I]}) -> {bits, N} = term_to_fate(GlobEnv, B), J = term_to_fate(GlobEnv, I), {bits, N band bnot (1 bsl J)}; -term_to_fate(GlobEnv, Env, {'let', X, E, Body}) -> +term_to_fate(GlobEnv, Env, {'let', _, X, E, Body}) -> Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) }, term_to_fate(GlobEnv, Env1, Body); -term_to_fate(_GlobEnv, Env, {var, X}) -> +term_to_fate(_GlobEnv, Env, {var, _, X}) -> case maps:get(X, Env, undefined) of undefined -> throw(not_a_fate_value); V -> V end; -term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, _, map_empty, []}) -> aeb_fate_data:make_map(#{}); -term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) -> +term_to_fate(GlobEnv, Env, {op, _, map_set, [M, K, V]}) -> Map = term_to_fate(GlobEnv, Env, M), Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)}; term_to_fate(_GlobEnv, _Env, _) -> @@ -313,52 +290,59 @@ term_to_fate(_GlobEnv, _Env, _) -> to_scode(Env, T) -> try term_to_fate(Env, T) of - V -> [push(?i(V))] + V -> + FAnn = element(2, T), + [dbg_loc(Env, FAnn), push(?i(V))] catch throw:not_a_fate_value -> to_scode1(Env, T) end. -to_scode1(Env, {lit, L}) -> - [push(?i(lit_to_fate(Env, L)))]; +to_scode1(Env, {lit, Ann, L}) -> + [ dbg_loc(Env, Ann), push(?i(lit_to_fate(Env, L))) ]; -to_scode1(_Env, nil) -> - [aeb_fate_ops:nil(?a)]; +to_scode1(Env, {nil, Ann}) -> + [ dbg_loc(Env, Ann), aeb_fate_ops:nil(?a) ]; -to_scode1(Env, {var, X}) -> - [push(lookup_var(Env, X))]; +to_scode1(Env, {var, Ann, X}) -> + [ dbg_loc(Env, Ann), push(lookup_var(Env, X)) ]; -to_scode1(Env, {con, Ar, I, As}) -> +to_scode1(Env, {con, Ann, Ar, I, As}) -> N = length(As), - [[to_scode(notail(Env), A) || A <- As], - aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N))]; + [ dbg_loc(Env, Ann), + [to_scode(notail(Env), A) || A <- As], + aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N)) ]; -to_scode1(Env, {tuple, As}) -> +to_scode1(Env, {tuple, Ann, As}) -> N = length(As), - [[ to_scode(notail(Env), A) || A <- As ], - tuple(N)]; + [ dbg_loc(Env, Ann), + [ to_scode(notail(Env), A) || A <- As ], + tuple(N) ]; -to_scode1(Env, {proj, E, I}) -> - [to_scode(notail(Env), E), - aeb_fate_ops:element_op(?a, ?i(I), ?a)]; +to_scode1(Env, {proj, Ann, E, I}) -> + [ dbg_loc(Env, Ann), + to_scode(notail(Env), E), + aeb_fate_ops:element_op(?a, ?i(I), ?a) ]; -to_scode1(Env, {set_proj, R, I, E}) -> - [to_scode(notail(Env), E), - to_scode(notail(Env), R), - aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a)]; +to_scode1(Env, {set_proj, Ann, R, I, E}) -> + [ dbg_loc(Env, Ann), + to_scode(notail(Env), E), + to_scode(notail(Env), R), + aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a) ]; -to_scode1(Env, {op, Op, Args}) -> - call_to_scode(Env, op_to_scode(Op), Args); +to_scode1(Env, {op, Ann, Op, Args}) -> + [ dbg_loc(Env, Ann) | call_to_scode(Env, op_to_scode(Op), Args) ]; -to_scode1(Env, {'let', X, {var, Y}, Body}) -> +to_scode1(Env, {'let', Ann, X, {var, _, Y}, Body}) -> Env1 = bind_var(X, lookup_var(Env, Y), Env), - to_scode(Env1, Body); -to_scode1(Env, {'let', X, Expr, Body}) -> + [ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], to_scode(Env1, Body)) ]; +to_scode1(Env, {'let', Ann, X, Expr, Body}) -> {I, Env1} = bind_local(X, Env), - [ to_scode(notail(Env), Expr), - aeb_fate_ops:store({var, I}, {stack, 0}), - to_scode(Env1, Body) ]; + SCode = [ to_scode(notail(Env), Expr), + aeb_fate_ops:store({var, I}, {stack, 0}), + to_scode(Env1, Body) ], + [ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], SCode) ]; -to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}) -> +to_scode1(Env = #env{ current_function = Fun, tailpos = true, debug_info = false }, {def, Ann, Fun, Args}) -> %% Tail-call to current function, f(e0..en). Compile to %% [ let xi = ei ] %% [ STORE argi xi ] @@ -371,61 +355,62 @@ to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args} aeb_fate_ops:store({var, I}, ?a)], {[I | Is], Acc1, Env2} end, {[], [], Env}, Args), - [ Code, + [ dbg_loc(Env, Ann), + Code, [ aeb_fate_ops:store({arg, I}, {var, J}) || {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1), lists:reverse(Vars)) ], loop ]; -to_scode1(Env, {def, Fun, Args}) -> +to_scode1(Env, {def, Ann, Fun, Args}) -> FName = make_function_id(Fun), Lbl = aeb_fate_data:make_string(FName), - call_to_scode(Env, local_call(Env, ?i(Lbl)), Args); -to_scode1(Env, {funcall, Fun, Args}) -> - call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args); + [ dbg_loc(Env, Ann) | call_to_scode(Env, local_call(Env, ?i(Lbl)), Args) ]; +to_scode1(Env, {funcall, Ann, Fun, Args}) -> + [ dbg_loc(Env, Ann) | call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args) ]; -to_scode1(Env, {builtin, B, Args}) -> - builtin_to_scode(Env, B, Args); +to_scode1(Env, {builtin, Ann, B, Args}) -> + [ dbg_loc(Env, Ann) | builtin_to_scode(Env, B, Args) ]; -to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) -> +to_scode1(Env, {remote, Ann, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) -> Lbl = make_function_id(Fun), {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), RetType = ?i(aeb_fate_data:make_typerep(RetType0)), - case Protected of - {lit, {bool, false}} -> + SCode = case Protected of + {lit, _, {bool, false}} -> case Gas of - {builtin, call_gas_left, _} -> + {builtin, _, call_gas_left, _} -> Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), call_to_scode(Env, Call, [Ct, Value | Args]); _ -> Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a), call_to_scode(Env, Call, [Ct, Value, Gas | Args]) end; - {lit, {bool, true}} -> + {lit, _, {bool, true}} -> Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)), call_to_scode(Env, Call, [Ct, Value, Gas | Args]); _ -> Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a), call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args]) - end; + end, + [ dbg_loc(Env, Ann) | SCode ]; -to_scode1(_Env, {get_state, Reg}) -> - [push(?s(Reg))]; -to_scode1(Env, {set_state, Reg, Val}) -> - call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, - tuple(0)], [Val]); +to_scode1(Env, {get_state, Ann, Reg}) -> + [ dbg_loc(Env, Ann), push(?s(Reg)) ]; +to_scode1(Env, {set_state, Ann, Reg, Val}) -> + [ dbg_loc(Env, Ann) | call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, tuple(0)], [Val]) ]; -to_scode1(Env, {closure, Fun, FVs}) -> - to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]}); +to_scode1(Env, {closure, Ann, Fun, FVs}) -> + [ to_scode(Env, {tuple, Ann, [{lit, Ann, {string, make_function_id(Fun)}}, FVs]}) ]; -to_scode1(Env, {switch, Case}) -> - split_to_scode(Env, Case). +to_scode1(Env, {switch, Ann, Case}) -> + [ dbg_loc(Env, Ann) | split_to_scode(Env, Case) ]. -local_call( Env, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun); -local_call(_Env, Fun) -> aeb_fate_ops:call(Fun). +local_call( Env = #env{debug_info = false}, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun); +local_call(_Env, Fun) -> aeb_fate_ops:call(Fun). -split_to_scode(Env, {nosplit, Expr}) -> - [switch_body, to_scode(Env, Expr)]; +split_to_scode(Env, {nosplit, Renames, Expr}) -> + [switch_body, dbg_scoped_vars(Env, Renames, to_scode(Env, Expr))]; split_to_scode(Env, {split, {tuple, _}, X, Alts}) -> {Def, Alts1} = catchall_to_scode(Env, X, Alts), Arg = lookup_var(Env, X), @@ -649,7 +634,7 @@ builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> builtin_to_scode(Env, chain_clone, [InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) -> case GasCap of - {builtin, call_gas_left, _} -> + {builtin, _, call_gas_left, _} -> call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), [Contract, InitArgsT, Value, Prot | InitArgs] ); @@ -751,6 +736,77 @@ push(A) -> {'STORE', ?a, A}. tuple(0) -> push(?i({tuple, {}})); tuple(N) -> aeb_fate_ops:tuple(?a, N). +%% -- Debug info functions -- + +dbg_contract(#env{debug_info = false}) -> + []; +dbg_contract(#env{contract = Contract}) -> + [{'DBG_CONTRACT', {immediate, Contract}}]. + +dbg_loc(#env{debug_info = false}, _) -> + []; +dbg_loc(_Env, Ann) -> + File = case proplists:get_value(file, Ann, no_file) of + no_file -> ""; + F -> F + end, + Line = proplists:get_value(line, Ann, undefined), + case Line of + undefined -> []; + _ -> [{'DBG_LOC', {immediate, File}, {immediate, Line}}] + end. + +dbg_scoped_vars(#env{debug_info = false}, _, SCode) -> + SCode; +dbg_scoped_vars(_Env, [], SCode) -> + SCode; +dbg_scoped_vars(Env, [{SavedVarName, Var} | Rest], SCode) -> + dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode)); +dbg_scoped_vars(Env = #env{saved_fresh_names = SavedFreshNames}, [Var | Rest], SCode) -> + SavedVarName = maps:get(Var, SavedFreshNames, Var), + dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode)). + +dbg_scoped_var(Env, SavedVarName, Var, SCode) -> + case SavedVarName == "_" orelse is_fresh_name(SavedVarName) of + true -> + SCode; + false -> + Register = lookup_var(Env, Var), + Def = [{'DBG_DEF', {immediate, SavedVarName}, Register}], + Undef = [{'DBG_UNDEF', {immediate, SavedVarName}, Register}], + Def ++ dbg_undef(Undef, SCode) + end. + +is_fresh_name([$% | _]) -> + true; +is_fresh_name(_) -> + false. + +dbg_undef(_Undef, missing) -> + missing; +dbg_undef(Undef, loop) -> + [Undef, loop]; +dbg_undef(Undef, switch_body) -> + [switch_body, Undef]; +dbg_undef(Undef, {switch, Arg, Type, Alts, Catch}) -> + NewAlts = [ dbg_undef(Undef, Alt) || Alt <- Alts ], + NewCatch = dbg_undef(Undef, Catch), + NewSwitch = {switch, Arg, Type, NewAlts, NewCatch}, + NewSwitch; +dbg_undef(Undef, SCode) when is_list(SCode) -> + lists:droplast(SCode) ++ [dbg_undef(Undef, lists:last(SCode))]; +dbg_undef(Undef, SCode) when is_tuple(SCode); is_atom(SCode) -> + [Mnemonic | _] = + case is_tuple(SCode) of + true -> tuple_to_list(SCode); + false -> [SCode] + end, + Op = aeb_fate_opcodes:m_to_op(Mnemonic), + case aeb_fate_opcodes:end_bb(Op) of + true -> [Undef, SCode]; + false -> [SCode, Undef] + end. + %% -- Phase II --------------------------------------------------------------- %% Optimize @@ -886,6 +942,10 @@ attributes(I) -> loop -> Impure(pc, []); switch_body -> Pure(none, []); 'RETURN' -> Impure(pc, []); + {'DBG_LOC', _, _} -> Impure(none, []); + {'DBG_DEF', _, _} -> Impure(none, []); + {'DBG_UNDEF', _, _} -> Impure(none, []); + {'DBG_CONTRACT', _} -> Impure(none, []); {'RETURNR', A} -> Impure(pc, A); {'CALL', A} -> Impure(?a, [A]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]); @@ -1605,7 +1665,23 @@ bb(_Name, Code) -> Blocks = lists:flatmap(fun split_calls/1, Blocks1), Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]), BBs = [ set_labels(Labels, B) || B <- Blocks ], - maps:from_list(BBs). + maps:from_list(dbg_loc_filter(BBs)). + +%% Filter DBG_LOC instructions to keep one instruction per line +dbg_loc_filter(BBs) -> + dbg_loc_filter(BBs, [], [], sets:new()). + +dbg_loc_filter([], _, AllBlocks, _) -> + lists:reverse(AllBlocks); +dbg_loc_filter([{I, []} | Rest], AllOps, AllBlocks, DbgLocs) -> + dbg_loc_filter(Rest, [], [{I, lists:reverse(AllOps)} | AllBlocks], DbgLocs); +dbg_loc_filter([{I, [Op = {'DBG_LOC', _, _} | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) -> + case sets:is_element(Op, DbgLocs) of + true -> dbg_loc_filter([{I, Ops} | Rest], AllOps, AllBlocks, DbgLocs); + false -> dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, sets:add_element(Op, DbgLocs)) + end; +dbg_loc_filter([{I, [Op | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) -> + dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, DbgLocs). %% -- Break up scode into basic blocks -- diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index f1c7f99..0ff9f5e 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -10,7 +10,7 @@ -export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]). --export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). +-export_type([ann_file/0, ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]). -export_type([bin_op/0, un_op/0]). -export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]). @@ -24,8 +24,9 @@ -type ann_col() :: integer(). -type ann_origin() :: system | user. -type ann_format() :: '?:' | hex | infix | prefix | elif. +-type ann_file() :: string() | no_file. --type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} +-type ann() :: [ {file, ann_file()} | {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private | payable | main | interface | entrypoint]. -type name() :: string(). From d5ff9d4a2f4aa888475ffc5483f4d534e0ad9945 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 15 Jun 2023 22:45:39 +0200 Subject: [PATCH 03/41] fix AENS.update stdlib doc (#459) --- docs/sophia_stdlib.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 450568c..d136669 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -190,7 +190,7 @@ using the private key of the `owner` account for signing. ##### update ``` AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), - new_ptrs : map(string, AENS.pointee), ) : unit + new_ptrs : option(map(string, AENS.pointee)), ) : unit ``` Updates the name. If the optional parameters are set to `None` that parameter From c1e8195fd84daef9747147bff2e1dbad5e18b9be Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 19 Jun 2023 11:49:03 +0200 Subject: [PATCH 04/41] Document Chain.spend and sort Chain functions (#460) * Document Chain.spend and sort Chain functions * Too little coffee, re-adding gas-limit --- docs/sophia_stdlib.md | 78 +++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index d136669..9fcdd7e 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -470,38 +470,6 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). -##### coinbase -``` -Chain.coinbase : address -``` - -The address of the account that mined the current block. - - -##### timestamp -``` -Chain.timestamp : int -``` - -The timestamp of the current block (unix time, milliseconds). - - -##### difficulty -``` -Chain.difficulty : int -``` - -The difficulty of the current block. - - -##### gas -``` -Chain.gas_limit : int -``` - -The gas limit of the current block. - - ##### bytecode_hash ``` Chain.bytecode_hash : 'c => option(hash) @@ -565,6 +533,7 @@ main contract Market = The typechecker must be certain about the created contract's type, so it is worth writing it explicitly as shown in the example. + ##### clone ``` Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ... @@ -623,11 +592,54 @@ implementation of the `init` function does not actually return `state`, but calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`. +##### coinbase +``` +Chain.coinbase : address +``` + +The address of the account that mined the current block. + + +##### difficulty +``` +Chain.difficulty : int +``` + +The difficulty of the current block. + + ##### event ``` Chain.event(e : event) : unit ``` -Emits the event. To use this function one needs to define the `event` type as a `datatype` in the contract. + +Emits the event. To use this function one needs to define the `event` type as a +`datatype` in the contract. + + +##### gas\_limit +``` +Chain.gas_limit : int +``` + +The gas limit of the current block. + + +##### spend +``` +Chain.spend(to : address, amount : int) : unit +``` + +Spend `amount` tokens to `to`. Will fail (and abort the contract) if contract +doesn't have `amount` tokens to transfer, or, if `to` is not `payable`. + + +##### timestamp +``` +Chain.timestamp : int +``` + +The timestamp of the current block (unix time, milliseconds). ### Char From b902226c26a64bef11423863dd70ab7017657c75 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 19 Jun 2023 13:21:44 +0300 Subject: [PATCH 05/41] Prepare v7.2.0 release (#462) --- CHANGELOG.md | 10 ++++++++-- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cf36f..f80e3da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +### Changed +### Removed +### Fixed + +## [7.2.0] +### Added - Toplevel compile-time constants ``` namespace N = @@ -14,7 +20,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 let cc = 2 ``` - API functions for encoding/decoding Sophia values to/from FATE. -### Changed ### Removed - Remove the mapping from variables to FATE registers from the compilation output. ### Fixed @@ -390,7 +395,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.1.0...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.2.0...HEAD +[7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0 [7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0 [7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1 [7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0 diff --git a/rebar.config b/rebar.config index e07433a..4703105 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "7.1.0"}, +{relx, [{release, {aesophia, "7.2.0"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index d195d99..8a34546 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.1.0"}, + {vsn, "7.2.0"}, {registered, []}, {applications, [kernel, From c15d411660f0f643616728c31cf1b10854a4813f Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Wed, 28 Jun 2023 18:43:41 +0400 Subject: [PATCH 06/41] Fix bugs caused by the addition of debugging symbols (#464) * Fix get_catchalls bug * Fix for event datatype --- src/aeso_ast_to_fcode.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 6890926..e219267 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -1262,7 +1262,7 @@ event_function(_Env = #{event_type := {variant_t, EventCons}}, EventType = {vari end, Indices = [ {var, [], V} || {indexed, V} <- IVars ], Body = {builtin, [], chain_event, [Payload, Hash | Indices]}, - {'case', {con, [], Arities, Tag, Vars}, {nosplit, [], Body}} + {'case', {con, Arities, Tag, Vars}, {nosplit, [], Body}} end, #{ attrs => [private], args => [{"e", EventType}], @@ -1580,7 +1580,7 @@ simpl_proj(Env, I, Expr) -> -spec get_catchalls([fcase()]) -> [fcase()]. get_catchalls(Alts) -> - [ C || C = {'case', {var, _, _}, _} <- Alts ]. + [ C || C = {'case', {var, _}, _} <- Alts ]. %% The scode compiler can't handle multiple catch-alls, so we need to nest them %% inside each other. Instead of From 43c8328615ae3752c1e80ea74a9dbade8123e655 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 29 Jun 2023 15:46:23 +0400 Subject: [PATCH 07/41] Prepare v7.2.1 release (#466) --- CHANGELOG.md | 7 ++++++- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f80e3da..998354f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Fixed +## [7.2.1] +### Fixed +- Fixed bugs with the newly added debugging symbols + ## [7.2.0] ### Added - Toplevel compile-time constants @@ -395,7 +399,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.2.0...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.2.1...HEAD +[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1 [7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0 [7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0 [7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1 diff --git a/rebar.config b/rebar.config index 4703105..de1f8b7 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "7.2.0"}, +{relx, [{release, {aesophia, "7.2.1"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index 8a34546..c1deacd 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.2.0"}, + {vsn, "7.2.1"}, {registered, []}, {applications, [kernel, From 86d7b36ba751002f534479f005ef220963184878 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 17 Jul 2023 13:32:11 +0300 Subject: [PATCH 08/41] Unify typesigs when implementing interface funs (#469) --- CHANGELOG.md | 1 + src/aeso_ast_infer_types.erl | 19 +++++++++++++------ test/aeso_compiler_tests.erl | 8 ++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 998354f..52bf16f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed ### Fixed +- Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function. ## [7.2.1] ### Fixed diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 6a71813..d74358b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1582,7 +1582,7 @@ check_reserved_entrypoints(Funs) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) -> Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type), TypeSig = {type_sig, Ann, none, Named, Args, Ret}, - register_implementation(Id, TypeSig), + register_implementation(Env, Id, TypeSig), {{Name, TypeSig}, {fun_decl, Ann, Id, Type1}}; check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) -> type_error({fundecl_must_have_funtype, Ann, Id, Type}), @@ -1590,13 +1590,16 @@ check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) -> %% Register the function FunId as implemented by deleting it from the functions %% to be implemented table if it is included there, or return true otherwise. --spec register_implementation(FunId, FunSig) -> true | no_return() when +-spec register_implementation(env(), FunId, FunSig) -> true | no_return() when FunId :: aeso_syntax:id(), FunSig :: typesig(). -register_implementation(Id, Sig) -> +register_implementation(Env, Id, Sig) -> Name = name(Id), case ets_lookup(functions_to_implement, Name) of - [{Name, Interface, Decl = {fun_decl, _, DeclId, _}}] -> + [{Name, Interface, Decl = {fun_decl, _, DeclId, FunT}}] -> + When = {implement_interface_fun, aeso_syntax:get_ann(Sig), Name, name(Interface)}, + unify(Env, typesig_to_fun_t(Sig), FunT, When), + DeclStateful = aeso_syntax:get_ann(stateful, Decl, false), DeclPayable = aeso_syntax:get_ann(payable, Decl, false), @@ -1624,7 +1627,7 @@ infer_nonrec(Env, LetFun) -> create_constraints(), NewLetFun = {{_, Sig}, _} = infer_letfun(Env, LetFun), check_special_funs(Env, NewLetFun), - register_implementation(get_letfun_id(LetFun), Sig), + register_implementation(Env, get_letfun_id(LetFun), Sig), solve_then_destroy_and_report_unsolved_constraints(Env), Result = {TypeSig, _} = instantiate(NewLetFun), print_typesig(TypeSig), @@ -1654,7 +1657,7 @@ infer_letrec(Env, Defs) -> Inferred = [ begin Res = {{Name, TypeSig}, LetFun} = infer_letfun(ExtendEnv, LF), - register_implementation(get_letfun_id(LetFun), TypeSig), + register_implementation(Env, get_letfun_id(LetFun), TypeSig), Got = proplists:get_value(Name, Funs), Expect = typesig_to_fun_t(TypeSig), unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}), @@ -4165,6 +4168,10 @@ pp_when({var_args, Ann, Fun}) -> {pos(Ann) , io_lib:format("when resolving arguments of variadic function `~s`", [pp_expr(Fun)]) }; +pp_when({implement_interface_fun, Ann, Entrypoint, Interface}) -> + { pos(Ann) + , io_lib:format("when implementing the entrypoint `~s` from the interface `~s`", [Entrypoint, Interface]) + }; pp_when(unknown) -> {pos(0,0), ""}. -spec pp_why_record(why_record()) -> {pos(), iolist()}. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index ed50821..fc47629 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -871,10 +871,10 @@ failing_contracts() -> "Trying to implement or extend an undefined interface `Z`">> ]) , ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type, - [<>]) + [<> + ]) , ?TYPE_ERROR(polymorphism_contract_missing_implementation, [<> From 3b0ca28c8e779567e860b8337b3508e801231b75 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 23 Aug 2023 09:43:49 +0200 Subject: [PATCH 09/41] Improve constraint solving (#480) * Clean up constraint solving a bit * Make unify always return true or false * Remove unused unify_throws field from Env * Better structure for constraint solving * Fix formatting of if_branches error * More cleanup --- src/aeso_ast_infer_types.erl | 328 ++++++++++++++++------------------- 1 file changed, 151 insertions(+), 177 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index d74358b..ae75468 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -154,7 +154,6 @@ , in_pattern = false :: boolean() , 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 @@ -1661,7 +1660,7 @@ infer_letrec(Env, Defs) -> Got = proplists:get_value(Name, Funs), Expect = typesig_to_fun_t(TypeSig), unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}), - solve_constraints(Env), + solve_all_constraints(Env), ?PRINT_TYPES("Checked ~s : ~s\n", [Name, pp(dereference_deep(Got))]), Res @@ -2576,61 +2575,118 @@ get_constraints() -> destroy_constraints() -> ets_delete(constraints). --spec solve_constraints(env()) -> ok. -solve_constraints(Env) -> - %% First look for record fields that appear in only one type definition - IsAmbiguous = - fun(#field_constraint{ - record_t = RecordType, - field = Field={id, _Attrs, FieldName}, - field_t = FieldType, - kind = Kind, - context = When }) -> - Arity = fun_arity(dereference_deep(FieldType)), - FieldInfos = case Arity of - none -> lookup_record_field(Env, FieldName, Kind); - _ -> lookup_record_field_arity(Env, FieldName, Arity, Kind) - end, - case FieldInfos of - [] -> - type_error({undefined_field, Field}), - false; - [#field_info{field_t = FldType, record_t = RecType}] -> - create_freshen_tvars(), - FreshFldType = freshen(FldType), - FreshRecType = freshen(RecType), - destroy_freshen_tvars(), - unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}), - unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}), - false; - _ -> - %% ambiguity--need cleverer strategy - true - end; - (_) -> true - end, - AmbiguousConstraints = lists:filter(IsAmbiguous, get_constraints()), +%% Solve all constraints by iterating until no-progress - % The two passes on AmbiguousConstraints are needed - solve_ambiguous_constraints(Env, AmbiguousConstraints ++ AmbiguousConstraints). +-spec solve_all_constraints(env()) -> ok. +solve_all_constraints(Env) -> + Constraints = [C || C <- get_constraints(), not one_shot_field_constraint(Env, C) ], + solve_constraints_top(Env, Constraints). --spec solve_ambiguous_constraints(env(), [constraint()]) -> ok. -solve_ambiguous_constraints(Env, Constraints) -> - Unknown = solve_known_record_types(Env, Constraints), - if Unknown == [] -> ok; - length(Unknown) < length(Constraints) -> - %% progress! Keep trying. - solve_ambiguous_constraints(Env, Unknown); +solve_constraints_top(Env, Constraints) -> + UnsolvedCs = solve_constraints(Env, Constraints), + Progress = solve_unknown_record_constraints(Env, UnsolvedCs), + + if length(UnsolvedCs) < length(Constraints) orelse Progress == true -> + solve_constraints_top(Env, UnsolvedCs); true -> - case solve_unknown_record_types(Env, Unknown) of - true -> %% Progress! - solve_ambiguous_constraints(Env, Unknown); - _ -> ok %% No progress. Report errors later. - end + ok end. +-spec solve_constraints(env(), [constraint()]) -> [constraint()]. +solve_constraints(Env, Constraints) -> + [ C1 || C <- Constraints, C1 <- [dereference_deep(C)], not solve_constraint(Env, C1) ]. + +solve_unknown_record_constraints(Env, Constraints) -> + FieldCs = lists:filter(fun(#field_constraint{record_t = {uvar, _, _}}) -> true; (_) -> false end, Constraints), + FieldCsUVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- FieldCs]), + + FieldConstraint = fun(#field_constraint{ field = F, kind = K, context = Ctx }) -> {K, Ctx, F} end, + FieldsForUVar = fun(UVar) -> + [ FieldConstraint(FC) || FC = #field_constraint{record_t = U} <- FieldCs, U == UVar ] + end, + + + Solutions = [ solve_for_uvar(Env, UVar, FieldsForUVar(UVar)) || UVar <- FieldCsUVars ], + case lists:member(true, Solutions) of + true -> true; + false -> Solutions + end. + +%% -- Simple constraints -- +%% Returns true if solved (unified or type error) +solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) -> + false; +solve_constraint(Env, #field_constraint{record_t = RecordType, + field = Field = {id, _As, FieldName}, + field_t = FieldType, + context = When}) -> + RecId = record_type_name(RecordType), + Attrs = aeso_syntax:get_ann(RecId), + case lookup_type(Env, RecId) of + {_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t -> + FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields], + case proplists:get_value(FieldName, FieldTypes) of + undefined -> + type_error({missing_field, Field, RecId}); + FldType -> + solve_field_constraint(Env, FieldType, FldType, RecordType, app_t(Attrs, RecId, Formals), When) + end; + _ -> + type_error({not_a_record_type, instantiate(RecordType), When}) + end, + true; +solve_constraint(Env, C = #dependent_type_constraint{}) -> + check_named_argument_constraint(Env, C); +solve_constraint(Env, C = #named_argument_constraint{}) -> + check_named_argument_constraint(Env, C); +solve_constraint(_Env, {is_bytes, _}) -> false; +solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) -> + A = unfold_types_in_type(Env, dereference(A0)), + B = unfold_types_in_type(Env, dereference(B0)), + C = unfold_types_in_type(Env, dereference(C0)), + case {A, B, C} of + {{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); + {{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); + {_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + _ -> false + end; +solve_constraint(_, _) -> false. + +one_shot_field_constraint(Env, #field_constraint{record_t = RecordType, + field = Field = {id, _As, FieldName}, + field_t = FieldType, + kind = Kind, + context = When}) -> + Arity = fun_arity(dereference_deep(FieldType)), + FieldInfos = case Arity of + none -> lookup_record_field(Env, FieldName, Kind); + _ -> lookup_record_field_arity(Env, FieldName, Arity, Kind) + end, + + case FieldInfos of + [] -> + type_error({undefined_field, Field}), + true; + [#field_info{field_t = FldType, record_t = RecType}] -> + solve_field_constraint(Env, FieldType, FldType, RecordType, RecType, When), + true; + _ -> + false + end; +one_shot_field_constraint(_Env, _Constraint) -> + false. + + +solve_field_constraint(Env, FieldType, FldType, RecordType, RecType, When) -> + create_freshen_tvars(), + FreshFldType = freshen(FldType), + FreshRecType = freshen(RecType), + destroy_freshen_tvars(), + unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}), + unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}). + solve_then_destroy_and_report_unsolved_constraints(Env) -> - solve_constraints(Env), + solve_all_constraints(Env), destroy_and_report_unsolved_constraints(Env). destroy_and_report_unsolved_constraints(Env) -> @@ -2661,21 +2717,10 @@ destroy_and_report_unsolved_constraints(Env) -> (_) -> false end, OtherCs5), - Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ], - S == unsolved ], - [ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ], - - Unknown = solve_known_record_types(Env, FieldCs), - if Unknown == [] -> ok; - true -> - case solve_unknown_record_types(Env, Unknown) of - true -> ok; - Errors -> [ type_error(Err) || Err <- Errors ] - end - end, - + check_field_constraints(Env, FieldCs), check_record_create_constraints(Env, CreateCs), check_is_contract_constraints(Env, ContractCs), + check_named_args_constraints(Env, NamedArgCs), check_bytes_constraints(Env, BytesCs), check_aens_resolve_constraints(Env, AensResolveCs), check_oracle_type_constraints(Env, OracleTypeCs), @@ -2693,20 +2738,21 @@ get_oracle_type(_Fun, _Args, _Ret) -> false. %% -- Named argument constraints -- -%% If false, a type error has been emitted, so it's safe to drop the constraint. --spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved. +%% True if solved (unified or type error), false otherwise +-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false. check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) -> - unsolved; + false; check_named_argument_constraint(Env, C = #named_argument_constraint{ args = Args, name = Id = {id, _, Name}, type = Type }) -> case [ T || {named_arg_t, _, {id, _, Name1}, T, _} <- Args, Name1 == Name ] of [] -> - type_error({bad_named_argument, Args, Id}), - false; - [T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true - end; + type_error({bad_named_argument, Args, Id}); + [T] -> + unify(Env, T, Type, {check_named_arg_constraint, C}) + end, + true; check_named_argument_constraint(Env, #dependent_type_constraint{ named_args_t = NamedArgsT0, named_args = NamedArgs, @@ -2723,10 +2769,11 @@ check_named_argument_constraint(Env, ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]), GenType1 = specialize_dependent_type(ArgEnv, GenType), - unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}), - true; - _ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true - end. + unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}); + _ -> + unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}) + end, + true. specialize_dependent_type(Env, Type) -> case dereference(Type) of @@ -2742,53 +2789,16 @@ specialize_dependent_type(Env, Type) -> _ -> Type %% Currently no deep dependent types end. -%% -- Bytes constraints -- +check_field_constraints(Env, Constraints) -> + UnsolvedFieldCs = solve_constraints(Env, Constraints), + case solve_unknown_record_constraints(Env, UnsolvedFieldCs) of + true -> ok; + Errors -> [ type_error(Err) || Err <- Errors ] + end. -solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) -> - not_solved; -solve_constraint(Env, C = #field_constraint{record_t = RecType, - field = FieldName, - field_t = FieldType, - context = When}) -> - RecId = record_type_name(RecType), - Attrs = aeso_syntax:get_ann(RecId), - case lookup_type(Env, RecId) of - {_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t -> - FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields], - {id, _, FieldString} = FieldName, - case proplists:get_value(FieldString, FieldTypes) of - undefined -> - type_error({missing_field, FieldName, RecId}), - not_solved; - FldType -> - create_freshen_tvars(), - FreshFldType = freshen(FldType), - FreshRecType = freshen(app_t(Attrs, RecId, Formals)), - destroy_freshen_tvars(), - unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}), - unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}), - C - end; - _ -> - type_error({not_a_record_type, instantiate(RecType), When}), - not_solved - end; -solve_constraint(Env, C = #dependent_type_constraint{}) -> - check_named_argument_constraint(Env, C); -solve_constraint(Env, C = #named_argument_constraint{}) -> - check_named_argument_constraint(Env, C); -solve_constraint(_Env, {is_bytes, _}) -> ok; -solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) -> - A = unfold_types_in_type(Env, dereference(A0)), - B = unfold_types_in_type(Env, dereference(B0)), - C = unfold_types_in_type(Env, dereference(C0)), - case {A, B, C} of - {{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); - {{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); - {_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); - _ -> ok - end; -solve_constraint(_, _) -> ok. +check_named_args_constraints(Env, Constraints) -> + UnsolvedNamedArgCs = solve_constraints(Env, Constraints), + [ type_error({unsolved_named_argument_constraint, C}) || C <- UnsolvedNamedArgCs ]. check_bytes_constraints(Env, Constraints) -> InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints, @@ -2885,30 +2895,6 @@ check_is_contract_constraints(Env, [C | Cs]) -> end, check_is_contract_constraints(Env, Cs). --spec solve_unknown_record_types(env(), [field_constraint()]) -> true | [tuple()]. -solve_unknown_record_types(Env, Unknown) -> - UVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- Unknown]), - Solutions = [solve_for_uvar(Env, UVar, [{Kind, When, Field} - || #field_constraint{record_t = U, field = Field, kind = Kind, context = When} <- Unknown, - U == UVar]) - || UVar <- UVars], - case lists:member(true, Solutions) of - true -> true; - false -> Solutions - end. - -%% This will solve all kinds of constraints but will only return the -%% unsolved field constraints --spec solve_known_record_types(env(), [constraint()]) -> [field_constraint()]. -solve_known_record_types(Env, Constraints) -> - DerefConstraints = lists:map(fun(C = #field_constraint{record_t = RecordType}) -> - C#field_constraint{record_t = dereference(RecordType)}; - (C) -> dereference_deep(C) - end, Constraints), - SolvedConstraints = lists:map(fun(C) -> solve_constraint(Env, dereference_deep(C)) end, DerefConstraints), - Unsolved = DerefConstraints--SolvedConstraints, - lists:filter(fun(#field_constraint{}) -> true; (_) -> false end, Unsolved). - record_type_name({app_t, _Attrs, RecId, _Args}) when ?is_type_id(RecId) -> RecId; record_type_name(RecId) when ?is_type_id(RecId) -> @@ -3087,16 +3073,12 @@ unify0(Env, A, B, Variance, When) -> unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) -> true; unify1(_Env, {uvar, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) -> - type_error({unify_varargs, When}); -unify1(Env, {uvar, A, R}, T, _Variance, When) -> + type_error({unify_varargs, When}), + false; +unify1(_Env, {uvar, A, R}, T, _Variance, When) -> case occurs_check(R, T) of true -> - if - Env#env.unify_throws -> - cannot_unify({uvar, A, R}, T, none, When); - true -> - ok - end, + cannot_unify({uvar, A, R}, T, none, When), false; false -> ets_insert(type_vars, {R, T}), @@ -3123,18 +3105,13 @@ unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) -> case is_subtype(Env, NameA, NameB, Variance) of true -> true; false -> - if - Env#env.unify_throws -> - IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse - is_subtype(Env, NameA, NameB, covariant), - Cxt = case IsSubtype of - true -> Variance; - false -> none - end, - cannot_unify(A, B, Cxt, When); - true -> - ok - end, + IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse + is_subtype(Env, NameA, NameB, covariant), + Cxt = case IsSubtype of + true -> Variance; + false -> none + end, + cannot_unify(A, B, Cxt, When), false end; unify1(_Env, {qid, _, Name}, {qid, _, Name}, _Variance, _When) -> @@ -3148,9 +3125,11 @@ unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, unify0(Env, Else1, Else2, Variance, When); unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) -> - type_error({unify_varargs, When}); + type_error({unify_varargs, When}), + false; unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) -> - type_error({unify_varargs, When}); + type_error({unify_varargs, When}), + false; unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When) when length(Args1) == length(Args2) -> unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso @@ -3172,7 +3151,7 @@ unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, Variance, When) when length(As) == length(Bs) -> unify0(Env, As, Bs, Variance, When); unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, Variance, When) -> - unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}), + unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}) andalso unify1(Env, Type1, Type2, Variance, When); %% The grammar is a bit inconsistent about whether types without %% arguments are represented as applications to an empty list of @@ -3181,13 +3160,8 @@ unify1(Env, {app_t, _, T, []}, B, Variance, When) -> unify0(Env, T, B, Variance, When); unify1(Env, A, {app_t, _, T, []}, Variance, When) -> unify0(Env, A, T, Variance, When); -unify1(Env, A, B, _Variance, When) -> - if - Env#env.unify_throws -> - cannot_unify(A, B, none, When); - true -> - ok - end, +unify1(_Env, A, B, _Variance, When) -> + cannot_unify(A, B, none, When), false. is_subtype(_Env, NameA, NameB, invariant) -> @@ -4120,8 +4094,8 @@ pp_when({if_branches, Then, ThenType0, Else, ElseType0}) -> Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ], {pos(element(1, hd(Branches))), io_lib:format("when comparing the types of the if-branches\n" - "~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)]) - || {B, BType} <- Branches ] ])}; + "~s", [string:join([ io_lib:format("~s (at ~s)", [pp_typed(" - ", B, BType), pp_loc(B)]) + || {B, BType} <- Branches ], "\n")])}; pp_when({case_pat, Pat, PatType0, ExprType0}) -> {PatType, ExprType} = instantiate({PatType0, ExprType0}), {pos(Pat), From 08fa372c2408165f53272df941bf73d64a144b53 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 24 Aug 2023 09:43:40 +0200 Subject: [PATCH 10/41] Improve independence analysis in code optimizer (#483) * Improve independence analysis * Changelog updated --- CHANGELOG.md | 1 + src/aeso_fcode_to_fate.erl | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52bf16f..96e8df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Fixed - Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function. +- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions. ## [7.2.1] ### Fixed diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 5c99d70..96ffe69 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1141,11 +1141,16 @@ independent({i, _, I}, {i, _, J}) -> StackI = lists:member(?a, [WI | RI]), StackJ = lists:member(?a, [WJ | RJ]), - if WI == pc; WJ == pc -> false; %% no jumps - not (PureI or PureJ) -> false; %% at least one is pure - StackI and StackJ -> false; %% cannot both use the stack - WI == WJ -> false; %% cannot write to the same register - true -> + ReadStoreI = [] /= [ x || {store, _} <- RI ], + ReadStoreJ = [] /= [ x || {store, _} <- RJ ], + + if WI == pc; WJ == pc -> false; %% no jumps + not (PureI or PureJ) -> false; %% at least one is pure + StackI and StackJ -> false; %% cannot both use the stack + WI == WJ -> false; %% cannot write to the same register + ReadStoreI and not PureJ -> false; %% can't read store/state if other is impure + ReadStoreJ and not PureI -> false; %% can't read store/state if other is impure + true -> %% and cannot write to each other's inputs not lists:member(WI, RJ) andalso not lists:member(WJ, RI) From 67948513d5a582491ef521e353dcab25934c7f33 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 24 Aug 2023 10:59:42 +0200 Subject: [PATCH 11/41] Prepare v7.3.0 (#484) --- CHANGELOG.md | 6 +++++- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e8df8..aad365a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed ### Fixed + +## [7.3.0] +### Fixed - Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function. - Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions. @@ -401,7 +404,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.2.1...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.3.0...HEAD +[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 [7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1 [7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0 [7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0 diff --git a/rebar.config b/rebar.config index de1f8b7..1b0248c 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "7.2.1"}, +{relx, [{release, {aesophia, "7.3.0"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index c1deacd..b39415c 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.2.1"}, + {vsn, "7.3.0"}, {registered, []}, {applications, [kernel, From 9b518150c375240446e5f0c2ca120af2eb267fb5 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 24 Aug 2023 13:00:42 +0200 Subject: [PATCH 12/41] Fix typo in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aad365a..ed23acb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -405,7 +405,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 pass a (stubbed) contract string. [Unreleased]: https://github.com/aeternity/aesophia/compare/v7.3.0...HEAD -[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 +[7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 [7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1 [7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0 [7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0 From 002e55d5291dfe2947131d205bf6ae228184e9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:50:37 +0200 Subject: [PATCH 13/41] Name lambdas by their locations (#486) * Name lambdas by their locations * changelog --- CHANGELOG.md | 3 + src/aeso_ast_to_fcode.erl | 133 ++++++++++++++++++++++------------- test/aeso_compiler_tests.erl | 4 +- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed23acb..d2aced6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +- Names of lifted lambdas now consist of parent function's name and their + position in the source code. ### Removed ### Fixed +- Lifted lambdas get their names assigned deterministically. ## [7.3.0] ### Fixed diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index e219267..38da87e 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -58,7 +58,10 @@ | {contract_code, string()} %% for CREATE, by name | {typerep, ftype()}. --type fann() :: [ {file, aeso_syntax:ann_file()} | {line, aeso_syntax:ann_line()} ]. +-type fann() :: [ {file, aeso_syntax:ann_file()} | + {line, aeso_syntax:ann_line()} | + {col, aeso_syntax:ann_col()} + ]. -type fexpr() :: {lit, fann(), flit()} | {nil, fann()} @@ -387,11 +390,23 @@ to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) -> Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls), to_fcode(Env1, Code). +-spec ann_loc(aeso_syntax:ann() | fann()) -> {File, Line, Column} when + File :: string() | none, + Line :: non_neg_integer() | none, + Column :: non_neg_integer() | none. +ann_loc(Ann) -> + File = proplists:get_value(file, Ann, none), + Line = proplists:get_value(line, Ann, none), + Col = proplists:get_value(col, Ann, none), + {File, Line, Col}. + -spec to_fann(aeso_syntax:ann()) -> fann(). to_fann(Ann) -> - File = proplists:lookup(file, Ann), - Line = proplists:lookup(line, Ann), - [ X || X <- [File, Line], X =/= none ]. + {File, Line, Col} = ann_loc(Ann), + [ {Tag, X} || + {Tag, X} <- [{file, File}, {line, Line}, {col, Col}], + X =/= none, X =/= no_file + ]. -spec get_fann(fexpr()) -> fann(). get_fann(FExpr) -> element(2, FExpr). @@ -1276,10 +1291,13 @@ event_function(_Env = #{event_type := {variant_t, EventCons}}, EventType = {vari -spec lambda_lift(fcode()) -> fcode(). lambda_lift(FCode = #{ functions := Funs, state_layout := StateLayout }) -> - init_lambda_funs(), - Funs1 = maps:map(fun(_, Body) -> lambda_lift_fun(StateLayout, Body) end, Funs), - NewFuns = get_lambda_funs(), - FCode#{ functions := maps:merge(Funs1, NewFuns) }. + NewFuns = + [ {FunName, FunDef} + || {ParentName, ParentDef} <- maps:to_list(Funs), + {NewParentDef, Lambdas} <- [lambda_lift_fun(StateLayout, ParentName, ParentDef)], + {FunName, FunDef} <- [{ParentName, NewParentDef} | maps:to_list(Lambdas)] + ], + FCode#{ functions := maps:from_list(NewFuns) }. -define(lambda_key, '%lambdalifted'). @@ -1289,16 +1307,35 @@ init_lambda_funs() -> put(?lambda_key, #{}). -spec get_lambda_funs() -> term(). get_lambda_funs() -> erase(?lambda_key). --spec add_lambda_fun(fun_def()) -> fun_name(). -add_lambda_fun(Def) -> - Name = fresh_fun(), +-spec add_lambda_fun(fun_name(), fann(), fun_def()) -> fun_name(). +add_lambda_fun(Parent, FAnn, Def) -> Funs = get(?lambda_key), - put(?lambda_key, Funs#{ Name => Def }), + LambdaId = maps:get({fresh, Parent}, Funs, 0), + Name = lambda_name(FAnn, LambdaId, Parent), + put(?lambda_key, Funs#{ Name => Def, {fresh, Parent} => LambdaId + 1}), Name. --spec lambda_lift_fun(state_layout(), fun_def()) -> fun_def(). -lambda_lift_fun(Layout, Def = #{ body := Body }) -> - Def#{ body := lambda_lift_expr(Layout, Body) }. +-spec lambda_name(fann(), non_neg_integer(), fun_name()) -> fun_name(). +lambda_name(FAnn, Id, PName) -> + PSName = case PName of + {entrypoint, N} -> [binary_to_list(N)]; + {local_fun, Ns} -> Ns + end, + {_File, Line, Col} = ann_loc(FAnn), + Name = PSName ++ + [ "%lambda" + , if is_integer(Line) -> integer_to_list(Line); true -> "" end + , if is_integer(Col) -> integer_to_list(Col); true -> "" end + , integer_to_list(Id)], + {local_fun, Name}. + +-spec lambda_lift_fun(state_layout(), fun_name(), fun_def()) -> {fun_def(), #{var_name() => term()}}. +lambda_lift_fun(Layout, Name, Def = #{ body := Body }) -> + %% Not thread safe! We initialize state per functions not to depend on the order in which + %% functions are processed. + init_lambda_funs(), + NewDef = Def#{ body := lambda_lift_expr(Layout, Name, Body) }, + {NewDef, get_lambda_funs()}. -spec lifted_fun([var_name()], [var_name()], fexpr()) -> fun_def(). lifted_fun([Z], Xs, Body) -> @@ -1316,21 +1353,20 @@ lifted_fun(FVs, Xs, Body) -> body => lists:foldr(Proj, Body, indexed(FVs)) }. --spec make_closure([var_name()], [var_name()], fexpr()) -> Closure when +-spec make_closure(fun_name(), fann(), [var_name()], [var_name()], fexpr()) -> Closure when Closure :: fexpr(). -make_closure(FVs, Xs, Body) -> - Fun = add_lambda_fun(lifted_fun(FVs, Xs, Body)), - FAnn = get_fann(Body), +make_closure(ParentName, FAnn, FVs, Xs, Body) -> + Name = add_lambda_fun(ParentName, FAnn, lifted_fun(FVs, Xs, Body)), Tup = fun([Y]) -> Y; (Ys) -> {tuple, FAnn, Ys} end, - {closure, FAnn, Fun, Tup([{var, FAnn, Y} || Y <- FVs])}. + {closure, FAnn, Name, Tup([{var, FAnn, Y} || Y <- FVs])}. --spec lambda_lift_expr(state_layout(), fexpr()) -> Closure when +-spec lambda_lift_expr(state_layout(), fun_name(), fexpr()) -> Closure when Closure :: fexpr(). -lambda_lift_expr(Layout, L = {lam, _, Xs, Body}) -> +lambda_lift_expr(Layout, Name, L = {lam, FAnn, Xs, Body}) -> FVs = free_vars(L), - make_closure(FVs, Xs, lambda_lift_expr(Layout, Body)); -lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u -> - [Tag, _, F, Ar | _] = tuple_to_list(UExpr), + make_closure(Name, FAnn, FVs, Xs, lambda_lift_expr(Layout, Name, Body)); +lambda_lift_expr(Layout, Name, UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u -> + [Tag, FAnn, F, Ar | _] = tuple_to_list(UExpr), ExtraArgs = case UExpr of {builtin_u, _, _, _, TypeArgs} -> TypeArgs; _ -> [] @@ -1341,41 +1377,41 @@ lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExp builtin_u -> builtin_to_fcode(Layout, get_fann(UExpr), F, Args); def_u -> {def, get_fann(UExpr), F, Args} end, - make_closure([], Xs, Body); -lambda_lift_expr(Layout, {remote_u, FAnn, ArgsT, RetT, Ct, F}) -> + make_closure(Name, FAnn, [], Xs, Body); +lambda_lift_expr(Layout, Name, {remote_u, FAnn, ArgsT, RetT, Ct, F}) -> FVs = free_vars(Ct), - Ct1 = lambda_lift_expr(Layout, Ct), + Ct1 = lambda_lift_expr(Layout, Name, Ct), NamedArgCount = 3, Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ], Args = [{var, [], X} || X <- Xs], - make_closure(FVs, Xs, {remote, FAnn, ArgsT, RetT, Ct1, F, Args}); -lambda_lift_expr(Layout, Expr) -> + make_closure(Name, FAnn, FVs, Xs, {remote, FAnn, ArgsT, RetT, Ct1, F, Args}); +lambda_lift_expr(Layout, Name, Expr) -> case Expr of {lit, _, _} -> Expr; {nil, _} -> Expr; {var, _, _} -> Expr; {closure, _, _, _} -> Expr; - {def, FAnn, D, As} -> {def, FAnn, D, lambda_lift_exprs(Layout, As)}; - {builtin, FAnn, B, As} -> {builtin, FAnn, B, lambda_lift_exprs(Layout, As)}; - {remote, FAnn, ArgsT, RetT, Ct, F, As} -> {remote, FAnn, ArgsT, RetT, lambda_lift_expr(Layout, Ct), F, lambda_lift_exprs(Layout, As)}; - {con, FAnn, Ar, C, As} -> {con, FAnn, Ar, C, lambda_lift_exprs(Layout, As)}; - {tuple, FAnn, As} -> {tuple, FAnn, lambda_lift_exprs(Layout, As)}; - {proj, FAnn, A, I} -> {proj, FAnn, lambda_lift_expr(Layout, A), I}; - {set_proj, FAnn, A, I, B} -> {set_proj, FAnn, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)}; - {op, FAnn, Op, As} -> {op, FAnn, Op, lambda_lift_exprs(Layout, As)}; - {'let', FAnn, X, A, B} -> {'let', FAnn, X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)}; - {funcall, FAnn, A, Bs} -> {funcall, FAnn, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)}; - {set_state, FAnn, R, A} -> {set_state, FAnn, R, lambda_lift_expr(Layout, A)}; + {def, FAnn, D, As} -> {def, FAnn, D, lambda_lift_exprs(Layout, Name, As)}; + {builtin, FAnn, B, As} -> {builtin, FAnn, B, lambda_lift_exprs(Layout, Name, As)}; + {remote, FAnn, ArgsT, RetT, Ct, F, As} -> {remote, FAnn, ArgsT, RetT, lambda_lift_expr(Layout, Name, Ct), F, lambda_lift_exprs(Layout, Name, As)}; + {con, FAnn, Ar, C, As} -> {con, FAnn, Ar, C, lambda_lift_exprs(Layout, Name, As)}; + {tuple, FAnn, As} -> {tuple, FAnn, lambda_lift_exprs(Layout, Name, As)}; + {proj, FAnn, A, I} -> {proj, FAnn, lambda_lift_expr(Layout, Name, A), I}; + {set_proj, FAnn, A, I, B} -> {set_proj, FAnn, lambda_lift_expr(Layout, Name, A), I, lambda_lift_expr(Layout, Name, B)}; + {op, FAnn, Op, As} -> {op, FAnn, Op, lambda_lift_exprs(Layout, Name, As)}; + {'let', FAnn, X, A, B} -> {'let', FAnn, X, lambda_lift_expr(Layout, Name, A), lambda_lift_expr(Layout, Name, B)}; + {funcall, FAnn, A, Bs} -> {funcall, FAnn, lambda_lift_expr(Layout, Name, A), lambda_lift_exprs(Layout, Name, Bs)}; + {set_state, FAnn, R, A} -> {set_state, FAnn, R, lambda_lift_expr(Layout, Name, A)}; {get_state, _, _} -> Expr; - {switch, FAnn, S} -> {switch, FAnn, lambda_lift_expr(Layout, S)}; - {split, Type, X, Alts} -> {split, Type, X, lambda_lift_exprs(Layout, Alts)}; - {nosplit, Rens, A} -> {nosplit, Rens, lambda_lift_expr(Layout, A)}; - {'case', P, S} -> {'case', P, lambda_lift_expr(Layout, S)} + {switch, FAnn, S} -> {switch, FAnn, lambda_lift_expr(Layout, Name, S)}; + {split, Type, X, Alts} -> {split, Type, X, lambda_lift_exprs(Layout, Name, Alts)}; + {nosplit, Rens, A} -> {nosplit, Rens, lambda_lift_expr(Layout, Name, A)}; + {'case', P, S} -> {'case', P, lambda_lift_expr(Layout, Name, S)} end. --spec lambda_lift_exprs(state_layout(), [fexpr()]) -> [Closure] when +-spec lambda_lift_exprs(state_layout(), fun_name(), [fexpr()]) -> [Closure] when Closure :: fexpr(). -lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As]. +lambda_lift_exprs(Layout, Name, As) -> [lambda_lift_expr(Layout, Name, A) || A <- As]. %% -- Optimisations ---------------------------------------------------------- @@ -1900,9 +1936,6 @@ fresh_name_save(Name) -> -spec fresh_name() -> var_name(). fresh_name() -> fresh_name("%"). --spec fresh_fun() -> fun_name(). -fresh_fun() -> {local_fun, [fresh_name("^")]}. - -spec fresh_name(string()) -> var_name(). fresh_name(Prefix) -> N = get('%fresh'), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index fc47629..26c5d64 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1313,7 +1313,9 @@ validate(Contract1, Contract2) -> true -> [debug_mode]; false -> [] end ++ - [{include, {file_system, [aeso_test_utils:contract_path()]}}]); + [ {src_file, lists:concat([Contract2, ".aes"])} + , {include, {file_system, [aeso_test_utils:contract_path()]}} + ]); Error -> print_and_throw(Error) end. From 33229c35131516e6f91ada1b74ee1505eb94a731 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 5 Sep 2023 10:04:46 +0200 Subject: [PATCH 14/41] Prepare release v7.4.0 (#487) --- CHANGELOG.md | 9 +++++++-- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2aced6..8ce4b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +### Removed +### Fixed + +## [7.4.0] +### Changed - Names of lifted lambdas now consist of parent function's name and their position in the source code. -### Removed ### Fixed - Lifted lambdas get their names assigned deterministically. @@ -407,7 +411,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.3.0...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.4.0...HEAD +[7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0 [7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 [7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1 [7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0 diff --git a/rebar.config b/rebar.config index 1b0248c..e2f0b23 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "7.3.0"}, +{relx, [{release, {aesophia, "7.4.0"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index b39415c..308e080 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.3.0"}, + {vsn, "7.4.0"}, {registered, []}, {applications, [kernel, From 03d6dd6ca2736993ae905e7471af4a0725d927a3 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 14 Sep 2023 15:00:30 +0200 Subject: [PATCH 15/41] Improve resolution of relative includes (#489) * Add aeso_utils:canonical_dir/1 * Add current file directory when resolving includes * Add CHANGELOG * Add documentation * Add a test case * Properly keep track of src_dir --- CHANGELOG.md | 2 ++ docs/sophia_features.md | 18 ++++++++++ src/aeso_compiler.erl | 7 ++-- src/aeso_parse_lib.erl | 9 ++++- src/aeso_parser.erl | 54 +++++++++++++++++++++-------- src/aeso_utils.erl | 14 +++++++- test/aeso_compiler_tests.erl | 1 + test/contracts/dir1/bar.aes | 4 +++ test/contracts/dir2/baz.aes | 3 ++ test/contracts/relative_include.aes | 3 ++ 10 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 test/contracts/dir1/bar.aes create mode 100644 test/contracts/dir2/baz.aes create mode 100644 test/contracts/relative_include.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce4b06..3d90868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +- Improve how includes with relative paths are resolved during parsing/compilation. Relative + include paths are now always relative to the file containing the `include` statement. ### Removed ### Fixed diff --git a/docs/sophia_features.md b/docs/sophia_features.md index e8170c0..3025f82 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -493,6 +493,24 @@ the file, except that error messages will refer to the original source locations. The language will try to include each file at most one time automatically, so even cyclic includes should be working without any special tinkering. +### Include files using relative paths + +When including code from another file using the `include` statement, the path +is relative to _the file that includes it_. Consider the following file tree: +``` +c1.aes +c3.aes +dir1/c2.aes +dir1/c3.aes +``` + +If `c1.aes` contains `include "c3.aes"` it will include the top level `c3.aes`, +while if `c2.aes` contained the same line it would as expected include +`dir1/c3.aes`. + +Note: Prior to v7.5.0, it would consider the include path relative to _the main +contract file_ (or any explicitly set include path). + ## Standard library Sophia offers [standard library](sophia_stdlib.md) which exposes some diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 1ff6ec3..5968081 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -42,6 +42,7 @@ | {include, {file_system, [string()]} | {explicit_files, #{string() => binary()}}} | {src_file, string()} + | {src_dir, string()} | {aci, aeso_aci:aci_type()}. -type options() :: [option()]. @@ -87,7 +88,9 @@ file(Filename) -> file(File, Options0) -> Options = add_include_path(File, Options0), case read_contract(File) of - {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); + {ok, Bin} -> + SrcDir = aeso_utils:canonical_dir(filename:dirname(File)), + from_string(Bin, [{src_file, File}, {src_dir, SrcDir} | Options]); {error, Error} -> Msg = lists:flatten([File,": ",file:format_error(Error)]), {error, [aeso_errors:new(file_error, Msg)]} @@ -99,7 +102,7 @@ add_include_path(File, Options) -> false -> Dir = filename:dirname(File), {ok, Cwd} = file:get_cwd(), - [{include, {file_system, [Cwd, Dir]}} | Options] + [{include, {file_system, [Cwd, aeso_utils:canonical_dir(Dir)]}} | Options] end. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}. diff --git a/src/aeso_parse_lib.erl b/src/aeso_parse_lib.erl index 4c50527..8246ef6 100644 --- a/src/aeso_parse_lib.erl +++ b/src/aeso_parse_lib.erl @@ -15,7 +15,7 @@ many/1, many1/1, sep/2, sep1/2, infixl/2, infixr/2]). --export([current_file/0, set_current_file/1, +-export([current_file/0, set_current_file/1, current_dir/0, set_current_dir/1, current_include_type/0, set_current_include_type/1]). %% -- Types ------------------------------------------------------------------ @@ -480,6 +480,13 @@ current_file() -> set_current_file(File) -> put('$current_file', File). +%% Current source directory +current_dir() -> + get('$current_dir'). + +set_current_dir(File) -> + put('$current_dir', File). + add_current_file({L, C}) -> {current_file(), L, C}; add_current_file(Pos) -> Pos. diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index d1d92cb..3c03bf0 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -19,6 +19,7 @@ -include("aeso_parse_lib.hrl"). -import(aeso_parse_lib, [current_file/0, set_current_file/1, + current_dir/0, set_current_dir/1, current_include_type/0, set_current_include_type/1]). -type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none(). @@ -58,6 +59,7 @@ run_parser(P, Inp, Opts) -> parse_and_scan(P, S, Opts) -> set_current_file(proplists:get_value(src_file, Opts, no_file)), + set_current_dir(proplists:get_value(src_dir, Opts, no_file)), set_current_include_type(proplists:get_value(include_type, Opts, none)), case aeso_scan:scan(S) of {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); @@ -556,6 +558,7 @@ bracket_list(P) -> brackets(comma_sep(P)). -spec pos_ann(ann_line(), ann_col()) -> ann(). pos_ann(Line, Col) -> [ {file, current_file()} + , {dir, current_dir()} , {include_type, current_include_type()} , {line, Line} , {col, Col} ]. @@ -696,7 +699,7 @@ expand_includes([], Included, Acc, Opts) -> end; expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) -> case get_include_code(File, Ann, Opts) of - {ok, Code} -> + {ok, AbsDir, Code} -> Hashed = hash_include(File, Code), case sets:is_element(Hashed, Included) of false -> @@ -706,9 +709,10 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op _ -> indirect end, Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), - Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}), + Opts2 = lists:keystore(src_dir, 1, Opts1, {src_dir, AbsDir}), + Opts3 = lists:keystore(include_type, 1, Opts2, {include_type, IncludeType}), Included1 = sets:add_element(Hashed, Included), - case parse_and_scan(file(), Code, Opts2) of + case parse_and_scan(file(), Code, Opts3) of {ok, AST1} -> expand_includes(AST1 ++ AST, Included1, Acc, Opts); Err = {error, _} -> @@ -726,13 +730,12 @@ expand_includes([E | AST], Included, Acc, Opts) -> read_file(File, Opts) -> case proplists:get_value(include, Opts, {explicit_files, #{}}) of {file_system, Paths} -> - CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ], - lists:foldr(fun(F, {error, _}) -> file:read_file(F); - (_F, OK) -> OK end, {error, not_found}, CandidateNames); + lists:foldr(fun(Path, {error, _}) -> read_file_(Path, File); + (_Path, OK) -> OK end, {error, not_found}, Paths); {explicit_files, Files} -> case maps:get(binary_to_list(File), Files, not_found) of not_found -> {error, not_found}; - Src -> {ok, Src} + Src -> {ok, File, Src} end; escript -> try @@ -741,7 +744,7 @@ read_file(File, Opts) -> Archive = proplists:get_value(archive, Sections), FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])), case zip:extract(Archive, [{file_list, [FileName]}, memory]) of - {ok, [{_, Src}]} -> {ok, Src}; + {ok, [{_, Src}]} -> {ok, escript, Src}; _ -> {error, not_found} end catch _:_ -> @@ -749,6 +752,13 @@ read_file(File, Opts) -> end end. +read_file_(Path, File) -> + AbsFile = filename:join(Path, File), + case file:read_file(AbsFile) of + {ok, Bin} -> {ok, aeso_utils:canonical_dir(filename:dirname(AbsFile)), Bin}; + Err -> Err + end. + stdlib_options() -> StdLibDir = aeso_stdlib:stdlib_include_path(), case filelib:is_dir(StdLibDir) of @@ -757,23 +767,37 @@ stdlib_options() -> end. get_include_code(File, Ann, Opts) -> - case {read_file(File, Opts), read_file(File, stdlib_options())} of - {{ok, Bin}, {ok, _}} -> + %% Temporarily extend include paths with the directory of the current file + Opts1 = include_current_file_dir(Opts, Ann), + case {read_file(File, Opts1), read_file(File, stdlib_options())} of + {{ok, Dir, Bin}, {ok, _}} -> case filename:basename(File) == File of true -> { error , fail( ann_pos(Ann) , "Illegal redefinition of standard library " ++ binary_to_list(File))}; %% If a path is provided then the stdlib takes lower priority - false -> {ok, binary_to_list(Bin)} + false -> {ok, Dir, binary_to_list(Bin)} end; - {_, {ok, Bin}} -> - {ok, binary_to_list(Bin)}; - {{ok, Bin}, _} -> - {ok, binary_to_list(Bin)}; + {_, {ok, _, Bin}} -> + {ok, stdlib, binary_to_list(Bin)}; + {{ok, Dir, Bin}, _} -> + {ok, Dir, binary_to_list(Bin)}; {_, _} -> {error, {ann_pos(Ann), include_error, File}} end. +include_current_file_dir(Opts, Ann) -> + case {proplists:get_value(dir, Ann, undefined), + proplists:get_value(include, Opts, undefined)} of + {undefined, _} -> Opts; + {CurrDir, {file_system, Paths}} -> + case lists:member(CurrDir, Paths) of + false -> [{include, {file_system, [CurrDir | Paths]}} | Opts]; + true -> Opts + end; + {_, _} -> Opts + end. + -spec hash_include(string() | binary(), string()) -> include_hash(). hash_include(File, Code) when is_binary(File) -> hash_include(binary_to_list(File), Code); diff --git a/src/aeso_utils.erl b/src/aeso_utils.erl index e86f9ca..dcf9b7d 100644 --- a/src/aeso_utils.erl +++ b/src/aeso_utils.erl @@ -6,10 +6,22 @@ %%%------------------------------------------------------------------- -module(aeso_utils). --export([scc/1]). +-export([scc/1, canonical_dir/1]). -export_type([graph/1]). +%% -- Simplistic canonical directory +%% Note: no attempts to be 100% complete + +canonical_dir(Dir) -> + {ok, Cwd} = file:get_cwd(), + AbsName = filename:absname(Dir), + RelAbsName = filename:join(tl(filename:split(AbsName))), + case filelib:safe_relative_path(RelAbsName, Cwd) of + unsafe -> AbsName; + Simplified -> filename:absname(Simplified, "") + end. + %% -- Topological sort -type graph(Node) :: #{Node => [Node]}. %% List of incoming edges (dependencies). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 26c5d64..b0f2008 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -161,6 +161,7 @@ compilable_contracts() -> "state_handling", "events", "include", + "relative_include", "basic_auth", "basic_auth_tx", "bitcoin_auth", diff --git a/test/contracts/dir1/bar.aes b/test/contracts/dir1/bar.aes new file mode 100644 index 0000000..abc4aed --- /dev/null +++ b/test/contracts/dir1/bar.aes @@ -0,0 +1,4 @@ +include "../dir2/baz.aes" +namespace D = + function g() = E.h() + diff --git a/test/contracts/dir2/baz.aes b/test/contracts/dir2/baz.aes new file mode 100644 index 0000000..e14962f --- /dev/null +++ b/test/contracts/dir2/baz.aes @@ -0,0 +1,3 @@ +namespace E = + function h() = 42 + diff --git a/test/contracts/relative_include.aes b/test/contracts/relative_include.aes new file mode 100644 index 0000000..e64ecb1 --- /dev/null +++ b/test/contracts/relative_include.aes @@ -0,0 +1,3 @@ +include "./dir1/bar.aes" +contract C = + entrypoint f() = D.g() From cdbd430f23aad7a60e38acaf991737d77ebe375b Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 27 Nov 2023 12:12:24 +0300 Subject: [PATCH 16/41] Disable unused type warnings for type used inside of records (#492) * Do not show warning for types used in records * Exclude .vscode in .gitignore * Update CHANGELOG.md --- .gitignore | 1 + CHANGELOG.md | 1 + src/aeso_ast_infer_types.erl | 4 ++-- test/aeso_compiler_tests.erl | 1 + test/contracts/warning_used_record_typedef.aes | 5 +++++ 5 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 test/contracts/warning_used_record_typedef.aes diff --git a/.gitignore b/.gitignore index 095c8b0..b0d35ee 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ current_counterexample.eqc test/contracts/test.aes __pycache__ .docssite/docs/*.md +.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d90868..cc75248 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 include paths are now always relative to the file containing the `include` statement. ### Removed ### Fixed +- Disable unused type warnings for types used inside of records. ## [7.4.0] ### Changed diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ae75468..2af3b62 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -924,14 +924,14 @@ infer(Contracts, Options) -> {Env1, Decls} = infer1(Env, Contracts1, [], Options), when_warning(warn_unused_functions, fun() -> destroy_and_report_unused_functions() end), when_option(warn_error, fun() -> destroy_and_report_warnings_as_type_errors() end), - WarningsUnsorted = lists:map(fun mk_warning/1, ets_tab2list(warnings)), - Warnings = aeso_warnings:sort_warnings(WarningsUnsorted), {Env2, DeclsFolded, DeclsUnfolded} = case proplists:get_value(dont_unfold, Options, false) of true -> {Env1, Decls, Decls}; false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), {E, Decls, unfold_record_types(E, Decls)} end, + WarningsUnsorted = lists:map(fun mk_warning/1, ets_tab2list(warnings)), + Warnings = aeso_warnings:sort_warnings(WarningsUnsorted), case proplists:get_value(return_env, Options, false) of false -> {DeclsFolded, DeclsUnfolded, Warnings}; true -> {Env2, DeclsFolded, DeclsUnfolded, Warnings} diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index b0f2008..483531f 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -70,6 +70,7 @@ simple_compile_test_() -> fun() -> #{ warnings := Warnings } = compile("warnings", [warn_all]), #{ warnings := [] } = compile("warning_unused_include_no_include", [warn_all]), + #{ warnings := [] } = compile("warning_used_record_typedef", [warn_all]), check_warnings(warnings(), Warnings) end} ] ++ []. diff --git a/test/contracts/warning_used_record_typedef.aes b/test/contracts/warning_used_record_typedef.aes new file mode 100644 index 0000000..537b19a --- /dev/null +++ b/test/contracts/warning_used_record_typedef.aes @@ -0,0 +1,5 @@ +contract Test = + type option_int = option(int) + record option_point = {x: int, y: option_int} + + entrypoint test_option_record(a: option_point) = a From f75455bb85cdd29c7722d7037d773473904a6779 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 23 Jan 2024 15:55:12 +0100 Subject: [PATCH 17/41] Bump pygments version (#496) --- .github/workflows/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index dcdee61..e3044b9 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -2,4 +2,4 @@ mkdocs==1.4.2 mkdocs-simple-hooks==0.1.5 mkdocs-material==9.0.9 mike==1.1.2 -pygments==2.14.0 \ No newline at end of file +pygments==2.17.2 From dbeb792ca524efec3a62449d3303a4eedf414181 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 23 Jan 2024 16:14:03 +0100 Subject: [PATCH 18/41] Prepare release v4.7.1 (#497) --- CHANGELOG.md | 9 +++++++-- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc75248..683f96d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +### Removed +### Fixed + +## [7.4.1] +### Changed - Improve how includes with relative paths are resolved during parsing/compilation. Relative include paths are now always relative to the file containing the `include` statement. -### Removed ### Fixed - Disable unused type warnings for types used inside of records. @@ -414,7 +418,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.4.0...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.4.1...HEAD +[7.4.1]: https://github.com/aeternity/aesophia/compare/v7.4.0...v7.4.1 [7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0 [7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 [7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1 diff --git a/rebar.config b/rebar.config index e2f0b23..7e4fa55 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "7.4.0"}, +{relx, [{release, {aesophia, "7.4.1"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index 308e080..bb97c16 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.4.0"}, + {vsn, "7.4.1"}, {registered, []}, {applications, [kernel, From 5c3b42aff1a79d0d21beea8ed653bd9ca32c652b Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 25 Jan 2024 09:57:08 +0100 Subject: [PATCH 19/41] Fix Typo and whitespace in sophia_features.md (#500) --- docs/sophia_features.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 3025f82..a696441 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -84,7 +84,7 @@ the return value of the call. ```sophia contract interface VotingType = - entrypoint : vote : string => unit + entrypoint vote : string => unit contract Voter = entrypoint tryVote(v : VotingType, alt : string) = @@ -204,7 +204,7 @@ When a `contract` or a `contract interface` implements another `contract interfa #### Subtyping and variance -Subtyping in Sophia follows common rules that take type variance into account. As described by [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)), +Subtyping in Sophia follows common rules that take type variance into account. As described by [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)), >Variance refers to how subtyping between more complex types relates to subtyping between their components. @@ -224,11 +224,11 @@ A good example of where it matters can be pictured by subtyping of function type ```sophia contract interface Animal = entrypoint age : () => int - + contract Dog : Animal = entrypoint age() = // ... entrypoint woof() = "woof" - + contract Cat : Animal = entrypoint age() = // ... entrypoint meow() = "meow" @@ -628,7 +628,7 @@ arithmetic operations: - remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y` - exponentiation (`x ^ y`) -All operations are *safe* with respect to overflow and underflow. +All operations are *safe* with respect to overflow and underflow. The division and modulo operations throw an arithmetic error if the right-hand operand is zero. From f60f9122ba392ad54267001336d72fed6c50d451 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 29 Oct 2021 13:34:40 +0200 Subject: [PATCH 20/41] [Ceres]: Add bitwise ops, Address.to_bytes and Crypto.poseidon --- CHANGELOG.md | 10 +++ docs/sophia_features.md | 8 +++ docs/sophia_stdlib.md | 104 ++++++----------------------- docs/sophia_syntax.md | 11 ++- priv/stdlib/Bitwise.aes | 126 ----------------------------------- src/aeso_ast_infer_types.erl | 17 ++++- src/aeso_ast_to_fcode.erl | 19 +++--- src/aeso_fcode_to_fate.erl | 18 +++++ src/aeso_parser.erl | 11 ++- src/aeso_pretty.erl | 14 ++-- src/aeso_scan.erl | 2 +- src/aeso_syntax.erl | 4 +- test/aeso_compiler_tests.erl | 1 + test/aeso_parser_tests.erl | 2 +- test/aeso_scan_tests.erl | 3 +- test/contracts/ceres.aes | 14 ++++ 16 files changed, 128 insertions(+), 236 deletions(-) delete mode 100644 priv/stdlib/Bitwise.aes create mode 100644 test/contracts/ceres.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 683f96d..daff8e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [CERES 8.0.0] +### Added +- Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`. +- `Int.mulmod` - combined builtin operation for multiplication and modulus. +- `Crypto.poseidon` - a ZK/SNARK-friendly hash function (over the BLS12-381 scalar field). +- `Address.to_bytes` - convert an address to its binary representation (for hashing, etc.). +### Changed +### Removed +- `Bitwise.aes` standard library is removed - the builtin operations are superior. + ## [Unreleased] ### Added ### Changed diff --git a/docs/sophia_features.md b/docs/sophia_features.md index a696441..487bb42 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -632,6 +632,14 @@ All operations are *safe* with respect to overflow and underflow. The division and modulo operations throw an arithmetic error if the right-hand operand is zero. +Sophia arbitrary-sized integers (FATE) also supports the following bitwise operations: +- bitwise and (`x band y`) +- bitwise or (`x bor y`) +- bitwise xor (`x bxor y`) +- bitwise not (`bnot x`) +- arithmetic bitshift left (`x << n`) +- arithmetic bitshift right (`x >> n`) + ## Bit fields Sophia integers do not support bit arithmetic. Instead there is a separate diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 9fcdd7e..7d5c91d 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -381,6 +381,12 @@ Call.gas_price : int The gas price of the current call. +#### mulmod +``` +Int.mulmod : (a : int, b : int, q : int) : int +``` + +Combined multiplication and modulus, returns `(a * b) mod q`. #### fee ``` @@ -469,6 +475,13 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). +#### to_bytes +``` +Address.to_bytes(a : address) : bytes(32) +``` + +The binary representation of the address. + ##### bytecode_hash ``` @@ -506,6 +519,13 @@ charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in `Contract.balance`, however. +#### poseidon +``` +Crypto.poseidon(x1 : int, x2 : int) : int +``` + +Hash two integers (in the scalar field of BLS12-381) to another integer (in the scalar +field of BLS12-281). This is a ZK/SNARK-friendly hash function. The type `'c` must be instantiated with a contract. @@ -926,90 +946,6 @@ It returns `true` iff the oracle query exist and has the expected type. These need to be explicitly included (with `.aes` suffix) -### Bitwise - -Bitwise operations on arbitrary precision integers. - -#### bsr -``` -Bitwise.bsr(n : int, x : int) : int -``` - -Logical bit shift `x` right `n` positions. - - -#### bsl -``` -Bitwise.bsl(n : int, x : int) : int -``` - -Logical bit shift `x` left `n` positions. - - -#### bsli -``` -Bitwise.bsli(n : int, x : int, lim : int) : int -``` - -Logical bit shift `x` left `n` positions, limit to `lim` bits. - - -#### band -``` -Bitwise.band(x : int, y : int) : int -``` - -Bitwise `and` of `x` and `y`. - - -#### bor -``` -Bitwise.bor(x : int, y : int) : int -``` - -Bitwise `or` of `x` and `y`. - - -#### bxor -``` -Bitwise.bxor(x : int, y : int) : int -``` - -Bitwise `xor` of `x` and `y`. - - -#### bnot -``` -Bitwise.bnot(x : int) : int -``` - -Bitwise `not` of `x`. Defined and implemented as `bnot(x) = bxor(x, -1)`. - - -#### uband -``` -Bitwise.uband(x : int, y : int) : int -``` - -Bitwise `and` of _non-negative_ numbers `x` and `y`. - - -#### ubor -``` -Bitwise.ubor(x : int, y : int) : int -``` - -Bitwise `or` of _non-negative_ `x` and `y`. - - -#### ubxor -``` -Bitwise.ubxor(x : int, y : int) : int -``` - -Bitwise `xor` of _non-negative_ `x` and `y`. - - ### BLS12\_381 #### Types diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index f0df9e6..712c9ce 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -256,8 +256,8 @@ Path ::= Id // Record field BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' - | '|>' -UnOp ::= '-' | '!' + | 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>' +UnOp ::= '-' | '!' | 'bnot' ``` ## Operators types @@ -266,6 +266,7 @@ UnOp ::= '-' | '!' | --- | --- | `-` `+` `*` `/` `mod` `^` | arithmetic operators | `!` `&&` `||` | logical operators +| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators | `==` `!=` `<` `>` `=<` `>=` | comparison operators | `::` `++` | list operators | `|>` | functional operators @@ -276,13 +277,17 @@ In order of highest to lowest precedence. | Operators | Associativity | --- | --- -| `!` | right +| `!` `bnot`| right | `^` | left | `*` `/` `mod` | left | `-` (unary) | right | `+` `-` | left +| `<<` `>>` | left | `::` `++` | right | `<` `>` `=<` `>=` `==` `!=` | none +| `band` | left +| `bxor` | left +| `bor` | left | `&&` | right | `||` | right | `|>` | left diff --git a/priv/stdlib/Bitwise.aes b/priv/stdlib/Bitwise.aes deleted file mode 100644 index cc273f0..0000000 --- a/priv/stdlib/Bitwise.aes +++ /dev/null @@ -1,126 +0,0 @@ -@compiler >= 4.3 - -namespace Bitwise = - - // bit shift 'x' right 'n' postions - function bsr(n : int, x : int) : int = - let step = 2^n - let res = x / step - if (x >= 0 || x mod step == 0) - res - else - res - 1 - - // bit shift 'x' left 'n' positions - function bsl(n : int, x : int) : int = - x * 2^n - - // bit shift 'x' left 'n' positions, limit at 'lim' bits - function bsli(n : int, x : int, lim : int) : int = - (x * 2^n) mod (2^lim) - - // bitwise 'and' for arbitrary precision integers - function band(a : int, b : int) : int = - if (a >= 0 && b >= 0) - uband_(a, b) - elif (b >= 0) - ubnand_(b, -1 - a) - elif (a >= 0) - ubnand_(a, -1 - b) - else - -1 - ubor_(-1 - a, -1 - b) - - // bitwise 'or' for arbitrary precision integers - function - bor : (int, int) => int - bor(0, b) = b - bor(a, 0) = a - bor(a : int, b : int) : int = - if (a >= 0 && b >= 0) - ubor_(a, b) - elif (b >= 0) - -1 - ubnand_(-1 - a, b) - elif (a >= 0) - -1 - ubnand_(-1 - b, a) - else - -1 - uband_(-1 - a, -1 - b) - - // bitwise 'xor' for arbitrary precision integers - function - bxor : (int, int) => int - bxor(0, b) = b - bxor(a, 0) = a - bxor(a, b) = - if (a >= 0 && b >= 0) - ubxor_(a, b) - elif (b >= 0) - -1 - ubxor_(-1 - a, b) - elif (a >= 0) - -1 - ubxor_(a, -1 - b) - else - ubxor_(-1 - a, -1 - b) - - // bitwise 'not' for arbitrary precision integers - function bnot(a : int) = bxor(a, -1) - - // Bitwise 'and' for non-negative integers - function uband(a : int, b : int) : int = - require(a >= 0 && b >= 0, "uband is only defined for non-negative integers") - switch((a, b)) - (0, _) => 0 - (_, 0) => 0 - _ => uband__(a, b, 1, 0) - - private function uband_(a, b) = uband__(a, b, 1, 0) - - private function - uband__(0, b, val, acc) = acc - uband__(a, 0, val, acc) = acc - uband__(a, b, val, acc) = - switch (a mod 2 + b mod 2) - 2 => uband__(a / 2, b / 2, val * 2, acc + val) - _ => uband__(a / 2, b / 2, val * 2, acc) - - // Bitwise 'or' for non-negative integers - function ubor(a, b) = - require(a >= 0 && b >= 0, "ubor is only defined for non-negative integers") - switch((a, b)) - (0, _) => b - (_, 0) => a - _ => ubor__(a, b, 1, 0) - - private function ubor_(a, b) = ubor__(a, b, 1, 0) - - private function - ubor__(0, 0, val, acc) = acc - ubor__(a, b, val, acc) = - switch (a mod 2 + b mod 2) - 0 => ubor__(a / 2, b / 2, val * 2, acc) - _ => ubor__(a / 2, b / 2, val * 2, acc + val) - - //Bitwise 'xor' for non-negative integers - function - ubxor : (int, int) => int - ubxor(0, b) = b - ubxor(a, 0) = a - ubxor(a, b) = - require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers") - ubxor__(a, b, 1, 0) - - private function ubxor_(a, b) = ubxor__(a, b, 1, 0) - - private function - ubxor__(0, 0, val, acc) = acc - ubxor__(a, b, val, acc) = - switch(a mod 2 + b mod 2) - 1 => ubxor__(a / 2, b / 2, val * 2, acc + val) - _ => ubxor__(a / 2, b / 2, val * 2, acc) - - private function ubnand_(a, b) = ubnand__(a, b, 1, 0) - - private function - ubnand__(0, b, val, acc) = acc - ubnand__(a, b, val, acc) = - switch((a mod 2, b mod 2)) - (1, 0) => ubnand__(a / 2, b / 2, val * 2, acc + val) - _ => ubnand__(a / 2, b / 2, val * 2, acc) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 2af3b62..5bf8e4f 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -764,7 +764,8 @@ global_env() -> {"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))}, {"sha3", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)}, - {"blake2b", Fun1(A, Hash)}]) }, + {"blake2b", Fun1(A, Hash)}, + {"poseidon", Fun([Int, Int], Int)}]) }, %% Fancy BLS12-381 crypto operations MCL_BLS12_381_Scope = #scope @@ -849,14 +850,16 @@ global_env() -> ]) }, %% Conversion - IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, + IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, + {"mulmod", Fun([Int, Int, Int], Int)}]) }, + AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, + {"to_bytes", Fun1(Address, Bytes(32))}, {"to_contract", FunC(address_to_contract, [Address], A)}, {"is_oracle", Fun1(Address, Bool)}, {"is_contract", Fun1(Address, Bool)}, {"is_payable", Fun1(Address, Bool)}]) }, - #env{ scopes = #{ [] => TopScope , ["Chain"] => ChainScope @@ -2396,6 +2399,11 @@ infer_infix({IntOp, As}) IntOp == '^'; IntOp == 'mod' -> Int = {id, As, "int"}, {fun_t, As, [], [Int, Int], Int}; +infer_infix({BitOp, As}) + when BitOp == 'band'; BitOp == 'bor'; BitOp == 'bxor'; + BitOp == '<<'; BitOp == '>>' -> + Int = {id, As, "int"}, + {fun_t, As, [], [Int, Int], Int}; infer_infix({RelOp, As}) when RelOp == '=='; RelOp == '!='; RelOp == '<'; RelOp == '>'; @@ -2423,6 +2431,9 @@ infer_infix({'|>', As}) -> infer_prefix({'!',As}) -> Bool = {id, As, "bool"}, {fun_t, As, [], [Bool], Bool}; +infer_prefix({BitOp,As}) when BitOp =:= 'bnot' -> + Int = {id, As, "int"}, + {fun_t, As, [], [Int], Int}; infer_prefix({IntOp,As}) when IntOp =:= '-' -> Int = {id, As, "int"}, {fun_t, As, [], [Int], Int}. diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 38da87e..583ce11 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -32,12 +32,13 @@ -type op() :: '+' | '-' | '*' | '/' | mod | '^' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '!' | + 'band' | 'bor' | 'bxor' | 'bnot' | '<<' | '>>' | map_get | map_get_d | map_set | map_from_list | map_to_list | map_delete | map_member | map_size | string_length | string_concat | bits_set | bits_clear | bits_test | bits_sum | bits_intersection | bits_union | bits_difference | contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 | - crypto_sha3 | crypto_sha256 | crypto_blake2b | + crypto_sha3 | crypto_sha256 | crypto_blake2b | crypto_poseidon | crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 | mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid | mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul | @@ -278,7 +279,7 @@ builtins() -> {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3}, {"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2}, - {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, + {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"poseidon", 2}]}, {["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2}, {"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2}, {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, @@ -291,8 +292,9 @@ builtins() -> {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, - {["Int"], [{"to_str", 1}]}, - {["Address"], [{"to_str", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} + {["Int"], [{"to_str", 1}, {"mulmod", 2}]}, + {["Address"], [{"to_str", 1}, {"to_bytes", 1}, {"to_contract", 1}, + {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} || {NS, Funs} <- Scopes, @@ -748,8 +750,9 @@ expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) -> expr_to_fcode(Env, _Type, {app, Ann, {Op, _}, [A]}) when is_atom(Op) -> FAnn = to_fann(Ann), case Op of - '-' -> {op, FAnn, '-', [{lit, FAnn, {int, 0}}, expr_to_fcode(Env, A)]}; - '!' -> {op, FAnn, '!', [expr_to_fcode(Env, A)]} + '-' -> {op, FAnn, '-', [{lit, FAnn, {int, 0}}, expr_to_fcode(Env, A)]}; + 'bnot' -> {op, FAnn, 'bnot', [expr_to_fcode(Env, A)]}; + '!' -> {op, FAnn, '!', [expr_to_fcode(Env, A)]} end; %% Function calls @@ -1170,9 +1173,9 @@ op_builtins() -> stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, - bits_difference, int_to_str, address_to_str, crypto_verify_sig, + bits_difference, int_to_str, int_mulmod, address_to_str, address_to_bytes, crypto_verify_sig, address_to_contract, - crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, + crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, crypto_poseidon, crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 96ffe69..95f80fe 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -667,6 +667,12 @@ op_to_scode('>=') -> aeb_fate_ops:egt(?a, ?a, ?a); op_to_scode('==') -> aeb_fate_ops:eq(?a, ?a, ?a); op_to_scode('!=') -> aeb_fate_ops:neq(?a, ?a, ?a); op_to_scode('!') -> aeb_fate_ops:not_op(?a, ?a); +op_to_scode('bnot') -> aeb_fate_ops:bin_not(?a, ?a); +op_to_scode('band') -> aeb_fate_ops:bin_and(?a, ?a, ?a); +op_to_scode('bor') -> aeb_fate_ops:bin_or(?a, ?a, ?a); +op_to_scode('bxor') -> aeb_fate_ops:bin_xor(?a, ?a, ?a); +op_to_scode('<<') -> aeb_fate_ops:bin_sl(?a, ?a, ?a); +op_to_scode('>>') -> aeb_fate_ops:bin_sr(?a, ?a, ?a); op_to_scode(map_get) -> aeb_fate_ops:map_lookup(?a, ?a, ?a); op_to_scode(map_get_d) -> aeb_fate_ops:map_lookup(?a, ?a, ?a, ?a); op_to_scode(map_set) -> aeb_fate_ops:map_update(?a, ?a, ?a, ?a); @@ -691,7 +697,9 @@ op_to_scode(bits_intersection) -> aeb_fate_ops:bits_and(?a, ?a, ?a); op_to_scode(bits_union) -> aeb_fate_ops:bits_or(?a, ?a, ?a); op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); +op_to_scode(address_to_bytes) -> aeb_fate_ops:addr_to_bytes(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); +op_to_scode(int_mulmod) -> aeb_fate_ops:mulmod(?a, ?a, ?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a); op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a); @@ -701,6 +709,7 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a, op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); +op_to_scode(crypto_poseidon) -> aeb_fate_ops:poseidon(?a, ?a, ?a); op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); @@ -973,6 +982,13 @@ attributes(I) -> {'DIV', A, B, C} -> Pure(A, [B, C]); {'MOD', A, B, C} -> Pure(A, [B, C]); {'POW', A, B, C} -> Pure(A, [B, C]); + {'MULMOD', A, B, C, D} -> Pure(A, [B, C, D]); + {'BAND', A, B, C} -> Pure(A, [B, C]); + {'BOR', A, B, C} -> Pure(A, [B, C]); + {'BXOR', A, B, C} -> Pure(A, [B, C]); + {'BNOT', A, B} -> Pure(A, [B]); + {'BSL', A, B, C} -> Pure(A, [B, C]); + {'BSR', A, B, C} -> Pure(A, [B, C]); {'LT', A, B, C} -> Pure(A, [B, C]); {'GT', A, B, C} -> Pure(A, [B, C]); {'EQ', A, B, C} -> Pure(A, [B, C]); @@ -1025,12 +1041,14 @@ attributes(I) -> {'SHA3', A, B} -> Pure(A, [B]); {'SHA256', A, B} -> Pure(A, [B]); {'BLAKE2B', A, B} -> Pure(A, [B]); + {'POSEIDON', A, B, C} -> Pure(A, [B, C]); {'VERIFY_SIG', A, B, C, D} -> Pure(A, [B, C, D]); {'VERIFY_SIG_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]); + {'ADDRESS_TO_BYTES', A, B} -> Pure(A, [B]); {'AUTH_TX_HASH', A} -> Pure(A, []); {'AUTH_TX', A} -> Pure(A, []); {'BYTES_TO_INT', A, B} -> Pure(A, [B]); diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 3c03bf0..12826ae 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -335,14 +335,19 @@ expr100() -> expr150() -> infixl(expr200(), binop('|>')). expr200() -> infixr(expr300(), binop('||')). -expr300() -> infixr(expr400(), binop('&&')). +expr300() -> infixr(expr325(), binop('&&')). +expr325() -> infixl(expr350(), binop('bor')). +expr350() -> infixl(expr375(), binop('bxor')). +expr375() -> infixl(expr400(), binop('band')). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). -expr500() -> infixr(expr600(), binop(['::', '++'])). +expr500() -> infixr(expr550(), binop(['::', '++'])). +expr550() -> infixl(expr600(), binop(['<<', '>>'])). expr600() -> infixl(expr650(), binop(['+', '-'])). expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)). expr700() -> infixl(expr750(), binop(['*', '/', mod])). expr750() -> infixl(expr800(), binop(['^'])). -expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)). +expr800() -> ?RULE(many(token('!')), expr850(), prefixes(_1, _2)). +expr850() -> ?RULE(many(token('bnot')), expr900(), prefixes(_1, _2)). expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)). exprAtom() -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index afce62c..3ef3300 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -436,15 +436,20 @@ bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('@') -> { 0, 0, 0}; %% Only in error messages bin_prec('|>') -> {150, 150, 200}; bin_prec('||') -> {200, 300, 200}; -bin_prec('&&') -> {300, 400, 300}; +bin_prec('&&') -> {300, 325, 300}; +bin_prec('bor') -> {325, 350, 325}; +bin_prec('bxor') -> {350, 375, 350}; +bin_prec('band') -> {375, 400, 375}; bin_prec('<') -> {400, 500, 500}; bin_prec('>') -> {400, 500, 500}; bin_prec('=<') -> {400, 500, 500}; bin_prec('>=') -> {400, 500, 500}; bin_prec('==') -> {400, 500, 500}; bin_prec('!=') -> {400, 500, 500}; -bin_prec('++') -> {500, 600, 500}; -bin_prec('::') -> {500, 600, 500}; +bin_prec('++') -> {500, 550, 500}; +bin_prec('::') -> {500, 550, 500}; +bin_prec('<<') -> {550, 600, 550}; +bin_prec('>>') -> {550, 600, 550}; bin_prec('+') -> {600, 600, 650}; bin_prec('-') -> {600, 600, 650}; bin_prec('*') -> {700, 700, 750}; @@ -454,7 +459,8 @@ bin_prec('^') -> {750, 750, 800}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. un_prec('-') -> {650, 650}; -un_prec('!') -> {800, 800}. +un_prec('!') -> {800, 800}; +un_prec('bnot') -> {850, 850}. equals(Ann, A, B) -> {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index 4587efa..a6746bc 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -45,7 +45,7 @@ lexer() -> Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace", - "interface", "main", "using", "as", "for", "hiding" + "interface", "main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot" ], KW = string:join(Keywords, "|"), diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 0ff9f5e..1e8ee2d 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -107,8 +107,8 @@ -type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' - | '||' | '&&' | '..' | '|>'. --type un_op() :: '-' | '!'. + | '||' | '&&' | '..' | 'band' | 'bor' | 'bxor' | '>>' | '<<' | '|>'. +-type un_op() :: '-' | '!' | 'bnot'. -type expr() :: {lam, ann(), [arg()], expr()} diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 483531f..0d87a92 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -225,6 +225,7 @@ compilable_contracts() -> "unapplied_named_arg_builtin", "resolve_field_constraint_by_arity", "toplevel_constants", + "ceres", "test" % Custom general-purpose test file. Keep it last on the list. ]. diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index e3b7598..2324fc4 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -53,7 +53,7 @@ simple_contracts_test_() -> %% associativity [ RightAssoc(Op) || Op <- ["||", "&&", "::", "++"] ], [ NonAssoc(Op) || Op <- ["==", "!=", "<", ">", "=<", ">="] ], - [ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod"] ], + [ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod", "band", "bor", "bxor", "<<", ">>"] ], %% precedence [ Stronger(Op2, Op1) || [T1 , T2 | _] <- tails(Tiers), Op1 <- T1, Op2 <- T2 ], diff --git a/test/aeso_scan_tests.erl b/test/aeso_scan_tests.erl index 685d3ac..92912e0 100644 --- a/test/aeso_scan_tests.erl +++ b/test/aeso_scan_tests.erl @@ -39,7 +39,8 @@ all_tokens() -> %% Symbols lists:map(Lit, [',', '.', ';', '|', ':', '(', ')', '[', ']', '{', '}']) ++ %% Operators - lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++ + lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, + ':', '::', '->', '=>', '||', '&&', '!', 'band', 'bor', 'bxor', 'bnot' ,'<<', '>>']) ++ %% Keywords lists:map(Lit, [contract, type, 'let', switch]) ++ %% Comment token (not an actual token), just for tests diff --git a/test/contracts/ceres.aes b/test/contracts/ceres.aes new file mode 100644 index 0000000..832334f --- /dev/null +++ b/test/contracts/ceres.aes @@ -0,0 +1,14 @@ +contract C = + entrypoint test() = + let a : int = 23 + let b : int = 52 + let c = a bor b + let d = c bxor b + let e = d band b + let f = bnot a + let g = f << 2 + let h = g >> 2 + let i = Int.mulmod(a, b, h) + let j = Crypto.poseidon(i, a) + let k : bytes(32) = Address.to_bytes(Call.origin) + (a bor b band c bxor a << bnot b >> a, k) From dcea538e119ca43febee3d21d6284fa2f916cf9c Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 16 Nov 2022 21:31:44 +0100 Subject: [PATCH 21/41] [Ceres]: Introduce AENSv2 to add raw data pointers (#426) Remove unused variable in AENSCompat --- CHANGELOG.md | 8 ++ docs/sophia_stdlib.md | 81 ++++++++++++++++----- priv/stdlib/AENSCompat.aes | 17 +++++ src/aeso_ast_infer_types.erl | 36 ++++++--- src/aeso_ast_to_fcode.erl | 21 ++++-- src/aeso_vm_decode.erl | 17 ++++- test/aeso_compiler_tests.erl | 6 +- test/contracts/aens.aes | 34 ++++----- test/contracts/aens_update.aes | 35 ++++++--- test/contracts/bad_aens_resolve.aes | 2 +- test/contracts/bad_aens_resolve_using.aes | 4 +- test/contracts/polymorphic_aens_resolve.aes | 2 +- test/contracts/unapplied_builtins.aes | 10 +-- 13 files changed, 200 insertions(+), 73 deletions(-) create mode 100644 priv/stdlib/AENSCompat.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index daff8e2..2f71178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Int.mulmod` - combined builtin operation for multiplication and modulus. - `Crypto.poseidon` - a ZK/SNARK-friendly hash function (over the BLS12-381 scalar field). - `Address.to_bytes` - convert an address to its binary representation (for hashing, etc.). +- Raw data pointers added to AENS. In short we have introduced a new namespace + `AENSv2`; they contain types similar to the old `AENS`; `AENS.name` and + `AENS.pointee`, where the latter now has a constructor `DataPt(string)`. All + AENS actions have been moved to `AENSv2`, and `AENSv2.lookup` and + `AENSv2.update` consume and produce the new types. The old `AENS` namespace + only contains the old datatypes, that can be used to interface existing + contracts. Standard library `AENSCompat` is added to convert between old and + new pointers. ### Changed ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 7d5c91d..209c132 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -14,6 +14,7 @@ The out-of-the-box namespaces are: - [Address](#address) - [AENS](#aens) +- [AENSv2](#aensv2) - [Auth](#auth) - [Bits](#bits) - [Bytes](#bytes) @@ -31,6 +32,7 @@ The following ones need to be included as regular files with `.aes` suffix, for include "List.aes" ``` +- [AENSCompat](#aenscompat) - [Bitwise](#bitwise) - [BLS12_381](#bls12_381) - [Func](#func) @@ -90,13 +92,10 @@ Cast address to contract type C (where `C` is a contract) ### AENS -The following functionality is available for interacting with the æternity -naming system (AENS). -If `owner` is equal to `Contract.address` the signature `signature` is -ignored, and can be left out since it is a named argument. Otherwise we need -a signature to prove that we are allowed to do AENS operations on behalf of -`owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature), -i.e. the signature material should be prefixed by the network id. +The old AENS namespace, kept in the compiler to be able to interact with +contracts from before Ceres, compiled using aesophia compiler version 7.x and +earlier. Used in [AENSCompat](#aenscompat) when converting between old and new +pointers. #### Types @@ -113,12 +112,41 @@ datatype pointee = AccountPt(address) | OraclePt(address) | ContractPt(address) | ChannelPt(address) ``` +### AENSv2 + +Note: introduced in v8.0 + +The following functionality is available for interacting with the æternity +naming system (AENS). If `owner` is equal to `Contract.address` the signature +`signature` is ignored, and can be left out since it is a named argument. +Otherwise we need a signature to prove that we are allowed to do AENS +operations on behalf of `owner`. The [signature is tied to a network +id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature), +i.e. the signature material should be prefixed by the network id. + +#### Types + +##### name +``` +datatype name = Name(address, Chain.ttl, map(string, AENSv2.pointee)) +``` + + +##### pointee + +``` +datatype pointee = AccountPt(address) | OraclePt(address) + | ContractPt(address) | ChannelPt(address) | DataPt(string) +``` + +Note: on-chain there is a maximum length enforced for `DataPt`, it is 1024 bytes. +Sophia itself does _not_ enforce this. #### Functions ##### resolve ``` -AENS.resolve(name : string, key : string) : option('a) +AENSv2.resolve(name : string, key : string) : option('a) ``` Name resolution. Here `name` should be a registered name and `key` one of the attributes @@ -129,21 +157,22 @@ type checked against this type at run time. ##### lookup ``` -AENS.lookup(name : string) : option(AENS.name) +AENSv2.lookup(name : string) : option(AENSv2.name) ``` -If `name` is an active name `AENS.lookup` returns a name object. +If `name` is an active name `AENSv2.lookup` returns a name object. The three arguments to `Name` are `owner`, `expiry` and a map of the `pointees` for the name. Note: the expiry of the name is always a fixed TTL. For example: ``` -let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain") +let Some(AENSv2.Name(owner, FixedTTL(expiry), ptrs)) = AENSv2.lookup("example.chain") ``` +Note: Changed to produce `AENSv2.name` in v8.0 (Ceres protocol upgrade). ##### preclaim ``` -AENS.preclaim(owner : address, commitment_hash : hash, ) : unit +AENSv2.preclaim(owner : address, commitment_hash : hash, ) : unit ``` The [signature](./sophia_features.md#delegation-signature) should be over @@ -152,7 +181,7 @@ The [signature](./sophia_features.md#delegation-signature) should be over ##### claim ``` -AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit +AENSv2.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` The [signature](./sophia_features.md#delegation-signature) should be over @@ -163,7 +192,7 @@ using the private key of the `owner` account for signing. ##### transfer ``` -AENS.transfer(owner : address, new_owner : address, name : string, ) : unit +AENSv2.transfer(owner : address, new_owner : address, name : string, ) : unit ``` Transfers name to the new owner. @@ -176,7 +205,7 @@ using the private key of the `owner` account for signing. ##### revoke ``` -AENS.revoke(owner : address, name : string, ) : unit +AENSv2.revoke(owner : address, name : string, ) : unit ``` Revokes the name to extend the ownership time. @@ -189,14 +218,15 @@ using the private key of the `owner` account for signing. ##### update ``` -AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), - new_ptrs : option(map(string, AENS.pointee)), ) : unit +AENSv2.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), + new_ptrs : option(map(string, AENSv2.pointee)), ) : unit ``` Updates the name. If the optional parameters are set to `None` that parameter will not be updated, for example if `None` is passed as `expiry` the expiry block of the name is not changed. +Note: Changed to consume `AENSv2.pointee` in v8.0 (Ceres protocol upgrade). ### Auth @@ -946,6 +976,23 @@ It returns `true` iff the oracle query exist and has the expected type. These need to be explicitly included (with `.aes` suffix) +### AENSCompat + +#### pointee\_to\_V2 +``` +AENSCompat.pointee_to_V2(p : AENS.pointee) : AENSv2.pointee +``` + +Translate old pointee format to new, this is always possible. + +#### pointee\_from\_V2 +``` +AENSCompat.pointee_from_V2(p2 : AENSv2.pointee) : option(AENS.pointee) +``` + +Translate new pointee format to old, `DataPt` can't be translated, so `None` is returned in this case. + + ### BLS12\_381 #### Types diff --git a/priv/stdlib/AENSCompat.aes b/priv/stdlib/AENSCompat.aes new file mode 100644 index 0000000..baaa05a --- /dev/null +++ b/priv/stdlib/AENSCompat.aes @@ -0,0 +1,17 @@ +namespace AENSCompat = + // Translate old format to new format - always possible + function pointee_to_V2(p : AENS.pointee) : AENSv2.pointee = + switch(p) + AENS.AccountPt(a) => AENSv2.AccountPt(a) + AENS.OraclePt(a) => AENSv2.OraclePt(a) + AENS.ContractPt(a) => AENSv2.ContractPt(a) + AENS.ChannelPt(a) => AENSv2.ChannelPt(a) + + // Translate new format to old format - option type! + function pointee_from_V2(p2 : AENSv2.pointee) : option(AENS.pointee) = + switch(p2) + AENSv2.AccountPt(a) => Some(AENS.AccountPt(a)) + AENSv2.OraclePt(a) => Some(AENS.OraclePt(a)) + AENSv2.ContractPt(a) => Some(AENS.ContractPt(a)) + AENSv2.ChannelPt(a) => Some(AENS.ChannelPt(a)) + AENSv2.DataPt(_) => None diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 5bf8e4f..431c093 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -593,6 +593,8 @@ global_env() -> TTL = {qid, Ann, ["Chain", "ttl"]}, Pointee = {qid, Ann, ["AENS", "pointee"]}, AENSName = {qid, Ann, ["AENS", "name"]}, + PointeeV2 = {qid, Ann, ["AENSv2", "pointee"]}, + AENSNameV2 = {qid, Ann, ["AENSv2", "name"]}, Fr = {qid, Ann, ["MCL_BLS12_381", "fr"]}, Fp = {qid, Ann, ["MCL_BLS12_381", "fp"]}, Fp2 = {tuple_t, Ann, [Fp, Fp]}, @@ -728,14 +730,7 @@ global_env() -> AENSScope = #scope { funs = MkDefs( - [{"resolve", Fun([String, String], option_t(Ann, A))}, - {"preclaim", SignFun([Address, Hash], Unit)}, - {"claim", SignFun([Address, String, Int, Int], Unit)}, - {"transfer", SignFun([Address, Address, String], Unit)}, - {"revoke", SignFun([Address, String], Unit)}, - {"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, Pointee))], Unit)}, - {"lookup", Fun([String], option_t(Ann, AENSName))}, - %% AENS pointee constructors + [%% AENS pointee constructors {"AccountPt", Fun1(Address, Pointee)}, {"OraclePt", Fun1(Address, Pointee)}, {"ContractPt", Fun1(Address, Pointee)}, @@ -745,6 +740,26 @@ global_env() -> ]) , types = MkDefs([{"pointee", 0}, {"name", 0}]) }, + AENSv2Scope = #scope + { funs = MkDefs( + [{"resolve", Fun([String, String], option_t(Ann, A))}, + {"preclaim", SignFun([Address, Hash], Unit)}, + {"claim", SignFun([Address, String, Int, Int], Unit)}, + {"transfer", SignFun([Address, Address, String], Unit)}, + {"revoke", SignFun([Address, String], Unit)}, + {"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, PointeeV2))], Unit)}, + {"lookup", Fun([String], option_t(Ann, AENSNameV2))}, + %% AENS pointee constructors v2 + {"AccountPt", Fun1(Address, PointeeV2)}, + {"OraclePt", Fun1(Address, PointeeV2)}, + {"ContractPt", Fun1(Address, PointeeV2)}, + {"ChannelPt", Fun1(Address, PointeeV2)}, + {"DataPt", Fun1(String, PointeeV2)}, + %% Name object constructor v2 + {"Name", Fun([Address, TTL, Map(String, PointeeV2)], AENSNameV2)} + ]) + , types = MkDefs([{"pointee", 0}, {"name", 0}]) }, + MapScope = #scope { funs = MkDefs( [{"from_list", Fun1(List(Pair(K, V)), Map(K, V))}, @@ -867,6 +882,7 @@ global_env() -> , ["Call"] => CallScope , ["Oracle"] => OracleScope , ["AENS"] => AENSScope + , ["AENSv2"] => AENSv2Scope , ["Map"] => MapScope , ["Auth"] => AuthScope , ["Crypto"] => CryptoScope @@ -2019,7 +2035,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end), [ add_constraint({aens_resolve_type, GeneralResultType}) - || element(3, FunName) =:= ["AENS", "resolve"] ], + || element(3, FunName) =:= ["AENSv2", "resolve"] ], [ add_constraint({oracle_type, Ann, OType}) || OType <- [get_oracle_type(FunName, ArgTypes, GeneralResultType)], OType =/= false ], @@ -3928,7 +3944,7 @@ mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) -> [ThingS, Name, Bad]), mk_t_err(pos(Ann), Msg); mk_error({invalid_aens_resolve_type, Ann, T}) -> - Msg = io_lib:format("Invalid return type of `AENS.resolve`:\n" + Msg = io_lib:format("Invalid return type of `AENSv2.resolve`:\n" "~s`\n" "It must be a `string` or a pubkey type (`address`, `oracle`, etc)", [pp_type(" `", T)]), diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 583ce11..bd5fc94 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -228,6 +228,12 @@ init_env(Options) -> ["AENS", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1] }, ["AENS", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1] }, ["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] }, + ["AENSv2", "AccountPt"] => #con_tag{ tag = 0, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "OraclePt"] => #con_tag{ tag = 1, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "DataPt"] => #con_tag{ tag = 4, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "Name"] => #con_tag{ tag = 0, arities = [3] }, ["Chain", "GAMetaTx"] => #con_tag{ tag = 0, arities = [2] }, ["Chain", "PayingForTx"] => #con_tag{ tag = 0, arities = [2] }, ["Chain", "SpendTx"] => #con_tag{ tag = 0, arities = ChainTxArities }, @@ -260,8 +266,11 @@ init_env(Options) -> -spec builtins() -> builtins(). builtins() -> - MkName = fun(NS, Fun) -> - list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_"))) + MkName = fun + (["AENSv2"], Fun) -> + list_to_atom(string:to_lower("AENS_" ++ Fun)); + (NS, Fun) -> + list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_"))) end, Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]}, {["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none}, @@ -273,7 +282,7 @@ builtins() -> {["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2}, {"respond", 4}, {"extend", 3}, {"get_answer", 2}, {"check", 1}, {"check_query", 2}]}, - {["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4}, + {["AENSv2"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4}, {"revoke", 3}, {"update", 6}, {"lookup", 1}]}, {["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2}, {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, @@ -330,11 +339,13 @@ init_type_env() -> ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}), ["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}), ["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}), + ["AENSv2", "pointee"] => ?type({variant, [[address], [address], [address], [address], [string]]}), + ["AENSv2", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address], [string]]}}]]}), ["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}), ["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}), ["Chain", "base_tx"] => ?type(BaseTx), - ["MCL_BLS12_381", "fr"] => ?type({bytes, 32}), - ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) + ["MCL_BLS12_381", "fr"] => ?type({bytes, 32}), + ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) }. -spec is_no_code(env()) -> boolean(). diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index dc5d18b..e1941b8 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -88,7 +88,7 @@ from_fate_builtin(QType, Val) -> {["AENS", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} -> App(["AENS","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL), - Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]); + Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]); {["AENS", "pointee"], {variant, [1, 1, 1, 1], 0, {Addr}}} -> App(["AENS","AccountPt"], [Chk(Adr, Addr)]); @@ -99,6 +99,21 @@ from_fate_builtin(QType, Val) -> {["AENS", "pointee"], {variant, [1, 1, 1, 1], 3, {Addr}}} -> App(["AENS","ChannelPt"], [Chk(Adr, Addr)]); + {["AENSv2", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} -> + App(["AENSv2","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL), + Chk(Map(Str, Qid(["AENSv2", "pointee"])), Ptrs)]); + + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 0, {Val}}} -> + App(["AENSv2","AccountPt"], [Chk(Adr, Val)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 1, {Val}}} -> + App(["AENSv2","OraclePt"], [Chk(Adr, Val)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 2, {Val}}} -> + App(["AENSv2","ContractPt"], [Chk(Adr, Val)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 3, {Val}}} -> + App(["AENSv2","ChannelPt"], [Chk(Adr, Val)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 4, {Val}}} -> + App(["AENSv2","DataPt"], [Chk(Str, Val)]); + {["Chain", "ga_meta_tx"], {variant, [2], 0, {Addr, X}}} -> App(["Chain","GAMetaTx"], [Chk(Adr, Addr), Chk(Int, X)]); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 0d87a92..154712a 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1107,19 +1107,19 @@ failing_contracts() -> ]) , ?TYPE_ERROR(polymorphic_aens_resolve, [<> ]) , ?TYPE_ERROR(bad_aens_resolve, [<> ]) , ?TYPE_ERROR(bad_aens_resolve_using, [<> ]) diff --git a/test/contracts/aens.aes b/test/contracts/aens.aes index 698e4c5..ac59b4d 100644 --- a/test/contracts/aens.aes +++ b/test/contracts/aens.aes @@ -6,77 +6,77 @@ main contract AENSTest = // Name resolution stateful entrypoint resolve_word(name : string, key : string) : option(address) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_string(name : string, key : string) : option(string) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_contract(name : string, key : string) : option(C) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) // Transactions stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address) chash : hash) : unit = // Commitment hash - AENS.preclaim(addr, chash) + AENSv2.preclaim(addr, chash) stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address) chash : hash, // Commitment hash sign : signature) : unit = // Signed by addr (if not Contract.address) - AENS.preclaim(addr, chash, signature = sign) + AENSv2.preclaim(addr, chash, signature = sign) stateful entrypoint claim(addr : address, name : string, salt : int, name_fee : int) : unit = - AENS.claim(addr, name, salt, name_fee) + AENSv2.claim(addr, name, salt, name_fee) stateful entrypoint signedClaim(addr : address, name : string, salt : int, name_fee : int, sign : signature) : unit = - AENS.claim(addr, name, salt, name_fee, signature = sign) + AENSv2.claim(addr, name, salt, name_fee, signature = sign) stateful entrypoint update(owner : address, name : string, ttl : option(Chain.ttl), client_ttl : option(int), - pointers : option(map(string, AENS.pointee))) : unit = - AENS.update(owner, name, ttl, client_ttl, pointers) + pointers : option(map(string, AENSv2.pointee))) : unit = + AENSv2.update(owner, name, ttl, client_ttl, pointers) stateful entrypoint signedUpdate(owner : address, name : string, ttl : option(Chain.ttl), client_ttl : option(int), - pointers : option(map(string, AENS.pointee)), + pointers : option(map(string, AENSv2.pointee)), sign : signature) : unit = - AENS.update(owner, name, ttl, client_ttl, pointers, signature = sign) + AENSv2.update(owner, name, ttl, client_ttl, pointers, signature = sign) stateful entrypoint transfer(owner : address, new_owner : address, name : string) : unit = - AENS.transfer(owner, new_owner, name) + AENSv2.transfer(owner, new_owner, name) stateful entrypoint signedTransfer(owner : address, new_owner : address, name : string, sign : signature) : unit = - AENS.transfer(owner, new_owner, name, signature = sign) + AENSv2.transfer(owner, new_owner, name, signature = sign) stateful entrypoint revoke(owner : address, name : string) : unit = - AENS.revoke(owner, name) + AENSv2.revoke(owner, name) stateful entrypoint signedRevoke(owner : address, name : string, sign : signature) : unit = - AENS.revoke(owner, name, signature = sign) + AENSv2.revoke(owner, name, signature = sign) diff --git a/test/contracts/aens_update.aes b/test/contracts/aens_update.aes index 5d65229..3b8fc7c 100644 --- a/test/contracts/aens_update.aes +++ b/test/contracts/aens_update.aes @@ -1,17 +1,30 @@ -contract AENSUpdate = +include "Option.aes" +include "AENSCompat.aes" +contract interface OldAENSContract = + entrypoint set : (string, string, AENS.pointee) => unit + entrypoint lookup : (string, string) => AENS.pointee + +main contract AENSUpdate = stateful entrypoint update_name(owner : address, name : string) = - let p1 : AENS.pointee = AENS.AccountPt(Call.caller) - let p2 : AENS.pointee = AENS.OraclePt(Call.caller) - let p3 : AENS.pointee = AENS.ContractPt(Call.caller) - let p4 : AENS.pointee = AENS.ChannelPt(Call.caller) - AENS.update(owner, name, None, None, - Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2, - ["contract_pubkey"] = p3, ["misc"] = p4 })) + let p1 : AENSv2.pointee = AENSv2.AccountPt(Call.caller) + let p2 : AENSv2.pointee = AENSv2.OraclePt(Call.caller) + let p3 : AENSv2.pointee = AENSv2.ContractPt(Call.caller) + let p4 : AENSv2.pointee = AENSv2.ChannelPt(Call.caller) + let p5 : AENSv2.pointee = AENSv2.DataPt("any something will do") + AENSv2.update(owner, name, None, None, + Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2, + ["contract_pubkey"] = p3, ["misc"] = p4, ["data"] = p5 })) + + stateful entrypoint old_interaction(c : OldAENSContract, owner : address, name : string) = + let p : AENS.pointee = c.lookup(name, "key1") + AENSv2.update(owner, name, None, None, Some({ ["key1"] = AENSCompat.pointee_to_V2(p) })) + switch(AENSv2.lookup(name)) + Some(AENSv2.Name(_, _, pt_map)) => + c.set(name, "key2", Option.force(AENSCompat.pointee_from_V2(pt_map["key1"]))) entrypoint get_ttl(name : string) = - switch(AENS.lookup(name)) - Some(AENS.Name(_, FixedTTL(ttl), _)) => ttl + switch(AENSv2.lookup(name)) + Some(AENSv2.Name(_, FixedTTL(ttl), _)) => ttl entrypoint expiry(o : oracle(int, int)) : int = Oracle.expiry(o) - diff --git a/test/contracts/bad_aens_resolve.aes b/test/contracts/bad_aens_resolve.aes index 8700dfb..44f15e5 100644 --- a/test/contracts/bad_aens_resolve.aes +++ b/test/contracts/bad_aens_resolve.aes @@ -3,7 +3,7 @@ contract BadAENSresolve = type t('a) = option(list('a)) function fail() : t(int) = - AENS.resolve("foo.aet", "whatever") + AENSv2.resolve("foo.aet", "whatever") entrypoint main_fun() = () diff --git a/test/contracts/bad_aens_resolve_using.aes b/test/contracts/bad_aens_resolve_using.aes index 8556f46..5b367e7 100644 --- a/test/contracts/bad_aens_resolve_using.aes +++ b/test/contracts/bad_aens_resolve_using.aes @@ -1,9 +1,9 @@ contract BadAENSresolve = - using AENS + using AENSv2 type t('a) = option(list('a)) function fail() : t(int) = resolve("foo.aet", "whatever") - entrypoint main_fun() = () \ No newline at end of file + entrypoint main_fun() = () diff --git a/test/contracts/polymorphic_aens_resolve.aes b/test/contracts/polymorphic_aens_resolve.aes index 0bd9cc8..15517dd 100644 --- a/test/contracts/polymorphic_aens_resolve.aes +++ b/test/contracts/polymorphic_aens_resolve.aes @@ -1,7 +1,7 @@ contract PolymorphicAENSresolve = function fail() : option('a) = - AENS.resolve("foo.aet", "whatever") + AENSv2.resolve("foo.aet", "whatever") entrypoint main_fun() = () diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index 169a29b..5a8b340 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -2,10 +2,10 @@ // Named argument builtins are: // Oracle.register // Oracle.respond -// AENS.preclaim -// AENS.claim -// AENS.transfer -// AENS.revoke +// AENSv2.preclaim +// AENSv2.claim +// AENSv2.transfer +// AENSv2.revoke // Oracle.extend include "String.aes" contract UnappliedBuiltins = @@ -28,7 +28,7 @@ contract UnappliedBuiltins = function oracle_get_answer() = Oracle.get_answer : (o, _) => _ function oracle_check() = Oracle.check : o => _ function oracle_check_query() = Oracle.check_query : (o, _) => _ - function aens_resolve() = AENS.resolve : (_, _) => option(string) + function aens_resolve() = AENSv2.resolve : (_, _) => option(string) function map_lookup() = Map.lookup : (_, m) => _ function map_lookup_default() = Map.lookup_default : (_, m, _) => _ function map_member() = Map.member : (_, m) => _ From e44174b71c020e560699a8ebfc9ace9fd8fadd30 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 3 Mar 2023 10:06:45 +0100 Subject: [PATCH 22/41] [Ceres]: document changes to Auth.tx_hash (#439) --- docs/sophia_stdlib.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 209c132..9fec147 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -266,7 +266,10 @@ namespace Chain = Auth.tx_hash : option(hash) ``` -Gets the transaction hash during authentication. +Gets the transaction hash during authentication. Note: `Auth.tx_hash` +computation differs between protocol versions (changed in Ceres!), see +[aeserialisation](https://github.com/aeternity/protocol/blob/master/serializations.md) +specification for details. ### Bits From c51d0a5e21f95777d4245d35fb5194294eee4527 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 22 Mar 2023 08:55:57 +0100 Subject: [PATCH 23/41] [Ceres]: Document generic all names delegation signatures (#440) --- docs/sophia_stdlib.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 9fec147..f8d10c1 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -178,6 +178,12 @@ AENSv2.preclaim(owner : address, commitment_hash : hash, The [signature](./sophia_features.md#delegation-signature) should be over `network id` + `owner address` + `Contract.address` (concatenated as byte arrays). +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. over `network id` + `owner address` + `string "AENS"` + +`Contract.address`. + ##### claim ``` @@ -185,9 +191,14 @@ AENSv2.claim(owner : address, name : string, salt : int, name_fee : int, Date: Fri, 30 Jun 2023 16:21:50 +0200 Subject: [PATCH 24/41] [Ceres]: Add arbitrary size byte arrays (#456) * Extend compiler to allow bytes()/bytes as type * Add split_any, to_fixed_size, size, to_any_size, Int.to_bytes and String.to_bytes * Add tests * Use and and not andalso in unify, some things have side-effects * Bump to aebytecode v3.3.0 * Changelog + update documentation * fix wording in documentation --- CHANGELOG.md | 4 + docs/sophia_stdlib.md | 60 +++++++++++-- priv/stdlib/String.aes | 3 + rebar.config | 2 +- src/aeso_aci.erl | 2 + src/aeso_ast_infer_types.erl | 140 +++++++++++++++++++++--------- src/aeso_ast_to_fcode.erl | 22 +++-- src/aeso_fcode_to_fate.erl | 15 ++++ src/aeso_parser.erl | 13 ++- src/aeso_pretty.erl | 4 +- test/aeso_compiler_tests.erl | 35 +++++++- test/contracts/bad_bytes_to_x.aes | 5 ++ test/contracts/bytes_misc.aes | 27 ++++++ test/contracts/bytes_to_x.aes | 2 + 14 files changed, 270 insertions(+), 64 deletions(-) create mode 100644 test/contracts/bad_bytes_to_x.aes create mode 100644 test/contracts/bytes_misc.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f71178..9e4ff71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 only contains the old datatypes, that can be used to interface existing contracts. Standard library `AENSCompat` is added to convert between old and new pointers. +- Introduce arbitrary sized binary arrays (type `bytes()`); adding `Bytes.split_any`, + `Bytes.to_fixed_size`, `Bytes.to_any_size`, `Bytes.size`, `String.to_bytes`, + and `Int.to_bytes`; and adjust `Bytes.concat` to allow both fixed and arbitrary + sized byte arrays. ### Changed ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index f8d10c1..feec8dd 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -371,7 +371,7 @@ Each bit is true if and only if it was 1 in `a` and 0 in `b` ### Bytes -#### to_int +#### to\_int ``` Bytes.to_int(b : bytes(n)) : int ``` @@ -379,7 +379,7 @@ Bytes.to_int(b : bytes(n)) : int Interprets the byte array as a big endian integer -#### to_str +#### to\_str ``` Bytes.to_str(b : bytes(n)) : string ``` @@ -392,7 +392,8 @@ Returns the hexadecimal representation of the byte array Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) ``` -Concatenates two byte arrays +Concatenates two byte arrays. If `m` and `n` are known at compile time, the +result can be used as a fixed size byte array, otherwise it has type `bytes()`. #### split @@ -402,6 +403,38 @@ Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) Splits a byte array at given index +#### split\_any +``` +Bytes.split_any(a : bytes(), at : int) : option(bytes() * bytes(n)) +``` + +Splits an arbitrary size byte array at index `at`. If `at` is positive split +from the beginning of the array, if `at` is negative, split `abs(at)` from the +_end_ of the array. If the array is shorter than `abs(at)` then `None` is +returned. + +#### to\_fixed\_size +``` +Bytes.to_fixed_size(a : bytes()) : option(bytes(n)) +``` + +Converts an arbitrary size byte array to a fix size byte array. If `a` is +`n` bytes, `None` is returned. + +#### to\_any\_size +``` +Bytes.to_any_size(a : bytes(n)) : bytes() +``` + +Converts a fixed size byte array to an arbitrary size byte array. This is a +no-op at run-time, and only used during type checking. + +#### size +``` +Bytes.size(a : bytes()) : int +``` + +Computes the lenght/size of a byte array. ### Call @@ -830,12 +863,20 @@ Verifies a standard 64-byte ECDSA signature (`R || S`). ### Int -#### to_str +#### to\_str ``` -Int.to_str : int => string +Int.to_str(n : int) : string ``` -Casts integer to string using decimal representation +Casts the integer to a string (in decimal representation). + +#### to\_bytes +``` +Int.to_bytes(n : int, size : int) : bytes() +``` + +Casts the integer to a byte array with `size` bytes (big endian, truncating if +necessary). ### Map @@ -2412,6 +2453,13 @@ to_int(s : string) : option(int) Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string into an integer. If the string doesn't contain a valid number `None` is returned. +#### to\_bytes +``` +to_bytes(s : string) : bytes() +``` + +Converts string into byte array. + #### sha3 ``` sha3(s : string) : hash diff --git a/priv/stdlib/String.aes b/priv/stdlib/String.aes index 33f813b..9927774 100644 --- a/priv/stdlib/String.aes +++ b/priv/stdlib/String.aes @@ -1,5 +1,8 @@ include "List.aes" namespace String = + // Gives a bytes() representation of the string + function to_bytes(s : string) : bytes() = StringInternal.to_bytes(s) + // Computes the SHA3/Keccak hash of the string function sha3(s : string) : hash = StringInternal.sha3(s) // Computes the SHA256 hash of the string. diff --git a/rebar.config b/rebar.config index 7e4fa55..767729d 100644 --- a/rebar.config +++ b/rebar.config @@ -14,7 +14,7 @@ ]}. {relx, [{release, {aesophia, "7.4.1"}, - [aesophia, aebytecode, getopt]}, + [aesophia, aebytecode]}, {dev_mode, true}, {include_erts, false}, diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index d0c7c68..5cf5aff 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -282,6 +282,8 @@ decode_type(#{list := [Et]}) -> decode_type(#{map := Ets}) -> Ts = decode_types(Ets), ["map",$(,lists:join(",", Ts),$)]; +decode_type(#{bytes := any}) -> + ["bytes()"]; decode_type(#{bytes := Len}) -> ["bytes(", integer_to_list(Len), ")"]; decode_type(#{variant := Ets}) -> diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 431c093..abf4d24 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -89,8 +89,9 @@ -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. --type byte_constraint() :: {is_bytes, utype()} - | {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}. +-type byte_constraint() :: {is_bytes, term(), utype()} + | {is_fixed_bytes, term(), utype()} + | {add_bytes, aeso_syntax:ann(), concat | split | split_any, utype(), utype(), utype()}. -type aens_resolve_constraint() :: {aens_resolve_type, utype()}. -type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}. @@ -828,6 +829,7 @@ global_env() -> [{"length", Fun1(String, Int)}, {"concat", Fun([String, String], String)}, {"to_list", Fun1(String, List(Char))}, + {"to_bytes", Fun1(String, Bytes(any))}, {"from_list", Fun1(List(Char), String)}, {"to_upper", Fun1(String, String)}, {"to_lower", Fun1(String, String)}, @@ -858,15 +860,20 @@ global_env() -> %% Bytes BytesScope = #scope { funs = MkDefs( - [{"to_int", Fun1(Bytes(any), Int)}, - {"to_str", Fun1(Bytes(any), String)}, - {"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))}, - {"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))} + [{"to_int", Fun1(Bytes('_'), Int)}, + {"to_str", Fun1(Bytes('_'), String)}, + {"to_fixed_size", Fun1(Bytes(any), Option(Bytes(fixed)))}, + {"to_any_size", Fun1(Bytes(fixed), Bytes(any))}, + {"size", Fun1(Bytes('_'), Int)}, + {"concat", FunC(bytes_concat, [Bytes('_'), Bytes('_')], Bytes('_'))}, + {"split", FunC1(bytes_split, Bytes(fixed), Pair(Bytes(fixed), Bytes(fixed)))}, + {"split_any", Fun([Bytes(any), Int], Option(Pair(Bytes(any), Bytes(any))))} ]) }, %% Conversion - IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, - {"mulmod", Fun([Int, Int, Int], Int)}]) }, + IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, + {"to_bytes", Fun([Int, Int], Bytes(any))}, + {"mulmod", Fun([Int, Int, Int], Int)}]) }, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, {"to_bytes", Fun1(Address, Bytes(32))}, @@ -1792,8 +1799,8 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of - {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty); - _ when Freshen -> freshen_type(As, Ty); + {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty, [{fun_name, Id}]); + _ when Freshen -> freshen_type(As, Ty, [{fun_name, Id}]); _ -> Ty end, {set_qname(QId, Id), Ty1} @@ -2666,15 +2673,21 @@ solve_constraint(Env, C = #dependent_type_constraint{}) -> check_named_argument_constraint(Env, C); solve_constraint(Env, C = #named_argument_constraint{}) -> check_named_argument_constraint(Env, C); -solve_constraint(_Env, {is_bytes, _}) -> false; -solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) -> +solve_constraint(_Env, {is_bytes, _, _}) -> false; +solve_constraint(_Env, {is_fixed_bytes, _, _}) -> false; +solve_constraint(Env, {add_bytes, Ann, Action, A0, B0, C0}) -> A = unfold_types_in_type(Env, dereference(A0)), B = unfold_types_in_type(Env, dereference(B0)), C = unfold_types_in_type(Env, dereference(C0)), case {A, B, C} of - {{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); - {{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); - {_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + {{bytes_t, _, M}, {bytes_t, _, N}, _} when is_integer(M), is_integer(N) -> + unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); + {{bytes_t, _, M}, _, {bytes_t, _, R}} when is_integer(M), is_integer(R), R >= M -> + unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); + {_, {bytes_t, _, N}, {bytes_t, _, R}} when is_integer(N), is_integer(R), R >= N -> + unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + {{bytes_t, _, _}, {bytes_t, _, _}, _} when Action == concat -> + unify(Env, {bytes_t, Ann, any}, C, {at, Ann}); _ -> false end; solve_constraint(_, _) -> false. @@ -2731,7 +2744,8 @@ destroy_and_report_unsolved_constraints(Env) -> (_) -> false end, OtherCs2), {BytesCs, OtherCs4} = - lists:partition(fun({is_bytes, _}) -> true; + lists:partition(fun({is_bytes, _, _}) -> true; + ({is_fixed_bytes, _, _}) -> true; ({add_bytes, _, _, _, _, _}) -> true; (_) -> false end, OtherCs3), @@ -2831,18 +2845,29 @@ check_bytes_constraints(Env, Constraints) -> InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints, T <- [A, B, C], element(1, T) /= bytes_t ], + InSplitConstraint = [ T || {add_bytes, _, split, A, B, C} <- Constraints, + T <- [A, B, C], + element(1, T) /= bytes_t ], %% Skip is_bytes constraints for types that occur in add_bytes constraints %% (no need to generate error messages for both is_bytes and add_bytes). - Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint); + Skip = fun({is_bytes, _, T}) -> lists:member(T, InAddConstraint); + ({is_fixed_bytes, _, T}) -> lists:member(T, InSplitConstraint); (_) -> false end, [ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ]. -check_bytes_constraint(Env, {is_bytes, Type}) -> +check_bytes_constraint(Env, {is_bytes, Ann, Type}) -> Type1 = unfold_types_in_type(Env, instantiate(Type)), case Type1 of - {bytes_t, _, _} -> ok; + {bytes_t, _, N} when is_integer(N); N == any -> ok; _ -> - type_error({unknown_byte_length, Type}) + type_error({unknown_byte_type, Ann, Type}) + end; +check_bytes_constraint(Env, {is_fixed_bytes, Ann, Type}) -> + Type1 = unfold_types_in_type(Env, instantiate(Type)), + case Type1 of + {bytes_t, _, N} when is_integer(N) -> ok; + _ -> + type_error({unknown_byte_length, Ann, Type}) end; check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) -> A = unfold_types_in_type(Env, instantiate(A0)), @@ -3115,9 +3140,9 @@ unify1(Env, T, {uvar, A, R}, Variance, When) -> unify1(Env, {uvar, A, R}, T, Variance, When); unify1(_Env, {tvar, _, X}, {tvar, _, X}, _Variance, _When) -> true; %% Rigid type variables unify1(Env, [A|B], [C|D], [V|Variances], When) -> - unify0(Env, A, C, V, When) andalso unify0(Env, B, D, Variances, When); + unify0(Env, A, C, V, When) and unify0(Env, B, D, Variances, When); unify1(Env, [A|B], [C|D], Variance, When) -> - unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When); + unify0(Env, A, C, Variance, When) and unify0(Env, B, D, Variance, When); unify1(_Env, X, X, _Variance, _When) -> true; unify1(_Env, _A, {id, _, "void"}, Variance, _When) @@ -3159,8 +3184,8 @@ unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) - false; unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When) when length(Args1) == length(Args2) -> - unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso - unify0(Env, Args1, Args2, opposite_variance(Variance), When) andalso + unify0(Env, Named1, Named2, opposite_variance(Variance), When) and + unify0(Env, Args1, Args2, opposite_variance(Variance), When) and unify0(Env, Result1, Result2, Variance, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, Variance, When) when length(Args1) == length(Args2), Tag == id orelse Tag == qid -> @@ -3269,49 +3294,59 @@ create_freshen_tvars() -> destroy_freshen_tvars() -> ets_delete(freshen_tvars). -freshen_type(Ann, Type) -> +freshen_type(Ann, Type, Ctx) -> create_freshen_tvars(), - Type1 = freshen(Ann, Type), + Type1 = freshen(Ann, Type, Ctx), destroy_freshen_tvars(), Type1. freshen(Type) -> - freshen(aeso_syntax:get_ann(Type), Type). + freshen(aeso_syntax:get_ann(Type), Type, none). -freshen(Ann, {tvar, _, Name}) -> +freshen(Ann, {tvar, _, Name}, _Ctx) -> NewT = case ets_lookup(freshen_tvars, Name) of [] -> fresh_uvar(Ann); [{Name, T}] -> T end, ets_insert(freshen_tvars, {Name, NewT}), NewT; -freshen(Ann, {bytes_t, _, any}) -> +freshen(Ann, {bytes_t, _, '_'}, Ctx) -> X = fresh_uvar(Ann), - add_constraint({is_bytes, X}), + add_constraint({is_bytes, Ctx, X}), X; -freshen(Ann, T) when is_tuple(T) -> - list_to_tuple(freshen(Ann, tuple_to_list(T))); -freshen(Ann, [A | B]) -> - [freshen(Ann, A) | freshen(Ann, B)]; -freshen(_, X) -> +freshen(Ann, {bytes_t, _, fixed}, Ctx) -> + X = fresh_uvar(Ann), + add_constraint({is_fixed_bytes, Ctx, X}), + X; +freshen(Ann, {fun_t, FAnn, NamedArgs, Args, Result}, Ctx) when is_list(Args) -> + {fun_t, FAnn, freshen(Ann, NamedArgs, Ctx), + [ freshen(Ann, Arg, [{arg, Ix} | Ctx]) || {Arg, Ix} <- lists:zip(Args, lists:seq(1, length(Args))) ], + freshen(Ann, Result, [result | Ctx])}; +freshen(Ann, {fun_t, FAnn, NamedArgs, Arg, Result}, Ctx) -> + {fun_t, FAnn, freshen(Ann, NamedArgs, Ctx), freshen(Ann, Arg, Ctx), freshen(Ann, Result, [result | Ctx])}; +freshen(Ann, T, Ctx) when is_tuple(T) -> + list_to_tuple(freshen(Ann, tuple_to_list(T), Ctx)); +freshen(Ann, [A | B], Ctx) -> + [freshen(Ann, A, Ctx) | freshen(Ann, B, Ctx)]; +freshen(_, X, _Ctx) -> X. -freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) -> - FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), +freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}, Ctx) -> + FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig), Ctx), apply_typesig_constraint(Ann, Constr, FunT), FunT. apply_typesig_constraint(_Ann, none, _FunT) -> ok; apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) -> add_constraint([#is_contract_constraint{ contract_t = Type, - context = {address_to_contract, Ann}}]); + context = {address_to_contract, Ann}}]); apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> add_constraint({add_bytes, Ann, concat, A, B, C}); apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> add_constraint({add_bytes, Ann, split, A, B, C}); apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) -> add_constraint([#is_contract_constraint{ contract_t = Con, - context = {bytecode_hash, Ann} }]). + context = {bytecode_hash, Ann} }]). %% Dereferences all uvars and replaces the uninstantiated ones with a @@ -3832,8 +3867,11 @@ mk_error({bad_top_level_decl, Decl}) -> Msg = io_lib:format("The definition of '~s' must appear inside a ~s.", [pp_expr(Id), What]), mk_t_err(pos(Decl), Msg); -mk_error({unknown_byte_length, Type}) -> - Msg = io_lib:format("Cannot resolve length of byte array.", []), +mk_error({unknown_byte_type, Ctx, Type}) -> + Msg = io_lib:format("Cannot resolve type of byte array in\n ~s", [pp_context(Ctx)]), + mk_t_err(pos(Type), Msg); +mk_error({unknown_byte_length, Ctx, Type}) -> + Msg = io_lib:format("Cannot resolve length of byte array in\n ~s", [pp_context(Ctx)]), mk_t_err(pos(Type), Msg); mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) -> Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n" @@ -3847,6 +3885,12 @@ mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) -> [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), pp_type(" - ", B), pp_loc(B)]), mk_t_err(pos(Ann), Msg); +mk_error({unsolved_bytes_constraint, Ann, split_any, A, B, C}) -> + Msg = io_lib:format("Failed to resolve byte arrays in call to Bytes.split_any with argument of type\n" + "~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)", + [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), + pp_type(" - ", B), pp_loc(B)]), + mk_t_err(pos(Ann), Msg); mk_error({failed_to_get_compiler_version, Err}) -> Msg = io_lib:format("Failed to get compiler version. Error: ~p", [Err]), mk_t_err(pos(0, 0), Msg); @@ -4217,6 +4261,18 @@ pp_type(Type) -> pp_type(Label, Type) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated])), 80, 80). + +pp_context([{fun_name, Id}]) -> ["a call to ", pp(Id)]; +pp_context([result | Ctx]) -> ["the result of ", pp_context(Ctx)]; +pp_context([{arg, N} | Ctx]) -> + Cnt = fun(1) -> "first"; + (2) -> "second"; + (3) -> "third"; + (I) -> io_lib:format("~pth", [I]) + end, + ["the ", Cnt(N), " argument of ", pp_context(Ctx)]; +pp_context(none) -> "unknown context". + src_file(T) -> aeso_syntax:get_ann(file, T, no_file). include_type(T) -> aeso_syntax:get_ann(include_type, T, none). line_number(T) -> aeso_syntax:get_ann(line, T, 0). @@ -4268,7 +4324,7 @@ pp({tuple_t, _, []}) -> "unit"; pp({tuple_t, _, Cpts}) -> ["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"]; -pp({bytes_t, _, any}) -> "bytes(_)"; +pp({bytes_t, _, any}) -> "bytes()"; pp({bytes_t, _, Len}) -> ["bytes(", integer_to_list(Len), ")"]; pp({app_t, _, T, []}) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index bd5fc94..1bd2bcc 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -294,14 +294,15 @@ builtins() -> {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, {"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1}, {"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]}, - {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, + {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, {"to_bytes", 1}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"to_lower", 1}, {"to_upper", 1}]}, {["Char"], [{"to_int", 1}, {"from_int", 1}]}, {["Auth"], [{"tx_hash", none}, {"tx", none}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, - {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, - {["Int"], [{"to_str", 1}, {"mulmod", 2}]}, + {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}, {"to_fixed_size", 1}, + {"to_any_size", 1}, {"size", 1}, {"split_any", 2}]}, + {["Int"], [{"to_str", 1}, {"to_bytes", 2}, {"mulmod", 2}]}, {["Address"], [{"to_str", 1}, {"to_bytes", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], @@ -626,6 +627,9 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) -> {builtin_u, FAnn, B = bytes_split, Ar} -> {fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type, {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; + {builtin_u, FAnn, B = bytes_to_fixed_size, Ar} -> + {fun_t, _, _, _, {app_t, _, {id, _, "option"}, [{bytes_t, _, N}]}} = Type, + {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; Other -> Other end; @@ -1181,13 +1185,13 @@ stmts_to_fcode(Env, [Expr | Stmts]) -> op_builtins() -> [map_from_list, map_to_list, map_delete, map_member, map_size, stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list, - stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, + stringinternal_to_bytes, stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper, - bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, - bits_difference, int_to_str, int_mulmod, address_to_str, address_to_bytes, crypto_verify_sig, - address_to_contract, - crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, crypto_poseidon, - crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, + bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_difference, + int_to_str, int_to_bytes, int_mulmod, + address_to_str, address_to_bytes, address_to_contract, + crypto_verify_sig, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, + crypto_poseidon, crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, mcl_bls12_381_g2_neg, mcl_bls12_381_g2_norm, mcl_bls12_381_g2_valid, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 95f80fe..ba4506e 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -539,6 +539,14 @@ builtin_to_scode(Env, bytes_concat, [_, _] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_concat(?a, ?a, ?a), Args); builtin_to_scode(Env, bytes_split, [_, _] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_split_any, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_split_any(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_to_fixed_size, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_to_fixed_size(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_to_any_size, [A]) -> + [to_scode(Env, A)]; %% no_op! +builtin_to_scode(Env, bytes_size, [_] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_size(?a, ?a), Args); builtin_to_scode(Env, abort, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:abort(?a), Args); builtin_to_scode(Env, exit, [_] = Args) -> @@ -683,6 +691,7 @@ op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a); op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a); op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a); op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_to_bytes) -> aeb_fate_ops:str_to_bytes(?a, ?a); op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a); op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a); op_to_scode(stringinternal_to_lower) -> aeb_fate_ops:str_to_lower(?a, ?a); @@ -699,6 +708,7 @@ op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); op_to_scode(address_to_bytes) -> aeb_fate_ops:addr_to_bytes(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); +op_to_scode(int_to_bytes) -> aeb_fate_ops:int_to_bytes(?a, ?a, ?a); op_to_scode(int_mulmod) -> aeb_fate_ops:mulmod(?a, ?a, ?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a); @@ -1019,9 +1029,11 @@ attributes(I) -> {'APPEND', A, B, C} -> Pure(A, [B, C]); {'STR_JOIN', A, B, C} -> Pure(A, [B, C]); {'INT_TO_STR', A, B} -> Pure(A, B); + {'INT_TO_BYTES', A, B, C} -> Pure(A, [B, C]); {'ADDR_TO_STR', A, B} -> Pure(A, B); {'STR_REVERSE', A, B} -> Pure(A, B); {'STR_LENGTH', A, B} -> Pure(A, B); + {'STR_TO_BYTES', A, B} -> Pure(A, B); {'INT_TO_ADDR', A, B} -> Pure(A, B); {'VARIANT', A, B, C, D} -> Pure(A, [?a, B, C, D]); {'VARIANT_TEST', A, B, C} -> Pure(A, [B, C]); @@ -1055,6 +1067,9 @@ attributes(I) -> {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); {'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SPLIT_ANY', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SIZE', A, B} -> Pure(A, B); + {'BYTES_TO_FIXED_SIZE', A, B, C} -> Pure(A, [B, C]); {'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]); {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]); {'IS_ORACLE', A, B} -> Pure(A, [B]); diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 12826ae..1591078 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -266,10 +266,11 @@ type300() -> type400() -> choice( [?RULE(typeAtom(), optional(type_args()), - case _2 of - none -> _1; - {ok, Args} -> {app_t, get_ann(_1), _1, Args} - end), + any_bytes( + case _2 of + none -> _1; + {ok, Args} -> {app_t, get_ann(_1), _1, Args} + end)), ?RULE(id("bytes"), parens(token(int)), {bytes_t, get_ann(_1), element(3, _2)}) ]). @@ -816,3 +817,7 @@ auto_imports(L) when is_list(L) -> auto_imports(T) when is_tuple(T) -> auto_imports(tuple_to_list(T)); auto_imports(_) -> []. + +any_bytes({id, Ann, "bytes"}) -> {bytes_t, Ann, any}; +any_bytes({app_t, _, {id, Ann, "bytes"}, []}) -> {bytes_t, Ann, any}; +any_bytes(Type) -> Type. diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 3ef3300..2697288 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -275,7 +275,9 @@ type({tuple_t, _, Args}) -> tuple_type(Args); type({args_t, _, Args}) -> args_type(Args); -type({bytes_t, _, any}) -> text("bytes(_)"); +type({bytes_t, _, any}) -> text("bytes()"); +type({bytes_t, _, '_'}) -> text("bytes(_)"); +type({bytes_t, _, fixed}) -> text("bytes(_)"); type({bytes_t, _, Len}) -> text(lists:concat(["bytes(", Len, ")"])); type({if_t, _, Id, Then, Else}) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 154712a..3bd922d 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -172,6 +172,7 @@ compilable_contracts() -> "namespace_bug", "bytes_to_x", "bytes_concat", + "bytes_misc", "aens", "aens_update", "tuple_match", @@ -450,6 +451,10 @@ failing_contracts() -> [<>, + <>]) , ?TYPE_ERROR(not_toplevel_include, [< [<>]) + , ?TYPE_ERROR(bad_bytes_to_x, + [<>, + < option('a)`\n" + "to arguments\n" + " `b : bytes(4)`">>, + <>, + <>]) , ?TYPE_ERROR(bad_bytes_concat, [< "and result type\n" " - 'c (at line 16, column 39)">>, <>]) + "Cannot resolve type of byte array in\n" + " the first argument of a call to Bytes.to_str">>]) , ?TYPE_ERROR(bad_bytes_split, [< < Cat) => dt_inv(Cat)`\nto arguments\n `f_c_to_a : (Cat) => Animal`">>, + <>, <>, @@ -979,6 +1003,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1022,6 +1049,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1046,6 +1076,9 @@ failing_contracts() -> <>, + <>, <>, diff --git a/test/contracts/bad_bytes_to_x.aes b/test/contracts/bad_bytes_to_x.aes new file mode 100644 index 0000000..4e3db65 --- /dev/null +++ b/test/contracts/bad_bytes_to_x.aes @@ -0,0 +1,5 @@ +// include "String.aes" +contract BytesToX = + entrypoint fail1(b : bytes()) = Bytes.to_fixed_size(b) + entrypoint fail2(b : bytes(4)) = Bytes.to_fixed_size(b) + entrypoint fail3(b : bytes()) = Bytes.to_any_size(b) diff --git a/test/contracts/bytes_misc.aes b/test/contracts/bytes_misc.aes new file mode 100644 index 0000000..13e7fb4 --- /dev/null +++ b/test/contracts/bytes_misc.aes @@ -0,0 +1,27 @@ +include "String.aes" +contract BytesMisc = + entrypoint sizeFixed(b : bytes(4)) : int = Bytes.size(b) + entrypoint sizeAny(b : bytes()) : int = Bytes.size(b) + entrypoint int_to_bytes(i : int) : bytes() = Int.to_bytes(i, 16) + + entrypoint test(b3 : bytes(3), b7 : bytes(7), bX : bytes, i : int, s : string) = + let bi = Int.to_bytes(i, 8) + let bs = String.to_bytes(s) + + let b10 = Bytes.concat(b3, b7) + + let (b4, b6 : bytes(6)) = Bytes.split(b10) + + let Some((b8, b2)) = Bytes.split_any(bX, 8) + + let bX7 = Bytes.concat(bX, b7) + + let Some((b5, bX2)) = Bytes.split_any(bX7, 5) + + let Some((b7b, b0)) = Bytes.split_any(bX, Bytes.size(b7)) + + let Some(b5b : bytes(5)) = Bytes.to_fixed_size(b5) + + let (b1 : bytes(1), _) = Bytes.split(b5b) + + [bi, bs, b0, Bytes.to_any_size(b1), b2, Bytes.to_any_size(b4), Bytes.to_any_size(b6), b7b, b8, bX2] diff --git a/test/contracts/bytes_to_x.aes b/test/contracts/bytes_to_x.aes index 6ab4852..14e150d 100644 --- a/test/contracts/bytes_to_x.aes +++ b/test/contracts/bytes_to_x.aes @@ -6,3 +6,5 @@ contract BytesToX = String.concat(Bytes.to_str(b), Bytes.to_str(#ffff)) entrypoint to_str_big(b : bytes(65)) : string = Bytes.to_str(b) + entrypoint to_fixed(b : bytes()) : option(bytes(4)) = Bytes.to_fixed_size(b) + entrypoint to_any(b : bytes(4)) = Bytes.to_any_size(b) From 108cb1f94871a309e741da67fbfc0d9fd472fe6f Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 3 Jul 2023 08:04:16 +0200 Subject: [PATCH 25/41] [Ceres]: Add Chain.network_id (#468) * Add Chain.network_id * Bump aebytecode version --- CHANGELOG.md | 1 + docs/sophia_stdlib.md | 8 ++++++++ rebar.config | 2 +- rebar.lock | 2 +- src/aeso_ast_infer_types.erl | 1 + src/aeso_ast_to_fcode.erl | 4 ++-- src/aeso_fcode_to_fate.erl | 3 +++ test/contracts/chain.aes | 6 +++++- 8 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e4ff71..0a00358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `Bytes.to_fixed_size`, `Bytes.to_any_size`, `Bytes.size`, `String.to_bytes`, and `Int.to_bytes`; and adjust `Bytes.concat` to allow both fixed and arbitrary sized byte arrays. +- `Chain.network_id` - a function to get hold of the Chain's network id. ### Changed ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index feec8dd..b07e4a1 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -734,6 +734,14 @@ Chain.gas_limit : int The gas limit of the current block. +##### network\_id +``` +Chain.network\_id : string +``` + +The network id of the chain. + + ##### spend ``` Chain.spend(to : address, amount : int) : unit diff --git a/rebar.config b/rebar.config index 767729d..842f919 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.3.0"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.4.0"}}} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} ]}. diff --git a/rebar.lock b/rebar.lock index 7f13b93..58ce514 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.2.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"b38349274fc2bed98d7fe86877e6e1a2df302109"}}, + {ref,"009e0361922037f978f9c0ef357d4d1be8559928"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index abf4d24..12e51f4 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -653,6 +653,7 @@ global_env() -> {"block_height", Int}, {"difficulty", Int}, {"gas_limit", Int}, + {"network_id", String}, {"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))}, {"create", Stateful( FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 1bd2bcc..d2bb96d 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -274,8 +274,8 @@ builtins() -> end, Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]}, {["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none}, - {"timestamp", none}, {"block_height", none}, {"difficulty", none}, - {"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]}, + {"timestamp", none}, {"block_height", none}, {"difficulty", none}, {"gas_limit", none}, + {"network_id", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]}, {["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]}, {["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"fee", none}, {"gas_left", 0}]}, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index ba4506e..e218778 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -568,6 +568,8 @@ builtin_to_scode(_Env, chain_difficulty, []) -> [aeb_fate_ops:difficulty(?a)]; builtin_to_scode(_Env, chain_gas_limit, []) -> [aeb_fate_ops:gaslimit(?a)]; +builtin_to_scode(_Env, chain_network_id, []) -> + [aeb_fate_ops:network_id(?a)]; builtin_to_scode(_Env, contract_balance, []) -> [aeb_fate_ops:balance(?a)]; builtin_to_scode(_Env, contract_address, []) -> @@ -1090,6 +1092,7 @@ attributes(I) -> {'MICROBLOCK', A} -> Pure(A, []); {'DIFFICULTY', A} -> Pure(A, []); {'GASLIMIT', A} -> Pure(A, []); + {'NETWORK_ID', A} -> Pure(A, []); {'GAS', A} -> Pure(A, []); {'LOG0', A} -> Impure(none, [A]); {'LOG1', A, B} -> Impure(none, [A, B]); diff --git a/test/contracts/chain.aes b/test/contracts/chain.aes index 9063a5e..a43493d 100644 --- a/test/contracts/chain.aes +++ b/test/contracts/chain.aes @@ -2,7 +2,8 @@ contract ChainTest = - record state = { last_bf : address } + record state = { last_bf : address + , nw_id : string } function init() : state = {last_bf = Contract.address} @@ -11,3 +12,6 @@ contract ChainTest = function save_coinbase() = put(state{last_bf = Chain.coinbase}) + + function save_network_id() = + put(state{nw_id = Chain.network_id}) From 78b758c337fd4e8683baad78a8defba2dd5191fb Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 24 Aug 2023 16:14:40 +0200 Subject: [PATCH 26/41] [Ceres]: Allow arbitrary sized message in Crypto.verify_sig (#481) * Allow arbitrary sized msg in signature verification * Move Address.to_bytes documentation to correct place --- CHANGELOG.md | 2 ++ docs/sophia_stdlib.md | 21 +++++++++++---------- src/aeso_ast_infer_types.erl | 2 +- test/contracts/unapplied_builtins.aes | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a00358..f561e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 sized byte arrays. - `Chain.network_id` - a function to get hold of the Chain's network id. ### Changed +- `Crypto.verify_sig` is changed to have `msg : bytes()`. I.e. the + signed data can be of any length (used to be limited to `bytes(32)`/`hash`). ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index b07e4a1..c80dfc8 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -57,6 +57,12 @@ Address.to_str(a : address) : string Base58 encoded string +#### to_bytes +``` +Address.to_bytes(a : address) : bytes(32) +``` + +The binary representation of the address. #### is_contract ``` @@ -564,14 +570,6 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). -#### to_bytes -``` -Address.to_bytes(a : address) : bytes(32) -``` - -The binary representation of the address. - - ##### bytecode_hash ``` Chain.bytecode_hash : 'c => option(hash) @@ -834,11 +832,14 @@ Hash any object to blake2b #### verify_sig ``` -Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool +Crypto.verify_sig(msg : bytes(), pubkey : address, sig : signature) : bool ``` Checks if the signature of `msg` was made using private key corresponding to -the `pubkey` +the `pubkey`. + +Note: before v8 of the compiler, `msg` had type `hash` (i.e. `bytes(32)`). + #### ecverify_secp256k1 ``` diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 12e51f4..afbba6d 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -775,7 +775,7 @@ global_env() -> %% Crypto/Curve operations CryptoScope = #scope { funs = MkDefs( - [{"verify_sig", Fun([Hash, Address, SignId], Bool)}, + [{"verify_sig", Fun([Bytes('_'), Address, SignId], Bool)}, {"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)}, {"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)}, {"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))}, diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index 5a8b340..f0be4c9 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -36,7 +36,7 @@ contract UnappliedBuiltins = function map_delete() = Map.delete : (_, m) => _ function map_from_list() = Map.from_list : _ => m function map_to_list() = Map.to_list : m => _ - function crypto_verify_sig() = Crypto.verify_sig + function crypto_verify_sig() = Crypto.verify_sig : (bytes(), _, _) => _ function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1 function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1 function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1 From 745eeda8585ccd7f1462cddcbb1b134d76edabc3 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 15 Dec 2023 13:27:12 +0100 Subject: [PATCH 27/41] [Ceres]: Raw pointers as bytes (#493) * DataPt should contain bytes() now that we have it * Fix missing negation in docs --- docs/sophia_stdlib.md | 4 ++-- src/aeso_ast_infer_types.erl | 2 +- src/aeso_ast_to_fcode.erl | 4 ++-- src/aeso_vm_decode.erl | 22 ++++++++++++---------- test/contracts/aens_update.aes | 8 +++++--- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index c80dfc8..17b2ea4 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -142,7 +142,7 @@ datatype name = Name(address, Chain.ttl, map(string, AENSv2.pointee)) ``` datatype pointee = AccountPt(address) | OraclePt(address) - | ContractPt(address) | ChannelPt(address) | DataPt(string) + | ContractPt(address) | ChannelPt(address) | DataPt(bytes()) ``` Note: on-chain there is a maximum length enforced for `DataPt`, it is 1024 bytes. @@ -425,7 +425,7 @@ Bytes.to_fixed_size(a : bytes()) : option(bytes(n)) ``` Converts an arbitrary size byte array to a fix size byte array. If `a` is -`n` bytes, `None` is returned. +not `n` bytes, `None` is returned. #### to\_any\_size ``` diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index afbba6d..ce7bea6 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -756,7 +756,7 @@ global_env() -> {"OraclePt", Fun1(Address, PointeeV2)}, {"ContractPt", Fun1(Address, PointeeV2)}, {"ChannelPt", Fun1(Address, PointeeV2)}, - {"DataPt", Fun1(String, PointeeV2)}, + {"DataPt", Fun1(Bytes(any), PointeeV2)}, %% Name object constructor v2 {"Name", Fun([Address, TTL, Map(String, PointeeV2)], AENSNameV2)} ]) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index d2bb96d..39e3fb5 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -340,8 +340,8 @@ init_type_env() -> ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}), ["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}), ["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}), - ["AENSv2", "pointee"] => ?type({variant, [[address], [address], [address], [address], [string]]}), - ["AENSv2", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address], [string]]}}]]}), + ["AENSv2", "pointee"] => ?type({variant, [[address], [address], [address], [address], [{bytes, any}]]}), + ["AENSv2", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address], [{bytes, any}]]}}]]}), ["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}), ["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}), ["Chain", "base_tx"] => ?type(BaseTx), diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index e1941b8..cbbebd5 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -15,6 +15,7 @@ from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin}; from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; +from_fate({bytes_t, _, any}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin}; from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N); from_fate({id, _, "int"}, N) when is_integer(N) -> @@ -78,6 +79,7 @@ from_fate_builtin(QType, Val) -> Hsh = {bytes_t, [], 32}, I32 = {bytes_t, [], 32}, I48 = {bytes_t, [], 48}, + Bts = {bytes_t, [], any}, Qid = fun(Name) -> {qid, [], Name} end, Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end, ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0], @@ -103,16 +105,16 @@ from_fate_builtin(QType, Val) -> App(["AENSv2","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL), Chk(Map(Str, Qid(["AENSv2", "pointee"])), Ptrs)]); - {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 0, {Val}}} -> - App(["AENSv2","AccountPt"], [Chk(Adr, Val)]); - {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 1, {Val}}} -> - App(["AENSv2","OraclePt"], [Chk(Adr, Val)]); - {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 2, {Val}}} -> - App(["AENSv2","ContractPt"], [Chk(Adr, Val)]); - {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 3, {Val}}} -> - App(["AENSv2","ChannelPt"], [Chk(Adr, Val)]); - {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 4, {Val}}} -> - App(["AENSv2","DataPt"], [Chk(Str, Val)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 0, {Value}}} -> + App(["AENSv2","AccountPt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 1, {Value}}} -> + App(["AENSv2","OraclePt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 2, {Value}}} -> + App(["AENSv2","ContractPt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 3, {Value}}} -> + App(["AENSv2","ChannelPt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 4, {Value}}} -> + App(["AENSv2","DataPt"], [Chk(Bts, Value)]); {["Chain", "ga_meta_tx"], {variant, [2], 0, {Addr, X}}} -> App(["Chain","GAMetaTx"], [Chk(Adr, Addr), Chk(Int, X)]); diff --git a/test/contracts/aens_update.aes b/test/contracts/aens_update.aes index 3b8fc7c..9a0804d 100644 --- a/test/contracts/aens_update.aes +++ b/test/contracts/aens_update.aes @@ -1,19 +1,21 @@ include "Option.aes" +include "String.aes" include "AENSCompat.aes" contract interface OldAENSContract = entrypoint set : (string, string, AENS.pointee) => unit entrypoint lookup : (string, string) => AENS.pointee main contract AENSUpdate = - stateful entrypoint update_name(owner : address, name : string) = + stateful entrypoint update_name(owner : address, name : string, b : bytes(2)) = let p1 : AENSv2.pointee = AENSv2.AccountPt(Call.caller) let p2 : AENSv2.pointee = AENSv2.OraclePt(Call.caller) let p3 : AENSv2.pointee = AENSv2.ContractPt(Call.caller) let p4 : AENSv2.pointee = AENSv2.ChannelPt(Call.caller) - let p5 : AENSv2.pointee = AENSv2.DataPt("any something will do") + let p5 : AENSv2.pointee = AENSv2.DataPt(String.to_bytes("any something will do")) + let p6 : AENSv2.pointee = AENSv2.DataPt(Int.to_bytes(1345, 4)) AENSv2.update(owner, name, None, None, Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2, - ["contract_pubkey"] = p3, ["misc"] = p4, ["data"] = p5 })) + ["contract_pubkey"] = p3, ["misc"] = p4, ["data"] = p5, ["data2"] = p6 })) stateful entrypoint old_interaction(c : OldAENSContract, owner : address, name : string) = let p : AENS.pointee = c.lookup(name, "key1") From 1a80f3faa0ec81174d9ae61335c2979042201b4d Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 23 Jan 2024 15:59:30 +0100 Subject: [PATCH 28/41] [Ceres]: Update delegation signature documentation (#494) * [Ceres]: Update delegation signature documentation * Fix TYPOs --- docs/sophia_features.md | 46 +++++++++++++++++++++------ docs/sophia_stdlib.md | 69 +++++++++++++++++++++++------------------ 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 487bb42..728be9e 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -948,16 +948,18 @@ functions are provided. ## AENS interface -Contracts can interact with the -[æternity naming system](https://github.com/aeternity/protocol/blob/master/AENS.md). -For this purpose the [AENS](sophia_stdlib.md#aens) library was exposed. +Contracts can interact with the [æternity naming +system](https://github.com/aeternity/protocol/blob/master/AENS.md). For this +purpose the [AENS](sophia_stdlib.md#aens) and later the +[AENSv2](sophia_stdlib.md#aensv2) library was exposed. ### Example In this example we assume that the name `name` already exists, and is owned by an account with address `addr`. In order to allow a contract `ct` to handle -`name` the account holder needs to create a -[signature](#delegation-signature) `sig` of `addr | name.hash | ct.address`. +`name` the account holder needs to create a [delegation +signature](#delegation-signature) `sig` from the name owner address `addr`, the +name hash and the contract address. Armed with this information we can for example write a function that extends the name if it expires within 1000 blocks: @@ -1099,8 +1101,34 @@ however is in the gas consumption — while `abort` returns unused gas, a call t ## Delegation signature -Some chain operations (`Oracle.` and `AENS.`) have an +Some chain operations (`Oracle.` and `AENSv2.`) have an optional delegation signature. This is typically used when a user/accounts -would like to allow a contract to act on it's behalf. The exact data to be -signed varies for the different operations, but in all cases you should prepend -the signature data with the `network_id` (`ae_mainnet` for the æternity mainnet, etc.). +would like to allow a contract to act on it's behalf. + +### From Ceres + +From the Ceres protocol version the delegation signatures have more structure, +including a unique tag, `network_id` and identifiers; there are five different +delegation signatures: + + - AENS wildcard - the user signs: `owner account + contract` + - `AENS_PRECLAIM` - the user signs: `owner account + contract` + - `AENS_CLAIM, AENS_UPDATE, AENS_TRANSFER, AENS_REVOKE` - the user signs: `owner account + name hash + contract` + - `ORACLE_REGISTER, ORACLE_EXTEND` - the user signs: `owner account + contract` + - `ORACLE_RESPOND` - the user signs: `query id + contract` + +See [Serialized signature +data](https://github.com/aeternity/protocol/blob/master/contracts/fate.md#from-ceres-serialized-signature-data) +for the exact structure used. + +### Before ceres + +The exact data to be signed varies for the different operations, but in all +cases you should prepend the signature data with the `network_id` (`ae_mainnet` +for the æternity mainnet, etc.). + +There are four different delegation signatures: + - `AENS_PRECLAIM` - the user signs: owner `network_id + account + contract` + - `AENS_CLAIM, AENS_UPDATE, AENS_TRANSFER, AENS_REVOKE` - the user signs: `network_id + owner account + name hash + contract` + - `ORACLE_REGISTER, ORACLE_EXTEND` - the user signs: `network_id + owner account + contract` + - `ORACLE_RESPOND` - the user signs: `network_id + query id + contract` diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 17b2ea4..8bd2b88 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -181,14 +181,14 @@ Note: Changed to produce `AENSv2.name` in v8.0 (Ceres protocol upgrade). AENSv2.preclaim(owner : address, commitment_hash : hash, ) : unit ``` -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `Contract.address` (concatenated as byte arrays). +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. From Ceres (i.e. FATE VM version 3) the [signature](./sophia_features.md#delegation-signature) can also be generic (allowing _all_, existing and future, names to be delegated with one -signature), i.e. over `network id` + `owner address` + `string "AENS"` + -`Contract.address`. +signature), i.e. containing `network id`, `owner address`, `Contract.address`. ##### claim @@ -196,14 +196,14 @@ signature), i.e. over `network id` + `owner address` + `string "AENS"` + AENSv2.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `name_hash` + `Contract.address` (concatenated -as byte arrays) using the private key of the `owner` account for signing. +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. From Ceres (i.e. FATE VM version 3) the [signature](./sophia_features.md#delegation-signature) can also be generic (allowing _all_, existing and future, names to be delegated with one -signature), i.e. over `network id` + `owner address` + `string "AENS"` + +signature), i.e. containing `network id`, `owner address`, `name_hash`, and `Contract.address`. @@ -214,15 +214,14 @@ AENSv2.transfer(owner : address, new_owner : address, name : string, ) : unit Revokes the name to extend the ownership time. -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `name_hash` + `Contract.address` -(concatenated as byte arrays) -using the private key of the `owner` account for signing. +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. From Ceres (i.e. FATE VM version 3) the [signature](./sophia_features.md#delegation-signature) can also be generic (allowing _all_, existing and future, names to be delegated with one -signature), i.e. over `network id` + `owner address` + `string "AENS"` + +signature), i.e. containing `network id`, `owner address`, `name_hash`, and `Contract.address`. @@ -257,6 +255,16 @@ block of the name is not changed. Note: Changed to consume `AENSv2.pointee` in v8.0 (Ceres protocol upgrade). +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. + +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. containing `network id`, `owner address`, `name_hash`, and +`Contract.address`. + ### Auth @@ -944,11 +952,11 @@ Oracle.register(, acct : address, qfee : int, ttl : Chain Registers new oracle answering questions of type `'a` with answers of type `'b`. * The `acct` is the address of the oracle to register (can be the same as the contract). -* `signature` is a signature proving that the contract is allowed to register the account - - the `network id` + `account address` + `contract address` (concatenated as byte arrays) is - [signed](./sophia_features.md#delegation-signature) with the - private key of the account, proving you have the private key of the oracle to be. If the - address is the same as the contract `sign` is ignored and can be left out entirely. +* The [signature](./sophia_features.md#delegation-signature) should be a + serialized structure containing `network id`, `account address`, and + `contract address`. Using the private key of `account address` for signing. + Proving you have the private key of the oracle to be. If the address is the same + as the contract `sign` is ignored and can be left out entirely. * The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle. * The `ttl` is the Time To Live for the oracle in key blocks, either relative to the current key block height (`RelativeTTL(delta)`) or a fixed key block height (`FixedTTL(height)`). @@ -962,7 +970,7 @@ Examples: ``` -#### get_question +#### get\_question ``` Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a ``` @@ -975,12 +983,11 @@ Checks what was the question of query `q` on oracle `o` Oracle.respond(, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit ``` -Responds to the question `q` on `o`. -Unless the contract address is the same as the oracle address the `signature` -(which is an optional, named argument) +Responds to the question `q` on `o`. Unless the contract address is the same +as the oracle address the `signature` (which is an optional, named argument) needs to be provided. Proving that we have the private key of the oracle by -[signing](./sophia_features.md#delegation-signature) -the `network id` + `oracle query id` + `contract address` +[signing](./sophia_features.md#delegation-signature) should be a serialized +structure containing `network id`, `oracle query id`, and `contract address`. #### extend @@ -993,7 +1000,7 @@ Extends TTL of an oracle. * `o` is the oracle being extended * `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value. -#### query_fee +#### query\_fee ``` Oracle.query_fee(o : oracle('a, 'b)) : int ``` @@ -1014,7 +1021,7 @@ Asks the oracle a question. The call fails if the oracle could expire before an answer. -#### get_answer +#### get\_answer ``` Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) ``` From fbaab570f2c84dc452f8377d06e1d1e95c3439a6 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 23 Jan 2024 16:09:37 +0100 Subject: [PATCH 29/41] [Ceres]: Handle Bytes.to_any_size in calldata (#495) * [Ceres] Handle Bytes.to_any_size in calldata * Edit CHANGELOG --- CHANGELOG.md | 2 ++ src/aeso_fcode_to_fate.erl | 2 ++ src/aeso_vm_decode.erl | 4 +++- test/aeso_calldata_tests.erl | 1 + test/contracts/funargs.aes | 2 ++ 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f561e45..078a84d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and `Int.to_bytes`; and adjust `Bytes.concat` to allow both fixed and arbitrary sized byte arrays. - `Chain.network_id` - a function to get hold of the Chain's network id. +- Allowing `Bytes.to_any_size` in calldata creation, to enable creation of arguments + with arbitray size. ### Changed - `Crypto.verify_sig` is changed to have `msg : bytes()`. I.e. the signed data can be of any length (used to be limited to `bytes(32)`/`hash`). diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index e218778..3328d36 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -285,6 +285,8 @@ term_to_fate(_GlobEnv, _Env, {builtin, _, map_empty, []}) -> term_to_fate(GlobEnv, Env, {op, _, map_set, [M, K, V]}) -> Map = term_to_fate(GlobEnv, Env, M), Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)}; +term_to_fate(GlobEnv, Env, {builtin, _, bytes_to_any_size, [Bs]}) -> + term_to_fate(GlobEnv, Env, Bs); term_to_fate(_GlobEnv, _Env, _) -> throw(not_a_fate_value). diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index cbbebd5..f158c8f 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -15,7 +15,7 @@ from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin}; from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; -from_fate({bytes_t, _, any}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin}; +from_fate({bytes_t, _, any}, ?FATE_BYTES(Bin)) -> make_any_bytes(Bin); from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N); from_fate({id, _, "int"}, N) when is_integer(N) -> @@ -187,3 +187,5 @@ make_bits(Set, Zero, I, N) when 0 == N rem 2 -> make_bits(Set, Zero, I, N) -> {app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}. +make_any_bytes(Bin) -> + {app, [], {qid, [], ["Bytes", "to_any_size"]}, [{bytes, [], Bin}]}. diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 88d7bae..3eb5af5 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -85,6 +85,7 @@ compilable_contracts() -> {"funargs", "bitsum", ["Bits.clear(Bits.clear(Bits.all, 4), 2)"]}, %% Order matters for test {"funargs", "bitsum", ["Bits.set(Bits.set(Bits.none, 4), 2)"]}, {"funargs", "read", ["{label = \"question 1\", result = 4}"]}, + {"funargs", "any_bytes", ["Bytes.to_any_size(#0011AA)"]}, {"funargs", "sjutton", ["#0011012003100011012003100011012003"]}, {"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940" "414243444546474849505152535455565758596061626364656667"]}, diff --git a/test/contracts/funargs.aes b/test/contracts/funargs.aes index cd54fa5..32f0f0b 100644 --- a/test/contracts/funargs.aes +++ b/test/contracts/funargs.aes @@ -20,6 +20,8 @@ contract FunctionArguments = entrypoint read(a : answer(int)) = a.result + entrypoint any_bytes(b : bytes()) = b + entrypoint sjutton(b : bytes(17)) = b From aa532046d373715ed51138a46a36a9046014dd2e Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 26 Jan 2024 10:39:06 +0100 Subject: [PATCH 30/41] [Ceres]: Improve documentation of new features --- docs/sophia_features.md | 10 ++++++++-- docs/sophia_stdlib.md | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 728be9e..4103705 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -640,10 +640,16 @@ Sophia arbitrary-sized integers (FATE) also supports the following bitwise opera - arithmetic bitshift left (`x << n`) - arithmetic bitshift right (`x >> n`) +Note: Arithmetic bitshift treats the number as a signed integer (in 2s +complement), and "retains" the topmost bit. I.e. shifting in zeros if the +topmost bit was 0, and ones if it was one. + ## Bit fields -Sophia integers do not support bit arithmetic. Instead there is a separate -type `bits`. See the standard library [documentation](sophia_stdlib.md#bits). +Originally Sophia integers did not support bit arithmetic. Instead we used a +separate type `bits` (see the standard library +[documentation](sophia_stdlib.md#bits)) - it is still provided as an +alternative to bit arithmetic. A bit field can be of arbitrary size (but it is still represented by the corresponding integer, so setting very high bits can be expensive). diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 8bd2b88..86c9317 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -146,7 +146,7 @@ datatype pointee = AccountPt(address) | OraclePt(address) ``` Note: on-chain there is a maximum length enforced for `DataPt`, it is 1024 bytes. -Sophia itself does _not_ enforce this. +Sophia itself does _not_ check for this. #### Functions @@ -893,7 +893,8 @@ Int.to_bytes(n : int, size : int) : bytes() ``` Casts the integer to a byte array with `size` bytes (big endian, truncating if -necessary). +necessary not preserving signedness). I.e. if you try to squeeze `-129` into a +single byte that will be indistinguishable from `127`. ### Map @@ -2474,7 +2475,9 @@ an integer. If the string doesn't contain a valid number `None` is returned. to_bytes(s : string) : bytes() ``` -Converts string into byte array. +Converts string into byte array. String is UTF-8 encoded. I.e. +`String.length(s)` is not guaranteed to be equal to +`Bytes.size(String.to_bytes(s))`. #### sha3 ``` From 44d6982d6659812b3ed03a48a5a71387496f75d3 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 24 Jan 2024 11:42:52 +0100 Subject: [PATCH 31/41] Prepare release v8.0.0-rc1 --- CHANGELOG.md | 19 ++++++++++--------- rebar.config | 2 +- src/aesophia.app.src | 3 +-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 078a84d..2cc9db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [CERES 8.0.0] +## [Unreleased] +### Added +### Changed +### Removed +### Fixed + +## [8.0.0-rc1] ### Added - Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`. - `Int.mulmod` - combined builtin operation for multiplication and modulus. @@ -12,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Address.to_bytes` - convert an address to its binary representation (for hashing, etc.). - Raw data pointers added to AENS. In short we have introduced a new namespace `AENSv2`; they contain types similar to the old `AENS`; `AENS.name` and - `AENS.pointee`, where the latter now has a constructor `DataPt(string)`. All + `AENS.pointee`, where the latter now has a constructor `DataPt(bytes())`. All AENS actions have been moved to `AENSv2`, and `AENSv2.lookup` and `AENSv2.update` consume and produce the new types. The old `AENS` namespace only contains the old datatypes, that can be used to interface existing @@ -31,12 +37,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. -## [Unreleased] -### Added -### Changed -### Removed -### Fixed - ## [7.4.1] ### Changed - Improve how includes with relative paths are resolved during parsing/compilation. Relative @@ -445,7 +445,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.4.1...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.0-rc1...HEAD +[8.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v7.4.1...v8.0.0-rc1 [7.4.1]: https://github.com/aeternity/aesophia/compare/v7.4.0...v7.4.1 [7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0 [7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 diff --git a/rebar.config b/rebar.config index 842f919..256d9c1 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "7.4.1"}, +{relx, [{release, {aesophia, "8.0.0-rc1"}, [aesophia, aebytecode]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index bb97c16..e19a2fa 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,13 +1,12 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.4.1"}, + {vsn, "8.0.0-rc1"}, {registered, []}, {applications, [kernel, stdlib, jsx, syntax_tools, - getopt, aebytecode, eblake2 ]}, From 1387e814f8bdc3266bab749b55a66e552620e576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Sun, 4 Feb 2024 09:24:41 +0100 Subject: [PATCH 32/41] Fix printing FCode with lifted lambdas (#501) --- src/aeso_ast_to_fcode.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 39e3fb5..2e8c841 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -1323,7 +1323,12 @@ lambda_lift(FCode = #{ functions := Funs, state_layout := StateLayout }) -> init_lambda_funs() -> put(?lambda_key, #{}). -spec get_lambda_funs() -> term(). -get_lambda_funs() -> erase(?lambda_key). +get_lambda_funs() -> + Lambdas = erase(?lambda_key), + %% Remove name feed entries and leave only actual functions + maps:filter(fun({fresh, _}, _) -> false; + (_, _) -> true + end, Lambdas). -spec add_lambda_fun(fun_name(), fann(), fun_def()) -> fun_name(). add_lambda_fun(Parent, FAnn, Def) -> From 944ed49f0bbb3f4fce25975c350e5973c3781fae Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 16 Feb 2024 09:56:26 +0100 Subject: [PATCH 33/41] Allow self-qualification (#503) * Properly allow self-qualification * Changelog * Simplify logic as suggested by Ulf --- CHANGELOG.md | 1 + src/aeso_ast_infer_types.erl | 4 +--- src/aeso_syntax_utils.erl | 12 ++++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc9db1..f7c046e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed ### Fixed +- Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`. ## [8.0.0-rc1] ### Added diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ce7bea6..ca8b212 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1161,7 +1161,7 @@ infer_contract(Env0, What, Defs0, Options) -> _ = bind_funs(lists:map(FunBind, Functions), #env{}), FunMap = maps:from_list([ {FunName(Def), Def} || Def <- Functions ]), check_reserved_entrypoints(FunMap), - DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap), + DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Env3#env.namespace, Def) end, FunMap), SCCs = aeso_utils:scc(DepGraph), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), %% Remove namespaces used in the current namespace @@ -2953,7 +2953,6 @@ record_type_name({app_t, _Attrs, RecId, _Args}) when ?is_type_id(RecId) -> record_type_name(RecId) when ?is_type_id(RecId) -> RecId; record_type_name(_Other) -> - %% io:format("~p is not a record type\n", [Other]), {id, [{origin, system}], "not_a_record_type"}. solve_for_uvar(Env, UVar = {uvar, Attrs, _}, Fields0) -> @@ -3562,7 +3561,6 @@ create_type_errors() -> destroy_and_report_type_errors(Env) -> Errors0 = lists:reverse(ets_tab2list(type_errors)), - %% io:format("Type errors now: ~p\n", [Errors0]), ets_delete(type_errors), Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ], aeso_errors:throw(Errors). %% No-op if Errors == [] diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index c9ee23a..689e55d 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -6,7 +6,7 @@ %%%------------------------------------------------------------------- -module(aeso_syntax_utils). --export([used_ids/1, used_types/2, used/1]). +-export([used_ids/1, used_ids/2, used_types/2, used/1]). -record(alg, {zero, plus, scoped}). @@ -110,8 +110,16 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> %% Name dependencies +%% Used ids, top level used_ids(E) -> - [ X || {{term, [X]}, _} <- used(E) ]. + used_ids([], E). + +%% Used ids, top level or in (current) namespace +used_ids(Ns, E) -> + [ lists:last(Xs) || {{term, Xs}, _} <- used(E), in_ns(Xs, Ns) ]. + +in_ns([_], _) -> true; +in_ns(Xs, Ns) -> lists:droplast(Xs) == Ns. used_types([Top] = _CurrentNS, T) -> F = fun({{type, [X]}, _}) -> [X]; From de426a210b1943638e21da88846a8bec9ebe81c6 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 16 Feb 2024 09:57:23 +0100 Subject: [PATCH 34/41] Fix parser/pretty printer for OTP-27 (#502) * In OTP-27 else is made a keyword, so needs quouting * Fix List.aes - remove unnecessary self-qualification * Changelog --- CHANGELOG.md | 1 + priv/stdlib/List.aes | 4 ++-- src/aeso_parser.erl | 10 +++++----- src/aeso_pretty.erl | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7c046e..2eda1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Support for OTP-27 - no changes in behavior. ### Changed ### Removed ### Fixed diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index e0201d0..59bbdf1 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -282,9 +282,9 @@ namespace List = private function asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a)) asc(lt, x, acc, h::t) = - if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t) + if(lt(h, x)) reverse(x::acc) :: monotonic_subs(lt, h::t) else asc(lt, h, x::acc, t) - asc(_, x, acc, []) = [List.reverse(x::acc)] + asc(_, x, acc, []) = [reverse(x::acc)] /** Merges list of sorted lists */ diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 1591078..85bcf95 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -302,7 +302,7 @@ stmt() -> , {switch, keyword(switch), parens(expr()), maybe_block(branch())} , {'if', keyword('if'), parens(expr()), body()} , {elif, keyword(elif), parens(expr()), body()} - , {else, keyword(else), body()} + , {'else', keyword('else'), body()} ])). branch() -> @@ -326,7 +326,7 @@ expr100() -> Expr150 = ?LAZY_P(expr150()), choice( [ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location - , {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)} + , {'if', keyword('if'), parens(Expr100), Expr150, right(tok('else'), Expr100)} , ?RULE(Expr150, optional(right(tok(':'), type())), case _2 of none -> _1; @@ -612,7 +612,7 @@ group_ifs([], Acc) -> group_ifs([{'if', Ann, Cond, Then} | Stmts], Acc) -> {Elses, Rest} = else_branches(Stmts, []), group_ifs(Rest, [build_if(Ann, Cond, Then, Elses) | Acc]); -group_ifs([{else, Ann, _} | _], _) -> +group_ifs([{'else', Ann, _} | _], _) -> fail({Ann, "No matching 'if' for 'else'"}); group_ifs([{elif, Ann, _, _} | _], _) -> fail({Ann, "No matching 'if' for 'elif'"}); @@ -622,14 +622,14 @@ group_ifs([Stmt | Stmts], Acc) -> build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) -> {'if', Ann, Cond, Then, set_ann(format, elif, build_if(Ann1, Cond1, Then1, Elses))}; -build_if(Ann, Cond, Then, [{else, _Ann, Else}]) -> +build_if(Ann, Cond, Then, [{'else', _Ann, Else}]) -> {'if', Ann, Cond, Then, Else}; build_if(Ann, Cond, Then, []) -> {'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}. else_branches([Elif = {elif, _, _, _} | Stmts], Acc) -> else_branches(Stmts, [Elif | Acc]); -else_branches([Else = {else, _, _} | Stmts], Acc) -> +else_branches([Else = {'else', _, _} | Stmts], Acc) -> {lists:reverse([Else | Acc]), Stmts}; else_branches(Stmts, Acc) -> {lists:reverse(Acc), Stmts}. diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 2697288..cb0c05d 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -418,7 +418,7 @@ stmt_p({'if', _, Cond, Then}) -> block_expr(200, beside(text("if"), paren(expr(Cond))), Then); stmt_p({elif, _, Cond, Then}) -> block_expr(200, beside(text("elif"), paren(expr(Cond))), Then); -stmt_p({else, Else}) -> +stmt_p({'else', Else}) -> HideGenerated = not show_generated(), case aeso_syntax:get_ann(origin, Else) of system when HideGenerated -> empty(); @@ -533,5 +533,5 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) -> elif -> get_elifs(Else, [{elif, Ann, Cond, Then} | Elifs]); _ -> {lists:reverse(Elifs), If} end; -get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}. +get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {'else', Else}}. From 31301911a27cd621a082162b5e91c1efb4699b05 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 16 Feb 2024 19:11:08 +0300 Subject: [PATCH 35/41] Docs: Mention the unit type in the literals section (#504) --- docs/sophia_features.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 4103705..13e47cb 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -558,6 +558,7 @@ Sophia has the following types: ## Literals | Type | Constant/Literal example(s) | | ---------- | ------------------------------- | +| unit | () | | int | `-1`, `2425`, `4598275923475723498573485768` | | address | `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt` | | bool | `true`, `false` | From 51bae6173639f8ba10065b271acf6daf4e705aa1 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 10 Apr 2024 16:34:29 +0200 Subject: [PATCH 36/41] Add signature literals (#505) * Add signature literals + handle system alias types * Add tests for signature literals + encode/decode * Add to CHANGELOG * Add in documentation * Additional documentation --- CHANGELOG.md | 4 ++++ docs/sophia_features.md | 2 +- docs/sophia_syntax.md | 2 ++ src/aeso_aci.erl | 3 ++- src/aeso_ast_infer_types.erl | 14 +++++++++-- src/aeso_ast_to_fcode.erl | 2 ++ src/aeso_compiler.erl | 25 ++++++++++++------- src/aeso_fcode_to_fate.erl | 1 + src/aeso_parser.erl | 2 +- src/aeso_pretty.erl | 3 ++- src/aeso_syntax.erl | 1 + src/aeso_vm_decode.erl | 3 +++ test/aeso_calldata_tests.erl | 1 + test/aeso_encode_decode_tests.erl | 40 +++++++++++++++++++++++++++++++ test/contracts/ceres.aes | 3 ++- test/contracts/funargs.aes | 2 ++ 16 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 test/aeso_encode_decode_tests.erl diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eda1e2..43c877b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Support for OTP-27 - no changes in behavior. +- Signature literals `sg_...` - they have type `signature` (which is an alias for `bytes(64)`). ### Changed +- System aliases are handled explicitly when converting to a Sophia value, this is only + observable for `signature` where a value of type `signature` is now represented as a + (new) signature literal. ### Removed ### Fixed - Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`. diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 13e47cb..25abd28 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -573,7 +573,7 @@ Sophia has the following types: | state | `state{ owner = Call.origin, magic_key = #a298105f }` | | event | `EventX(0, "Hello")` | | hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | -| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | +| signature | `sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj`, `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | | Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` | | oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` | | oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` | diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index 712c9ce..5b975ab 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -30,6 +30,7 @@ interface main using as for hiding - `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix - `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix - `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix +- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix Valid string escape codes are @@ -239,6 +240,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + | Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%' | AccountAddress | ContractAddress // Chain identifiers | OracleAddress | OracleQueryId // Chain identifiers + | Signature // Signature | '???' // Hole expression 1 + ??? Generator ::= Pattern '<-' Expr // Generator diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 5cf5aff..e4e6d2f 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -198,7 +198,8 @@ encode_expr({bytes, _, B}) -> <> = B, list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N]))); encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id; - Lit == contract_pubkey; Lit == account_pubkey -> + Lit == contract_pubkey; Lit == account_pubkey; + Lit == signature -> aeser_api_encoder:encode(Lit, L); encode_expr({app, _, {'-', _}, [{int, _, N}]}) -> encode_expr({int, [], -N}); diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ca8b212..b78ea38 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1935,6 +1935,8 @@ infer_expr(_Env, Body={bytes, As, Bin}) -> {typed, As, Body, {bytes_t, As, byte_size(Bin)}}; infer_expr(_Env, Body={account_pubkey, As, _}) -> {typed, As, Body, {id, As, "address"}}; +infer_expr(_Env, Body={signature, As, Bin}) when byte_size(Bin) == 64 -> + {typed, As, Body, {bytes_t, As, 64}}; infer_expr(_Env, Body={oracle_pubkey, As, _}) -> Q = fresh_uvar(As), R = fresh_uvar(As), @@ -2173,6 +2175,8 @@ check_valid_const_expr({bytes, _, _}) -> true; check_valid_const_expr({account_pubkey, _, _}) -> true; +check_valid_const_expr({signature, _, _}) -> + true; check_valid_const_expr({oracle_pubkey, _, _}) -> true; check_valid_const_expr({oracle_query_id, _, _}) -> @@ -3060,7 +3064,8 @@ unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) -> %% Like the case above, but for types without parameters. when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, 0) end), - UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), + UnfoldSysAlias = not proplists:get_value(not_unfold_system_alias_types, Options, false), + UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false), case lookup_type(Env, Id) of {_, {_, {[], {record_t, Fields}}}} when UnfoldRecords -> @@ -3068,7 +3073,12 @@ unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) -> {_, {_, {[], {variant_t, Constrs}}}} when UnfoldVariants -> {variant_t, unfold_types_in_type(Env, Constrs, Options)}; {_, {_, {[], {alias_t, Type1}}}} -> - unfold_types_in_type(Env, Type1, Options); + case aeso_syntax:get_ann(Type1) of + [{origin, system}] when not UnfoldSysAlias -> + Id; + _ -> + unfold_types_in_type(Env, Type1, Options) + end; _ -> %% Not a record type, or ill-formed record type Id diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 2e8c841..4ad2af9 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -55,6 +55,7 @@ | {contract_pubkey, binary()} | {oracle_pubkey, binary()} | {oracle_query_id, binary()} + | {signature, binary()} | {bool, false | true} | {contract_code, string()} %% for CREATE, by name | {typerep, ftype()}. @@ -599,6 +600,7 @@ expr_to_fcode(_Env, _Type, {char, Ann, N}) -> {lit, to_fann(Ann), {in expr_to_fcode(_Env, _Type, {bool, Ann, B}) -> {lit, to_fann(Ann), {bool, B}}; expr_to_fcode(_Env, _Type, {string, Ann, S}) -> {lit, to_fann(Ann), {string, S}}; expr_to_fcode(_Env, _Type, {account_pubkey, Ann, K}) -> {lit, to_fann(Ann), {account_pubkey, K}}; +expr_to_fcode(_Env, _Type, {signature, Ann, K}) -> {lit, to_fann(Ann), {signature, K}}; expr_to_fcode(_Env, _Type, {contract_pubkey, Ann, K}) -> {lit, to_fann(Ann), {contract_pubkey, K}}; expr_to_fcode(_Env, _Type, {oracle_pubkey, Ann, K}) -> {lit, to_fann(Ann), {oracle_pubkey, K}}; expr_to_fcode(_Env, _Type, {oracle_query_id, Ann, K}) -> {lit, to_fann(Ann), {oracle_query_id, K}}; diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 5968081..1cc72c3 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -228,10 +228,13 @@ encode_value(Contract0, Type, Value, Options) -> decode_value(Contract0, Type, FateValue, Options) -> case add_extra_call(Contract0, {type, Type}, Options) of {ok, CallName, Code} -> - #{ unfolded_typed_ast := TypedAst - , type_env := TypeEnv} = Code, + #{ folded_typed_ast := TypedAst + , type_env := TypeEnv} = Code, {ok, _, Type0} = get_decode_type(CallName, TypedAst), - Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), + Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, + [ unfold_record_types + , unfold_variant_types + , not_unfold_system_alias_types ]), fate_data_to_sophia_value(Type0, Type1, FateValue); Err = {error, _} -> Err @@ -272,7 +275,7 @@ insert_call_function(Ast, Code, Call, {type, Type}) -> [ Code, "\n\n", lists:duplicate(Ind, " "), - "entrypoint ", Call, "(val : ", Type, ") = val\n" + "entrypoint ", Call, "(val : ", Type, ") : ", Type, " = val\n" ]). -spec insert_init_function(string(), options()) -> string(). @@ -311,9 +314,12 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> Options = [no_code | Options0], try Code = string_to_code(ContractString, Options), - #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code, + #{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code, {ok, _, Type0} = get_decode_type(FunName, TypedAst), - Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), + Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, + [ unfold_record_types + , unfold_variant_types + , not_unfold_system_alias_types]), fate_data_to_sophia_value(Type0, Type, Data) catch @@ -360,14 +366,17 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> Options = [no_code | Options0], try Code = string_to_code(ContractString, Options), - #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code, + #{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code, {ok, Args, _} = get_decode_type(FunName, TypedAst), GetType = fun({typed, _, _, T}) -> T; (T) -> T end, ArgTypes = lists:map(GetType, Args), Type0 = {tuple_t, [], ArgTypes}, %% user defined data types such as variants needed to match against - Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), + Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, + [ unfold_record_types + , unfold_variant_types + , not_unfold_system_alias_types]), case aeb_fate_abi:decode_calldata(FunName, Calldata) of {ok, FateArgs} -> try diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 3328d36..e0fbe16 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -235,6 +235,7 @@ lit_to_fate(Env, L) -> {bytes, B} -> aeb_fate_data:make_bytes(B); {bool, B} -> aeb_fate_data:make_boolean(B); {account_pubkey, K} -> aeb_fate_data:make_address(K); + {signature, S} -> aeb_fate_data:make_bytes(S); {contract_pubkey, K} -> aeb_fate_data:make_contract(K); {oracle_pubkey, K} -> aeb_fate_data:make_oracle(K); {oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H); diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 85bcf95..24e154d 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -525,7 +525,7 @@ id_or_addr() -> ?RULE(id(), parse_addr_literal(_1)). parse_addr_literal(Id = {id, Ann, Name}) -> - case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of + case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_", "sg_"]) of false -> Id; true -> try aeser_api_encoder:decode(list_to_binary(Name)) of diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index cb0c05d..effe80f 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -387,7 +387,8 @@ expr_p(_, {Type, _, Bin}) when Type == account_pubkey; Type == contract_pubkey; Type == oracle_pubkey; - Type == oracle_query_id -> + Type == oracle_query_id; + Type == signature -> text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); expr_p(_, {string, _, <<>>}) -> text("\"\""); expr_p(_, {string, _, S}) -> diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 1e8ee2d..cfa79b5 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -100,6 +100,7 @@ | {contract_pubkey, ann(), binary()} | {oracle_pubkey, ann(), binary()} | {oracle_query_id, ann(), binary()} + | {signature, ann(), binary()} | {string, ann(), binary()} | {char, ann(), integer()}. diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index f158c8f..b0e2f78 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -12,6 +12,9 @@ -spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr(). from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin}; +from_fate({id, _, "signature"}, ?FATE_BYTES(Bin)) -> {signature, [], Bin}; +from_fate({id, _, "hash"}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin}; +from_fate({id, _, "unit"}, ?FATE_UNIT) -> {tuple, [], []}; from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 3eb5af5..a49efd6 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -117,6 +117,7 @@ compilable_contracts() -> {"funargs", "chain_base_tx", ["Chain.NameRevokeTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]}, {"funargs", "chain_base_tx", ["Chain.NameTransferTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, #ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]}, {"funargs", "chain_base_tx", ["Chain.GAAttachTx"]}, + {"funargs", "sig", ["sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj"]}, {"variant_types", "init", []}, {"basic_auth", "init", []}, {"address_literals", "init", []}, diff --git a/test/aeso_encode_decode_tests.erl b/test/aeso_encode_decode_tests.erl new file mode 100644 index 0000000..898842f --- /dev/null +++ b/test/aeso_encode_decode_tests.erl @@ -0,0 +1,40 @@ +-module(aeso_encode_decode_tests). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(EMPTY, "contract C =\n entrypoint f() = true"). + +encode_decode_test_() -> + [ {lists:flatten(io_lib:format("Testing encode-decode roundtrip for ~p : ~p", [Value, {EType, DType}])), + fun() -> + {ok, SerRes} = aeso_compiler:encode_value(?EMPTY, EType, Value, []), + {ok, Expr} = aeso_compiler:decode_value(?EMPTY, DType, SerRes, []), + Value2 = prettypr:format(aeso_pretty:expr(Expr)), + ?assertEqual(Value, Value2) + end} || {Value, EType, DType} <- test_data() ]. + +test_data() -> + lists:map(fun({V, T}) -> {V, T, T}; + ({V, T1, T2}) -> {V, T1, T2} end, data()). + +data() -> + [ {"42", "int"} + , {"- 42", "int"} + , {"true", "bool"} + , {"ak_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ", "address"} + , {"ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ", "C"} + , {"Some(42)", "option(int)"} + , {"None", "option(int)"} + , {"(true, 42)", "bool * int"} + , {"{[1] = true, [3] = false}", "map(int, bool)"} + , {"()", "unit"} + , {"#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "hash"} + , {"#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "bytes(32)"} + , {"sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj", "signature"} + , {"sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj", "bytes(64)", "signature"} + , {"#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637", "bytes(64)"} + , {"#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637", "signature", "bytes(64)"} + ]. + diff --git a/test/contracts/ceres.aes b/test/contracts/ceres.aes index 832334f..7e869cf 100644 --- a/test/contracts/ceres.aes +++ b/test/contracts/ceres.aes @@ -11,4 +11,5 @@ contract C = let i = Int.mulmod(a, b, h) let j = Crypto.poseidon(i, a) let k : bytes(32) = Address.to_bytes(Call.origin) - (a bor b band c bxor a << bnot b >> a, k) + let l = sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj + (a bor b band c bxor a << bnot b >> a, k, l) diff --git a/test/contracts/funargs.aes b/test/contracts/funargs.aes index 32f0f0b..5d16112 100644 --- a/test/contracts/funargs.aes +++ b/test/contracts/funargs.aes @@ -59,3 +59,5 @@ contract FunctionArguments = entrypoint chain_ga_meta_tx(tx : Chain.ga_meta_tx) = true entrypoint chain_paying_for_tx(tx : Chain.paying_for_tx) = true entrypoint chain_base_tx(tx : Chain.base_tx) = true + + entrypoint sig(sg : signature) = true From ffdd4ecf26d7f08ed89b0f430ca4dc19dbb401bd Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 18 Apr 2024 09:15:34 +0200 Subject: [PATCH 37/41] Prepare release v8.0.0 (#506) --- CHANGELOG.md | 19 +++++++++---------- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c877b..69fb8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Support for OTP-27 - no changes in behavior. -- Signature literals `sg_...` - they have type `signature` (which is an alias for `bytes(64)`). ### Changed -- System aliases are handled explicitly when converting to a Sophia value, this is only - observable for `signature` where a value of type `signature` is now represented as a - (new) signature literal. ### Removed -### Fixed -- Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`. -## [8.0.0-rc1] +## [8.0.0] ### Added - Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`. - `Int.mulmod` - combined builtin operation for multiplication and modulus. @@ -37,9 +30,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Chain.network_id` - a function to get hold of the Chain's network id. - Allowing `Bytes.to_any_size` in calldata creation, to enable creation of arguments with arbitray size. +- Signature literals `sg_...` - they have type `signature` (which is an alias for `bytes(64)`). +- Support for OTP-27 - no changes in behavior. ### Changed - `Crypto.verify_sig` is changed to have `msg : bytes()`. I.e. the signed data can be of any length (used to be limited to `bytes(32)`/`hash`). +- System aliases are handled explicitly when converting to a Sophia value, this is only + observable for `signature` where a value of type `signature` is now represented as a + (new) signature literal. +- Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`. ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. @@ -451,8 +450,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.0-rc1...HEAD -[8.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v7.4.1...v8.0.0-rc1 +[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.0...HEAD +[8.0.0]: https://github.com/aeternity/aesophia/compare/v7.4.1...v8.0.0 [7.4.1]: https://github.com/aeternity/aesophia/compare/v7.4.0...v7.4.1 [7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0 [7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 diff --git a/rebar.config b/rebar.config index 256d9c1..f9f611f 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "8.0.0-rc1"}, +{relx, [{release, {aesophia, "8.0.0"}, [aesophia, aebytecode]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index e19a2fa..b9124de 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "8.0.0-rc1"}, + {vsn, "8.0.0"}, {registered, []}, {applications, [kernel, From 83dcc6dbc488cf8b6b264396d1337499b6bf5611 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 24 May 2024 15:46:21 +0300 Subject: [PATCH 38/41] Upgrade to the latest aebytecode to fix C warnings (#509) --- CHANGELOG.md | 7 ++++++- rebar.config | 4 ++-- rebar.lock | 6 +++--- src/aesophia.app.src | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69fb8f8..5b6cf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed +## [8.0.1] +### Changed +- Upgrade aebytecode to v3.4.1 to fix C warnings + ## [8.0.0] ### Added - Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`. @@ -450,7 +454,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.0...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.1...HEAD +[8.0.1]: https://github.com/aeternity/aesophia/compare/v8.0.0...v8.0.1 [8.0.0]: https://github.com/aeternity/aesophia/compare/v7.4.1...v8.0.0 [7.4.1]: https://github.com/aeternity/aesophia/compare/v7.4.0...v7.4.1 [7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0 diff --git a/rebar.config b/rebar.config index f9f611f..ed141dc 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.4.0"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.4.1"}}} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} ]}. @@ -13,7 +13,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "8.0.0"}, +{relx, [{release, {aesophia, "8.0.1"}, [aesophia, aebytecode]}, {dev_mode, true}, diff --git a/rebar.lock b/rebar.lock index 58ce514..8a19f04 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,11 +1,11 @@ {"1.2.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"009e0361922037f978f9c0ef357d4d1be8559928"}}, + {ref,"6bd6f82c70d800950ea1a2c70c364a4181ff5291"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", - {ref,"177bf604b2a05e940f92cf00e96e6e269e708245"}}, + {ref,"b26e6d105424748ba1c27917267b7cff07f37802"}}, 1}, {<<"base58">>, {git,"https://github.com/aeternity/erl-base58.git", @@ -14,7 +14,7 @@ {<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0}, {<<"enacl">>, {git,"https://github.com/aeternity/enacl.git", - {ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}}, + {ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}, 2}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1}, {<<"jsx">>, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index b9124de..926f26a 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "8.0.0"}, + {vsn, "8.0.1"}, {registered, []}, {applications, [kernel, From 46a307432ff43d92021ae1eab54daad119b4fbd2 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Mon, 12 Aug 2024 11:02:29 +0400 Subject: [PATCH 39/41] Docs: Fix return value type in Auction example (#511) --- docs/sophia_stdlib.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 86c9317..f194841 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -687,8 +687,8 @@ Example usage: ``` payable contract interface Auction = entrypoint init : (int, string) => void - stateful payable entrypoint buy : (int) => () - stateful entrypoint sell : (int) => () + stateful payable entrypoint buy : (int) => unit + stateful entrypoint sell : (int) => unit main contract Market = type state = list(Auction) From 16308a7840f484245b14b437b08f1954d313d65a Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Mon, 12 Aug 2024 11:02:56 +0400 Subject: [PATCH 40/41] Docs: Move `poseidon` outside of `create` (#510) --- docs/sophia_stdlib.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index f194841..8cbcf1f 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -614,14 +614,6 @@ charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in `Contract.balance`, however. -#### poseidon -``` -Crypto.poseidon(x1 : int, x2 : int) : int -``` - -Hash two integers (in the scalar field of BLS12-381) to another integer (in the scalar -field of BLS12-281). This is a ZK/SNARK-friendly hash function. - The type `'c` must be instantiated with a contract. @@ -748,6 +740,15 @@ Chain.network\_id : string The network id of the chain. +#### poseidon +``` +Crypto.poseidon(x1 : int, x2 : int) : int +``` + +Hash two integers (in the scalar field of BLS12-381) to another integer (in the scalar +field of BLS12-281). This is a ZK/SNARK-friendly hash function. + + ##### spend ``` Chain.spend(to : address, amount : int) : unit From 927cd4259235718523c87b55d3c6dd3499c512ea Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 26 Aug 2024 13:34:02 +0200 Subject: [PATCH 41/41] Add check for number of type variables in type signature (#512) * Fix tvar string generation * Add check for number of tvars in a single type signature The number of tvars is limited by serialization (u8) to 255 * Added a comment in CHANGELOG * Docs fixes * Adding docs about the limitation on number of tvars * Limit is 256, not 255 * Update CHANGELOG.md Co-authored-by: Gaith Hallak --------- Co-authored-by: Gaith Hallak --- CHANGELOG.md | 2 ++ docs/sophia_features.md | 5 +++ docs/sophia_syntax.md | 8 ++--- src/aeso_ast_infer_types.erl | 24 ++++++++++++- test/aeso_compiler_tests.erl | 4 +++ test/contracts/too_many_tvars.aes | 60 +++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 test/contracts/too_many_tvars.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6cf3c..8e35c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added a check for number of type variables in a type signature; it is serialized using 8 bits, + so the upper limit is 256. ### Changed ### Removed diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 25abd28..dfbfe59 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -295,6 +295,11 @@ of `A`. - When a user-defined type `t('a)` is invariant in `'a`, then `t(A)` can never be a subtype of `t(B)`. +#### Type variable limitation + +Because of how FATE represents types as values there is a fixed upper limit (256) +of type variables that can be used in a single type signature. + ## Mutable state Sophia does not have arbitrary mutable state, but only a limited form of state diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index 5b975ab..6a9ae86 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -267,11 +267,11 @@ UnOp ::= '-' | '!' | 'bnot' | Operators | Type | --- | --- | `-` `+` `*` `/` `mod` `^` | arithmetic operators -| `!` `&&` `||` | logical operators +| `!` `&&` `\|\|` | logical operators | `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators | `==` `!=` `<` `>` `=<` `>=` | comparison operators | `::` `++` | list operators -| `|>` | functional operators +| `\|>` | functional operators ## Operator precedence @@ -291,5 +291,5 @@ In order of highest to lowest precedence. | `bxor` | left | `bor` | left | `&&` | right -| `||` | right -| `|>` | left +| `\|\|` | right +| `\|>` | left diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index b78ea38..d6f8e5b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1760,8 +1760,27 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> end. print_typesig({Name, TypeSig}) -> + assert_tvars(Name, TypeSig), ?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]). +assert_tvars(Name, TS) -> + TVars = assert_tvars_(TS, #{}), + case maps:size(TVars) > 256 of + true -> + type_error({too_many_tvars, Name, TS}); + false -> + ok + end. + +assert_tvars_({tvar, _, TV}, TVars) -> + TVars#{TV => ok}; +assert_tvars_(T, TVars) when is_tuple(T) -> + assert_tvars_(tuple_to_list(T), TVars); +assert_tvars_(Ts, TVars) when is_list(Ts) -> + lists:foldl(fun(T, TVars1) -> assert_tvars_(T, TVars1) end, TVars, Ts); +assert_tvars_(_, TVars) -> + TVars. + arg_type(ArgAnn, {id, Ann, "_"}) -> case aeso_syntax:get_ann(origin, Ann, user) of system -> fresh_uvar(ArgAnn); @@ -3389,7 +3408,7 @@ instantiate1(X) -> integer_to_tvar(X) when X < 26 -> [$a + X]; integer_to_tvar(X) -> - [integer_to_tvar(X div 26)] ++ [$a + (X rem 26)]. + integer_to_tvar(X div 26 - 1) ++ [$a + (X rem 26)]. %% Warnings @@ -3776,6 +3795,9 @@ 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({too_many_tvars, Name, {type_sig, Pos, _, _, _, _}}) -> + Msg = io_lib:format("Too many type variables (max 256) in definition of `~s`", [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)]), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 3bd922d..c13b180 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -1302,6 +1302,10 @@ failing_contracts() -> <> ]) + , ?TYPE_ERROR(too_many_tvars, + [<> + ]) ]. validation_test_() -> diff --git a/test/contracts/too_many_tvars.aes b/test/contracts/too_many_tvars.aes new file mode 100644 index 0000000..4fa78ce --- /dev/null +++ b/test/contracts/too_many_tvars.aes @@ -0,0 +1,60 @@ +contract C = + entrypoint too_many( + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _)) = 0 + + entrypoint not_too_many( + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _)) = 0