From 9e85658ca41a3a5f72987426e7afd816f4f44fd2 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Wed, 25 Aug 2021 12:12:00 +0300 Subject: [PATCH 01/23] Add case guards to parser --- src/aeso_parser.erl | 7 ++++++- src/aeso_syntax.erl | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index c33e47c..9ea8286 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -282,9 +282,14 @@ stmt() -> , {else, keyword(else), body()} ])). -branch() -> +branch() -> choice(unguarded_branch(), guarded_branch()). + +unguarded_branch() -> ?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, _3}). +guarded_branch() -> + ?RULE(pattern(), tok('|'), expr(), keyword('=>'), body(), {'case', _4, _1, _3, _5}). + pattern() -> ?LET_P(E, expr(), parse_pattern(E)). diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 1873d8e..b22f97d 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -145,7 +145,8 @@ -type stmt() :: letbind() | expr(). --type alt() :: {'case', ann(), pat(), expr()}. +-type alt() :: {'case', ann(), pat(), expr()} + | {'case', ann(), pat(), expr(), expr()}. -type lvalue() :: nonempty_list(elim()). -- 2.30.2 From f437ee564e865695dcb0bbd3e3760adf45dcaea7 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 30 Aug 2021 11:41:12 +0300 Subject: [PATCH 02/23] Add pattern guards to infer types and fcode generation --- src/aeso_ast_infer_types.erl | 23 ++++++++++++++++++++--- src/aeso_ast_to_fcode.erl | 21 ++++++++++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 1e6baf8..1c4a068 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1616,8 +1616,16 @@ infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> infer_expr(Env, {switch, Attrs, Expr, Cases}) -> NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr), SwitchType = fresh_uvar(Attrs), - NewCases = [infer_case(Env, As, Pattern, ExprType, Branch, SwitchType) - || {'case', As, Pattern, Branch} <- Cases], + ApplyInferCase = + fun(Case) -> + case Case of + {'case', As, Pattern, Branch} -> + infer_case(Env, As, Pattern, ExprType, Branch, SwitchType); + {'case', As, Pattern, Guard, Branch} -> + infer_case(Env, As, Pattern, Guard, ExprType, Branch, SwitchType) + end + end, + NewCases = lists:map(ApplyInferCase, Cases), {typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType}; infer_expr(Env, {record, Attrs, Fields}) -> RecordType = fresh_uvar(Attrs), @@ -1837,10 +1845,19 @@ infer_pattern(Env, Pattern) -> {NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}. infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) -> + infer_case(Env, Attrs, Pattern, none, ExprType, Branch, SwitchType). + +infer_case(Env, Attrs, Pattern, Guard, ExprType, Branch, SwitchType) -> {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType), unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}), - {'case', Attrs, NewPattern, NewBranch}. + case Guard of + none -> + {'case', Attrs, NewPattern, NewBranch}; + _ -> + NewGuard = check_expr(NewEnv, Guard, {id, Attrs, "bool"}), + {'case', Attrs, NewPattern, NewGuard, NewBranch} + end. %% NewStmts = infer_block(Env, Attrs, Stmts, BlockType) infer_block(_Env, Attrs, [], BlockType) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 69378d5..b5a43b8 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -683,9 +683,9 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) -> expr_to_fcode(Env, Else)); %% Switch -expr_to_fcode(Env, _, {switch, _, Expr = {typed, _, E, Type}, Alts}) -> +expr_to_fcode(Env, _, S = {switch, _, Expr = {typed, _, E, Type}, Alts}) -> Switch = fun(X) -> - {switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)} + {switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)} end, case E of {id, _, X} -> Switch(X); @@ -863,9 +863,9 @@ is_first_order(_) -> true. %% -- Pattern matching -- --spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit(). -alts_to_fcode(Env, Type, X, Alts) -> - FAlts = [alt_to_fcode(Env, Alt) || Alt <- Alts], +-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit(). +alts_to_fcode(Env, Type, X, Alts, Switch) -> + FAlts = remove_guards(Env, Alts, Switch), split_tree(Env, [{X, Type}], FAlts). %% Intermediate format before case trees (fcase() and fsplit()). @@ -879,6 +879,17 @@ alts_to_fcode(Env, Type, X, Alts) -> | {con, arities(), tag(), [fpat()]} | {assign, fpat(), fpat()}. +remove_guards(_Env, [], _Switch) -> + []; +remove_guards(Env, [Alt = {'case', _, _, _} | Rest], Switch) -> + [alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)]; +remove_guards(Env, [{'case', _, Pat, Guard, Body} | Rest], {switch, Ann, Expr, _}) -> + FPat = pat_to_fcode(Env, Pat), + FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, Rest}), + FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard), + FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body), + [{'case', [FPat], make_if(FGuard, FBody, FSwitch)}]. + %% %% Invariant: the number of variables matches the number of patterns in each falt. -spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit(). split_tree(_Env, _Vars, []) -> -- 2.30.2 From 4bfae33945eb453703cf6fc6bc415406bd659375 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 2 Sep 2021 12:41:23 +0300 Subject: [PATCH 03/23] Add functions guards --- src/aeso_ast_infer_types.erl | 103 ++++++++++++++++++++++++++++------- src/aeso_ast_to_fcode.erl | 16 +++++- src/aeso_parse_lib.hrl | 14 +++-- src/aeso_parser.erl | 10 +++- src/aeso_syntax.erl | 3 +- 5 files changed, 117 insertions(+), 29 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 1c4a068..3910cc3 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -287,6 +287,13 @@ bind_contract({Contract, Ann, Id, Contents}, Env) || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents, Name =/= "init" ] ++ + [ {field_t, AnnF, Entrypoint, + contract_call_type( + {fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT}) + } + || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, _Guard, {typed, _, _, RetT}} <- Contents, + Name =/= "init" + ] ++ %% Predefined fields [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++ [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, @@ -294,6 +301,9 @@ bind_contract({Contract, Ann, Id, Contents}, Env) case [ [ArgT || {typed, _, _, ArgT} <- Args] || {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents, aeso_syntax:get_ann(entrypoint, AnnF, false)] + ++ [ [ArgT || {typed, _, _, ArgT} <- Args] + || {letfun, AnnF, {id, _, "init"}, Args, _, _, _} <- Contents, + aeso_syntax:get_ann(entrypoint, AnnF, false)] ++ [ Args || {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents, aeso_syntax:get_ann(entrypoint, AnnF, false)] @@ -895,12 +905,13 @@ infer_contract(Env0, What, Defs0, Options) -> end, destroy_and_report_type_errors(Env0), Env = Env0#env{ what = What }, - Kind = fun({type_def, _, _, _, _}) -> type; - ({letfun, _, _, _, _, _}) -> function; - ({fun_clauses, _, _, _, _}) -> function; - ({fun_decl, _, _, _}) -> prototype; - ({using, _, _, _, _}) -> using; - (_) -> unexpected + Kind = fun({type_def, _, _, _, _}) -> type; + ({letfun, _, _, _, _, _}) -> function; + ({letfun, _, _, _, _, _, _}) -> function; + ({fun_clauses, _, _, _, _}) -> function; + ({fun_decl, _, _, _}) -> prototype; + ({using, _, _, _, _}) -> using; + (_) -> unexpected end, Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end, OldUsedNamespaces = Env#env.used_namespaces, @@ -918,8 +929,9 @@ infer_contract(Env0, What, Defs0, Options) -> Env3 = bind_funs(ProtoSigs, Env2), Functions = Get(function, Defs), %% Check for duplicates in Functions (we turn it into a map below) - FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; - ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, + FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; + ({letfun, Ann, {id, _, Fun}, _, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; + ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, FunName = fun(Def) -> {Name, _} = FunBind(Def), Name end, _ = bind_funs(lists:map(FunBind, Functions), #env{}), FunMap = maps:from_list([ {FunName(Def), Def} || Def <- Functions ]), @@ -946,7 +958,8 @@ process_blocks(Decls) -> process_block(_, []) -> []; process_block(_, [Decl]) -> [Decl]; process_block(_Ann, [Decl | Decls]) -> - IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1; + IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1; + ({letfun, _, {id, _, Name1}, _, _, _, _}) -> Name == Name1; (_) -> false end end, case Decl of {fun_decl, Ann1, Id = {id, _, Name}, Type} -> @@ -954,6 +967,10 @@ process_block(_Ann, [Decl | Decls]) -> [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], [{fun_clauses, Ann1, Id, Type, Clauses}]; {letfun, Ann1, Id = {id, _, Name}, _, _, _} -> + {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), + [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], + [{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}]; + {letfun, Ann1, Id = {id, _, Name}, _, _, _, _} -> {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], [{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}] @@ -1086,7 +1103,11 @@ check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest]) [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of {true, []} -> type_error({contract_has_no_entrypoints, Con}); _ when IsInterface -> - case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of + GetAnnId = fun({letfun, AnnF, Id, _, _, _}) -> {true, {AnnF, Id}}; + ({letfun, AnnF, Id, _, _, _, _}) -> {true, {AnnF, Id}}; + (_) -> false + end, + case lists:filtermap(GetAnnId, Decls) of [{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id}); [] -> ok end; @@ -1323,8 +1344,9 @@ typesig_to_fun_t({type_sig, Ann, _Constr, Named, Args, Res}) -> infer_letrec(Env, Defs) -> create_constraints(), - Funs = lists:map(fun({letfun, _, {id, Ann, Name}, _, _, _}) -> {Name, fresh_uvar(Ann)}; - ({fun_clauses, _, {id, Ann, Name}, _, _}) -> {Name, fresh_uvar(Ann)} + Funs = lists:map(fun({letfun, _, {id, Ann, Name}, _, _, _}) -> {Name, fresh_uvar(Ann)}; + ({letfun, _, {id, Ann, Name}, _, _, _, _}) -> {Name, fresh_uvar(Ann)}; + ({fun_clauses, _, {id, Ann, Name}, _, _}) -> {Name, fresh_uvar(Ann)} end, Defs), ExtendEnv = bind_funs(Funs, Env), Inferred = @@ -1354,6 +1376,9 @@ infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) -> end || ClauseSig <- Sigs ], {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, Clauses1)}; infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _}) -> + {{Name, Sig}, Clause} = infer_letfun1(Env, LetFun), + {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}; +infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _, _}) -> {{Name, Sig}, Clause} = infer_letfun1(Env, LetFun), {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}. @@ -1366,13 +1391,25 @@ infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, B NamedArgs = [], TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType}, {{Name, TypeSig}, - {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewBody}}. + {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewBody}}; +infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Guard, Body}) -> + Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), + current_function = Fun }, + {NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}), + NewGuard = check_expr(NewEnv, Guard, {id, Attrib, "bool"}), + ExpectedType = check_type(Env, arg_type(NameAttrib, What)), + NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType), + NamedArgs = [], + TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType}, + {{Name, TypeSig}, + {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuard, NewBody}}. desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> NeedDesugar = case Clauses of - [{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); - _ -> true + [{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); + [{letfun, _, _, As, _, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); + _ -> true end, case NeedDesugar of false -> [Clause] = Clauses, Clause; @@ -1383,11 +1420,14 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> Tuple = fun([X]) -> X; (As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}} end, + ToCase = fun({letfun, AnnC, _, ArgsC, _, Body}) -> {true, {'case', AnnC, Tuple(ArgsC), Body}}; + ({letfun, AnnC, _, ArgsC, _, Guard, Body}) -> {true, {'case', AnnC, Tuple(ArgsC), Guard, Body}}; + (_) -> false + end, {letfun, Ann, Fun, Args, RetType, {typed, NoAnn, - {switch, NoAnn, Tuple(Args), - [ {'case', AnnC, Tuple(ArgsC), Body} - || {letfun, AnnC, _, ArgsC, _, Body} <- Clauses ]}, RetType}} + {switch, NoAnn, Tuple(Args), lists:filtermap(ToCase, Clauses)}, + RetType}} end. print_typesig({Name, TypeSig}) -> @@ -1449,7 +1489,11 @@ check_state_dependencies(Env, Defs) -> SetState = Top ++ ["put"], Init = Top ++ ["init"], UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end, - Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ], + GetFun = fun(F = {letfun, _, {id, _, Name}, _, _, _}) -> {true, {Top ++ [Name], F}}; + (F = {letfun, _, {id, _, Name}, _, _, _, _}) -> {true, {Top ++ [Name], F}}; + (_) -> false + end, + Funs = lists:filtermap(GetFun, Defs), Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]), case maps:get(Init, Deps, false) of false -> ok; %% No init, so nothing to check @@ -1577,6 +1621,17 @@ infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) , {list_comp, AsLC, TypedYield, [LetFun|TypedRest]} , ResType }; +infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _, _}|Rest]}) -> + {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), + FunT = typesig_to_fun_t(TypeSig), + NewE = bind_var({id, AsLF, Name}, FunT, Env), + {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = + infer_expr(NewE, {list_comp, AsLC, Yield, Rest}), + { typed + , AsLC + , {list_comp, AsLC, TypedYield, [LetFun|TypedRest]} + , ResType + }; infer_expr(Env, {typed, As, Body, Type}) -> Type1 = check_type(Env, Type), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), @@ -1718,6 +1773,9 @@ infer_expr(Env, Let = {letval, Attrs, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> + type_error({missing_body_for_let, Attrs}), + infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); +infer_expr(Env, Let = {letfun, Attrs, _, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). @@ -1869,6 +1927,11 @@ infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, Ann, Name}, FunT, Env), [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; +infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _, _}|Rest], BlockType) -> + {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), + FunT = typesig_to_fun_t(TypeSig), + NewE = bind_var({id, Ann, Name}, FunT, Env), + [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) -> NewE = {typed, _, _, PatType} = infer_expr(Env, E), {'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} = @@ -2443,6 +2506,8 @@ unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) -> {fun_decl, Ann, Name, unfold_types(Env, Type, Options)}; unfold_types(Env, {letfun, Ann, Name, Args, Type, Body}, Options) -> {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Body, Options)}; +unfold_types(Env, {letfun, Ann, Name, Args, Type, Guard, Body}, Options) -> + {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Guard, Options), unfold_types(Env, Body, Options)}; unfold_types(Env, T, Options) when is_tuple(T) -> list_to_tuple(unfold_types(Env, tuple_to_list(T), Options)); unfold_types(Env, [H|T], Options) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index b5a43b8..1298c3f 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -798,6 +798,13 @@ make_if(Cond, Then, Else) -> X = fresh_name(), {'let', X, Cond, make_if({var, X}, Then, Else)}. +make_if_no_else({var, X}, Then) -> + {switch, {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)}. + -spec make_tuple([fexpr()]) -> fexpr(). make_tuple([E]) -> E; make_tuple(Es) -> {tuple, Es}. @@ -885,10 +892,15 @@ remove_guards(Env, [Alt = {'case', _, _, _} | Rest], Switch) -> [alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)]; remove_guards(Env, [{'case', _, Pat, Guard, Body} | Rest], {switch, Ann, Expr, _}) -> FPat = pat_to_fcode(Env, Pat), - FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, Rest}), FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard), FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body), - [{'case', [FPat], make_if(FGuard, FBody, FSwitch)}]. + case Rest of + [] -> + [{'case', [FPat], make_if_no_else(FGuard, FBody)}]; + _ -> + FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, Rest}), + [{'case', [FPat], make_if(FGuard, FBody, FSwitch)}] + end. %% %% Invariant: the number of variables matches the number of patterns in each falt. -spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit(). diff --git a/src/aeso_parse_lib.hrl b/src/aeso_parse_lib.hrl index 1b85252..670884d 100644 --- a/src/aeso_parse_lib.hrl +++ b/src/aeso_parse_lib.hrl @@ -9,12 +9,14 @@ false -> fail() end). --define(RULE(A, Do), map(fun(_1) -> Do end, A )). --define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )). --define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )). --define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )). --define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )). --define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F})). +-define(RULE(A, Do), map(fun(_1) -> Do end, A )). +-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )). +-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )). +-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )). +-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )). +-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F} )). +-define(RULE(A, B, C, D, E, F, G, Do), map(fun({_1, _2, _3, _4, _5, _6, _7}) -> Do end, {A, B, C, D, E, F, G} )). +-define(RULE(A, B, C, D, E, F, G, H, Do), map(fun({_1, _2, _3, _4, _5, _6, _7, _8}) -> Do end, {A, B, C, D, E, F, G, H})). -import(aeso_parse_lib, [tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2, diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 9ea8286..b95e6d6 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -211,12 +211,20 @@ letdef() -> choice(valdef(), fundef()). valdef() -> ?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}). -fundef() -> +fundef() -> choice(unguarded_fundef(), guarded_fundef()). + +unguarded_fundef() -> choice( [ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4}) , ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6}) ]). +guarded_fundef() -> + choice( + [ ?RULE(id(), args(), tok('|'), expr(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4, _6}) + , ?RULE(id(), args(), tok(':'), type(), tok('|'), expr(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6, _8}) + ]). + args() -> paren_list(pattern()). lam_args() -> paren_list(arg()). diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index b22f97d..b720594 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -57,7 +57,8 @@ -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. -type letval() :: {letval, ann(), pat(), expr()}. --type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}. +-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()} + | {letfun, ann(), id(), [pat()], type(), expr(), expr()}. -type letpat() :: {letpat, ann(), id(), pat()}. -type fundecl() :: {fun_decl, ann(), id(), type()}. -- 2.30.2 From 74e8064a1bd10e34b79abc702574a5973468d0b8 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 2 Sep 2021 12:52:34 +0300 Subject: [PATCH 04/23] Add test for patterns guards --- test/aeso_compiler_tests.erl | 1 + test/contracts/patterns_guards.aes | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 test/contracts/patterns_guards.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 1af71da..af88614 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -202,6 +202,7 @@ compilable_contracts() -> "child_contract_init_bug", "using_namespace", "assign_patterns", + "patterns_guards", "test" % Custom general-purpose test file. Keep it last on the list. ]. diff --git a/test/contracts/patterns_guards.aes b/test/contracts/patterns_guards.aes new file mode 100644 index 0000000..6c6db60 --- /dev/null +++ b/test/contracts/patterns_guards.aes @@ -0,0 +1,15 @@ +include "List.aes" + +contract C = + type state = int + + entrypoint init() = f([1, 2, 3, 4]) + + function + f(x::[]) = 1 + f(x::y::[]) = 2 + f(x::y::z) = switch(z) + [] => 4 + a::[] | a > 10 => 5 + b | List.length(b) > 5 => 6 + c => 7 \ No newline at end of file -- 2.30.2 From 059059ac879423685e1aba6ad1e6ee487272bc49 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 2 Sep 2021 13:02:18 +0300 Subject: [PATCH 05/23] Update docs --- docs/sophia_features.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 263eb12..48e6c8a 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -121,7 +121,7 @@ contract IntHolder = type state = int entrypoint init(x) = x entrypoint get() = state - + main contract IntHolderFactory = stateful entrypoint new(x : int) : IntHolder = let ih = Chain.create(x) : IntHolder @@ -482,6 +482,25 @@ function g(p : int * option(int)) : int = b ``` +Guards are boolean functions that can be used on patterns in both switch +statements and functions definitions: + +```sophia +function get_left_if_positive(x : one_or_both('a, 'b)) : option('a) = + switch(x) + Left(x) | x > 0 => Some(x) + Both(x, _) | x > 0 => Some(x) + _ => None +``` + +```sophia +function + get_left_if_positive : one_or_both('a, 'b) => option('a) + get_left(Left(x)) | x > 0 = Some(x) + get_left(Both(x, _)) | x > 0 = Some(x) + get_left(_) = None +``` + *NOTE: Data types cannot currently be recursive.* ## Lists @@ -851,4 +870,4 @@ Some chain operations (`Oracle.` and `AENS.`) 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.). \ No newline at end of file +the signature data with the `network_id` (`ae_mainnet` for the æternity mainnet, etc.). -- 2.30.2 From ac7a3f98ab0a78cd79cf5703d07d95d7a324cba5 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Thu, 2 Sep 2021 13:07:09 +0300 Subject: [PATCH 06/23] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a980bc5..b547e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Assign patterns to variables (e.g. `let x::(t = y::_) = [1, 2, 3, 4]` where `t == [2, 3, 4]`) - Add builtin types (`AENS.name, AENS.pointee, Chain.ttl, Chain.base_tx, Chain.ga_meta_tx, Chain.paying_for_tx`) to the calldata and result decoder +- Patterns guards + ``` + switch(x) + a::[] | a > 10 => 1 + _ => 2 + ``` + ``` + function + f(a::[]) | a > 10 = 1 + f(_) = 2 + ``` ### Changed - Fixed the ACI renderer, it shouldn't drop the `stateful` modifier ### Removed -- 2.30.2 From eadd00cc4daaa2589b2823391fa73410c16e118e Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 18 Sep 2021 18:09:57 +0300 Subject: [PATCH 07/23] Remove stateful context from Env for guards --- src/aeso_ast_infer_types.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 3910cc3..8aa0433 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1396,7 +1396,7 @@ infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, G Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), current_function = Fun }, {NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}), - NewGuard = check_expr(NewEnv, Guard, {id, Attrib, "bool"}), + NewGuard = check_expr(NewEnv#env{ stateful = false }, Guard, {id, Attrib, "bool"}), ExpectedType = check_type(Env, arg_type(NameAttrib, What)), NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType), NamedArgs = [], @@ -1913,7 +1913,7 @@ infer_case(Env, Attrs, Pattern, Guard, ExprType, Branch, SwitchType) -> none -> {'case', Attrs, NewPattern, NewBranch}; _ -> - NewGuard = check_expr(NewEnv, Guard, {id, Attrs, "bool"}), + NewGuard = check_expr(NewEnv#env{ stateful = false }, Guard, {id, Attrs, "bool"}), {'case', Attrs, NewPattern, NewGuard, NewBranch} end. -- 2.30.2 From 15ddec00bdc4f8f34f671f70541b457cc7ce5a39 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 18 Sep 2021 18:33:07 +0300 Subject: [PATCH 08/23] Elaborate on guards --- docs/sophia_features.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 48e6c8a..246ac14 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -471,6 +471,8 @@ function get_left(Both(x, _)) = Some(x) ``` +*NOTE: Data types cannot currently be recursive.* + Sophia also supports the assignment of patterns to variables: ```sophia function f(x) = switch(x) @@ -482,8 +484,10 @@ function g(p : int * option(int)) : int = b ``` -Guards are boolean functions that can be used on patterns in both switch -statements and functions definitions: +Guards are boolean expressions that can be used on patterns in both switch +statements and functions definitions, if a guard expression evalutaes to +`true`, then the corresponding body will be used, otherwise, the next pattern +will be checked: ```sophia function get_left_if_positive(x : one_or_both('a, 'b)) : option('a) = @@ -501,7 +505,7 @@ function get_left(_) = None ``` -*NOTE: Data types cannot currently be recursive.* +Guards cannot be stateful even when used inside a stateful function. ## Lists -- 2.30.2 From 36fa3d1d2b8f0ec6a9bb96a5ffbb8af9c86a9e86 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sat, 18 Sep 2021 19:27:15 +0300 Subject: [PATCH 09/23] Add failing test for stateful pattern guards --- src/aeso_ast_infer_types.erl | 15 +++++++++++++-- test/aeso_compiler_tests.erl | 4 ++++ test/contracts/stateful_pattern_guard.aes | 12 ++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 test/contracts/stateful_pattern_guard.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 8aa0433..3ae5534 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -132,6 +132,7 @@ , namespace = [] :: qname() , used_namespaces = [] :: used_namespaces() , in_pattern = false :: boolean() + , in_guard = false :: boolean() , stateful = false :: boolean() , current_function = none :: none | aeso_syntax:id() , what = top :: top | namespace | contract | contract_interface @@ -1396,7 +1397,7 @@ infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, G Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), current_function = Fun }, {NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}), - NewGuard = check_expr(NewEnv#env{ stateful = false }, Guard, {id, Attrib, "bool"}), + NewGuard = check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"}), ExpectedType = check_type(Env, arg_type(NameAttrib, What)), NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType), NamedArgs = [], @@ -1465,6 +1466,12 @@ lookup_name(Env, As, Id, Options) -> {set_qname(QId, Id), Ty1} end. +check_stateful(#env{ in_guard = true }, Id, Type = {type_sig, _, _, _, _, _}) -> + case aeso_syntax:get_ann(stateful, Type, false) of + false -> ok; + true -> + type_error({stateful_not_allowed_in_guards, Id}) + end; check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _, _}) -> case aeso_syntax:get_ann(stateful, Type, false) of false -> ok; @@ -1913,7 +1920,7 @@ infer_case(Env, Attrs, Pattern, Guard, ExprType, Branch, SwitchType) -> none -> {'case', Attrs, NewPattern, NewBranch}; _ -> - NewGuard = check_expr(NewEnv#env{ stateful = false }, Guard, {id, Attrs, "bool"}), + NewGuard = check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}), {'case', Attrs, NewPattern, NewGuard, NewBranch} end. @@ -3008,6 +3015,10 @@ mk_error({stateful_not_allowed, Id, Fun}) -> Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", [pp(Id), pp_loc(Id), pp(Fun)]), mk_t_err(pos(Id), Msg); +mk_error({stateful_not_allowed_in_guards, Id}) -> + Msg = io_lib:format("Cannot reference stateful function ~s (at ~s) in a pattern guard.\n", + [pp(Id), pp_loc(Id)]), + mk_t_err(pos(Id), Msg); mk_error({value_arg_not_allowed, Value, Fun}) -> Msg = io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n", [pp_expr("", Value), pp_loc(Value), pp(Fun)]), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index af88614..8ad091b 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -809,6 +809,10 @@ failing_contracts() -> [<> ]) + , ?TYPE_ERROR(stateful_pattern_guard, + [<> + ]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/stateful_pattern_guard.aes b/test/contracts/stateful_pattern_guard.aes new file mode 100644 index 0000000..e3706b4 --- /dev/null +++ b/test/contracts/stateful_pattern_guard.aes @@ -0,0 +1,12 @@ +include "List.aes" + +contract C = + type state = int + + entrypoint init() = f(4) + + function + f(x) | x > 0 = 1 + f(x) | g(x) = 2 + + stateful function g(x) = x < 0 -- 2.30.2 From 842e39d09de8754df4249ea20da19b4fdcf682f0 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 3 Oct 2021 15:45:46 +0300 Subject: [PATCH 10/23] Implement multiple guards --- src/aeso_ast_infer_types.erl | 134 ++++++++--------------------------- src/aeso_ast_to_fcode.erl | 22 +++--- src/aeso_parser.erl | 23 ++++-- src/aeso_pretty.erl | 8 +-- src/aeso_syntax.erl | 6 +- src/aeso_syntax_utils.erl | 21 +++--- 6 files changed, 75 insertions(+), 139 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 3ae5534..96dc09f 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -288,13 +288,6 @@ bind_contract({Contract, Ann, Id, Contents}, Env) || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents, Name =/= "init" ] ++ - [ {field_t, AnnF, Entrypoint, - contract_call_type( - {fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT}) - } - || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, _Guard, {typed, _, _, RetT}} <- Contents, - Name =/= "init" - ] ++ %% Predefined fields [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++ [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, @@ -302,9 +295,6 @@ bind_contract({Contract, Ann, Id, Contents}, Env) case [ [ArgT || {typed, _, _, ArgT} <- Args] || {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents, aeso_syntax:get_ann(entrypoint, AnnF, false)] - ++ [ [ArgT || {typed, _, _, ArgT} <- Args] - || {letfun, AnnF, {id, _, "init"}, Args, _, _, _} <- Contents, - aeso_syntax:get_ann(entrypoint, AnnF, false)] ++ [ Args || {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents, aeso_syntax:get_ann(entrypoint, AnnF, false)] @@ -907,7 +897,6 @@ infer_contract(Env0, What, Defs0, Options) -> destroy_and_report_type_errors(Env0), Env = Env0#env{ what = What }, Kind = fun({type_def, _, _, _, _}) -> type; - ({letfun, _, _, _, _, _}) -> function; ({letfun, _, _, _, _, _, _}) -> function; ({fun_clauses, _, _, _, _}) -> function; ({fun_decl, _, _, _}) -> prototype; @@ -930,8 +919,7 @@ infer_contract(Env0, What, Defs0, Options) -> Env3 = bind_funs(ProtoSigs, Env2), Functions = Get(function, Defs), %% Check for duplicates in Functions (we turn it into a map below) - FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; - ({letfun, Ann, {id, _, Fun}, _, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; + FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, FunName = fun(Def) -> {Name, _} = FunBind(Def), Name end, _ = bind_funs(lists:map(FunBind, Functions), #env{}), @@ -959,18 +947,13 @@ process_blocks(Decls) -> process_block(_, []) -> []; process_block(_, [Decl]) -> [Decl]; process_block(_Ann, [Decl | Decls]) -> - IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1; - ({letfun, _, {id, _, Name1}, _, _, _, _}) -> Name == Name1; + IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _, _}) -> Name == Name1; (_) -> false end end, case Decl of {fun_decl, Ann1, Id = {id, _, Name}, Type} -> {Clauses, Rest} = lists:splitwith(IsThis(Name), Decls), [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], [{fun_clauses, Ann1, Id, Type, Clauses}]; - {letfun, Ann1, Id = {id, _, Name}, _, _, _} -> - {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), - [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], - [{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}]; {letfun, Ann1, Id = {id, _, Name}, _, _, _, _} -> {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], @@ -1104,11 +1087,7 @@ check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest]) [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of {true, []} -> type_error({contract_has_no_entrypoints, Con}); _ when IsInterface -> - GetAnnId = fun({letfun, AnnF, Id, _, _, _}) -> {true, {AnnF, Id}}; - ({letfun, AnnF, Id, _, _, _, _}) -> {true, {AnnF, Id}}; - (_) -> false - end, - case lists:filtermap(GetAnnId, Decls) of + case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _, _} <- Decls ] of [{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id}); [] -> ok end; @@ -1345,8 +1324,7 @@ typesig_to_fun_t({type_sig, Ann, _Constr, Named, Args, Res}) -> infer_letrec(Env, Defs) -> create_constraints(), - Funs = lists:map(fun({letfun, _, {id, Ann, Name}, _, _, _}) -> {Name, fresh_uvar(Ann)}; - ({letfun, _, {id, Ann, Name}, _, _, _, _}) -> {Name, fresh_uvar(Ann)}; + Funs = lists:map(fun({letfun, _, {id, Ann, Name}, _, _, _, _}) -> {Name, fresh_uvar(Ann)}; ({fun_clauses, _, {id, Ann, Name}, _, _}) -> {Name, fresh_uvar(Ann)} end, Defs), ExtendEnv = bind_funs(Funs, Env), @@ -1376,44 +1354,31 @@ infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) -> unify(Env, ClauseT, Type1, {check_typesig, Name, ClauseT, Type1}) end || ClauseSig <- Sigs ], {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, Clauses1)}; -infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _}) -> - {{Name, Sig}, Clause} = infer_letfun1(Env, LetFun), - {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}; infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _, _}) -> {{Name, Sig}, Clause} = infer_letfun1(Env, LetFun), {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}. - -infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) -> +infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Guards, Bodies}) -> Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), current_function = Fun }, {NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}), + NewGuards = lists:map(fun(Guard) -> check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"}) end, Guards), ExpectedType = check_type(Env, arg_type(NameAttrib, What)), - NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType), + NewBodies = [{typed, _, _, ResultType} | _] = lists:map(fun(Body) -> check_expr(NewEnv, Body, ExpectedType) end, Bodies), NamedArgs = [], TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType}, {{Name, TypeSig}, - {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewBody}}; -infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Guard, Body}) -> - Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), - current_function = Fun }, - {NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}), - NewGuard = check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"}), - ExpectedType = check_type(Env, arg_type(NameAttrib, What)), - NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType), - NamedArgs = [], - TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType}, - {{Name, TypeSig}, - {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuard, NewBody}}. + {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuards, NewBodies}}. desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> NeedDesugar = case Clauses of - [{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); [{letfun, _, _, As, _, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); _ -> true end, case NeedDesugar of - false -> [Clause] = Clauses, Clause; + false -> + [{letfun, AnnF, FunF, Args, RetTypeF, [], [Expr]}] = Clauses, + {letfun, AnnF, FunF, Args, RetTypeF, Expr}; true -> NoAnn = [{origin, system}], Args = [ {typed, NoAnn, {id, NoAnn, "x#" ++ integer_to_list(I)}, Type} @@ -1421,14 +1386,11 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> Tuple = fun([X]) -> X; (As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}} end, - ToCase = fun({letfun, AnnC, _, ArgsC, _, Body}) -> {true, {'case', AnnC, Tuple(ArgsC), Body}}; - ({letfun, AnnC, _, ArgsC, _, Guard, Body}) -> {true, {'case', AnnC, Tuple(ArgsC), Guard, Body}}; - (_) -> false - end, {letfun, Ann, Fun, Args, RetType, {typed, NoAnn, - {switch, NoAnn, Tuple(Args), lists:filtermap(ToCase, Clauses)}, - RetType}} + {switch, NoAnn, Tuple(Args), + [ {'case', AnnC, Tuple(ArgsC), Guards, Bodies} + || {letfun, AnnC, _, ArgsC, _, Guards, Bodies} <- Clauses ]}, RetType}} end. print_typesig({Name, TypeSig}) -> @@ -1496,11 +1458,7 @@ check_state_dependencies(Env, Defs) -> SetState = Top ++ ["put"], Init = Top ++ ["init"], UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end, - GetFun = fun(F = {letfun, _, {id, _, Name}, _, _, _}) -> {true, {Top ++ [Name], F}}; - (F = {letfun, _, {id, _, Name}, _, _, _, _}) -> {true, {Top ++ [Name], F}}; - (_) -> false - end, - Funs = lists:filtermap(GetFun, Defs), + Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ], Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]), case maps:get(Init, Deps, false) of false -> ok; %% No init, so nothing to check @@ -1604,12 +1562,13 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> NewE = {typed, _, _, PatType} = infer_expr(Env, E), BlockType = fresh_uvar(AsLV), - {'case', _, NewPattern, NewRest} = + {'case', _, NewPattern, _, [NewRest]} = infer_case( Env , AsLC , Pattern + , [] , PatType - , {list_comp, AsLC, Yield, Rest} + , [{list_comp, AsLC, Yield, Rest}] , BlockType), {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = NewRest, { typed @@ -1617,17 +1576,6 @@ infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> , {list_comp, AsLC, TypedYield, [{letval, AsLV, NewPattern, NewE}|TypedRest]} , ResType }; -infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> - {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), - FunT = typesig_to_fun_t(TypeSig), - NewE = bind_var({id, AsLF, Name}, FunT, Env), - {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = - infer_expr(NewE, {list_comp, AsLC, Yield, Rest}), - { typed - , AsLC - , {list_comp, AsLC, TypedYield, [LetFun|TypedRest]} - , ResType - }; infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _, _}|Rest]}) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), @@ -1678,16 +1626,8 @@ infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> infer_expr(Env, {switch, Attrs, Expr, Cases}) -> NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr), SwitchType = fresh_uvar(Attrs), - ApplyInferCase = - fun(Case) -> - case Case of - {'case', As, Pattern, Branch} -> - infer_case(Env, As, Pattern, ExprType, Branch, SwitchType); - {'case', As, Pattern, Guard, Branch} -> - infer_case(Env, As, Pattern, Guard, ExprType, Branch, SwitchType) - end - end, - NewCases = lists:map(ApplyInferCase, Cases), + NewCases = [infer_case(Env, As, Pattern, Guards, ExprType, Branches, SwitchType) + || {'case', As, Pattern, Guards, Branches} <- Cases], {typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType}; infer_expr(Env, {record, Attrs, Fields}) -> RecordType = fresh_uvar(Attrs), @@ -1769,8 +1709,8 @@ infer_expr(Env, {lam, Attrs, Args, Body}) -> ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args], ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args], ResultType = fresh_uvar(Attrs), - {'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} = - infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType), + {'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, _, [NewBody]} = + infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, [], {tuple_t, Attrs, ArgTypes}, [Body], ResultType), NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> @@ -1779,9 +1719,6 @@ infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> infer_expr(Env, Let = {letval, Attrs, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); -infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> - type_error({missing_body_for_let, Attrs}), - infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); infer_expr(Env, Let = {letfun, Attrs, _, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). @@ -1909,31 +1846,18 @@ infer_pattern(Env, Pattern) -> NewPattern = infer_expr(NewEnv, Pattern), {NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}. -infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) -> - infer_case(Env, Attrs, Pattern, none, ExprType, Branch, SwitchType). - -infer_case(Env, Attrs, Pattern, Guard, ExprType, Branch, SwitchType) -> +infer_case(Env, Attrs, Pattern, Guards, ExprType, Branches, SwitchType) -> {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), - NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType), + NewGuards = lists:map(fun(Guard) -> check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}) end, Guards), + NewBranches = lists:map(fun(Branch) -> check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType) end, Branches), unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}), - case Guard of - none -> - {'case', Attrs, NewPattern, NewBranch}; - _ -> - NewGuard = check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}), - {'case', Attrs, NewPattern, NewGuard, NewBranch} - end. + {'case', Attrs, NewPattern, NewGuards, NewBranches}. %% NewStmts = infer_block(Env, Attrs, Stmts, BlockType) infer_block(_Env, Attrs, [], BlockType) -> error({impossible, empty_block, Attrs, BlockType}); infer_block(Env, _, [E], BlockType) -> [check_expr(Env, E, BlockType)]; -infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> - {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), - FunT = typesig_to_fun_t(TypeSig), - NewE = bind_var({id, Ann, Name}, FunT, Env), - [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _, _}|Rest], BlockType) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), @@ -1941,8 +1865,8 @@ infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _, _}|Rest], BlockType) -> [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) -> NewE = {typed, _, _, PatType} = infer_expr(Env, E), - {'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} = - infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType), + {'case', _, NewPattern, _, [{typed, _, {block, _, NewRest}, _}]} = + infer_case(Env, Attrs, Pattern, [], PatType, [{block, Attrs, Rest}], BlockType), [{letval, Attrs, NewPattern, NewE}|NewRest]; infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) -> infer_block(check_usings(Env, Using), Attrs, Rest, BlockType); @@ -2513,8 +2437,6 @@ unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) -> {fun_decl, Ann, Name, unfold_types(Env, Type, Options)}; unfold_types(Env, {letfun, Ann, Name, Args, Type, Body}, Options) -> {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Body, Options)}; -unfold_types(Env, {letfun, Ann, Name, Args, Type, Guard, Body}, Options) -> - {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Guard, Options), unfold_types(Env, Body, Options)}; unfold_types(Env, T, Options) when is_tuple(T) -> list_to_tuple(unfold_types(Env, tuple_to_list(T), Options)); unfold_types(Env, [H|T], Options) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 1298c3f..b486467 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -662,8 +662,8 @@ expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {ty 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, {list_comp, As, Yield, Rest}}, - {'case', As, {id, As, "_"}, {list, As, []}}]})}, + [{'case', As, Pat, [], [{list_comp, As, Yield, Rest}]}, + {'case', As, {id, As, "_"}, [], [{list, As, []}]}]})}, {def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]), {def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]}; expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) -> @@ -888,17 +888,21 @@ alts_to_fcode(Env, Type, X, Alts, Switch) -> remove_guards(_Env, [], _Switch) -> []; -remove_guards(Env, [Alt = {'case', _, _, _} | Rest], Switch) -> +remove_guards(Env, [Alt = {'case', _, _, [], [_Expr]} | Rest], Switch) -> [alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)]; -remove_guards(Env, [{'case', _, Pat, Guard, Body} | Rest], {switch, Ann, Expr, _}) -> - FPat = pat_to_fcode(Env, Pat), +remove_guards(Env, [{'case', AnnC, Pat, [Guard | Guards], [Body | Bodies]} | Rest], {switch, Ann, Expr, _}) -> + FPat = pat_to_fcode(Env, Pat), FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard), FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body), - case Rest of + R = case {Guards, Bodies} of + {[], []} -> Rest; + _ -> [{'case', AnnC, Pat, Guards, Bodies} | Rest] + end, + case R of [] -> [{'case', [FPat], make_if_no_else(FGuard, FBody)}]; _ -> - FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, Rest}), + FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, R}), [{'case', [FPat], make_if(FGuard, FBody, FSwitch)}] end. @@ -1028,7 +1032,7 @@ next_split(Pats) -> end. -spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt(). -alt_to_fcode(Env, {'case', _, Pat, Expr}) -> +alt_to_fcode(Env, {'case', _, Pat, _, [Expr]}) -> FPat = pat_to_fcode(Env, Pat), FExpr = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Expr), {'case', [FPat], FExpr}. @@ -1106,7 +1110,7 @@ decision_tree_to_fcode({'if', A, Then, Else}) -> 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, Pat, Expr} | Stmts]) -> - expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, {block, Ann, Stmts}}]}); + expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [], [{block, Ann, Stmts}]}]}); stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, Expr} | Stmts]) -> LamArgs = [ case Arg of {typed, Ann1, Id, T} -> {arg, Ann1, Id, T}; diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index b95e6d6..e3e1bef 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -215,14 +215,19 @@ fundef() -> choice(unguarded_fundef(), guarded_fundef()). unguarded_fundef() -> choice( - [ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4}) - , ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6}) + [ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), [], [_4]}) + , ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, [], [_6]}) ]). +fundef_guard() -> + ?RULE(tok('|'), expr(), tok('='), body(), {_2, _4}). + guarded_fundef() -> + GetGuards = fun(Xs) -> lists:map(fun({Guard, _}) -> Guard end, Xs) end, + GetBodies = fun(Xs) -> lists:map(fun({_, Body}) -> Body end, Xs) end, choice( - [ ?RULE(id(), args(), tok('|'), expr(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4, _6}) - , ?RULE(id(), args(), tok(':'), type(), tok('|'), expr(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6, _8}) + [ ?RULE(id(), args(), maybe_block(fundef_guard()), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), GetGuards(_3), GetBodies(_3)}) + , ?RULE(id(), args(), tok(':'), type(), maybe_block(fundef_guard()), {letfun, get_ann(_1), _1, _2, _4, GetGuards(_5), GetBodies(_5)}) ]). args() -> paren_list(pattern()). @@ -293,10 +298,16 @@ stmt() -> branch() -> choice(unguarded_branch(), guarded_branch()). unguarded_branch() -> - ?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, _3}). + ?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, [], [_3]}). + +branch_guard() -> + ?RULE(tok('|'), expr(), keyword('=>'), body(), {_3, _2, _4}). guarded_branch() -> - ?RULE(pattern(), tok('|'), expr(), keyword('=>'), body(), {'case', _4, _1, _3, _5}). + GetFirstAnn = fun([{Ann, _, _} | _]) -> Ann end, + GetGuards = fun(Xs) -> lists:map(fun({_, Guard, _}) -> Guard end, Xs) end, + GetBodies = fun(Xs) -> lists:map(fun({_, _, Body}) -> Body end, Xs) end, + ?RULE(pattern(), maybe_block(branch_guard()), {'case', GetFirstAnn(_2), _1, GetGuards(_2), GetBodies(_2)}). pattern() -> ?LET_P(E, expr(), parse_pattern(E)). diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 9be659a..e3121bd 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -173,7 +173,7 @@ decl({fun_decl, Ann, F, T}) -> false -> text("function") end, hsep(lists:map(Mod, Ann) ++ [Fun, typed(name(F), T)]); -decl(D = {letfun, Attrs, _, _, _, _}) -> +decl(D = {letfun, Attrs, _, _, _, _, _}) -> Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable -> text(atom_to_list(Mod)); (_) -> empty() end, @@ -212,7 +212,7 @@ name({typed, _, Name, _}) -> name(Name). -spec letdecl(string(), aeso_syntax:letbind()) -> doc(). letdecl(Let, {letval, _, P, E}) -> block_expr(0, hsep([text(Let), expr(P), text("=")]), E); -letdecl(Let, {letfun, _, F, Args, T, E}) -> +letdecl(Let, {letfun, _, F, Args, T, _, [E | _]}) -> block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E). -spec args([aeso_syntax:arg()]) -> doc(). @@ -482,7 +482,7 @@ elim1(Proj={proj, _, _}) -> beside(text("."), elim(Proj)); elim1(Get={map_get, _, _}) -> elim(Get); elim1(Get={map_get, _, _, _}) -> elim(Get). -alt({'case', _, Pat, Body}) -> +alt({'case', _, Pat, _, [Body | _]}) -> block_expr(0, hsep(expr(Pat), text("=>")), Body). block_expr(_, Header, {block, _, Ss}) -> @@ -494,7 +494,7 @@ statements(Stmts) -> above([ statement(S) || S <- Stmts ]). statement(S = {letval, _, _, _}) -> letdecl("let", S); -statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S); +statement(S = {letfun, _, _, _, _, _, _}) -> letdecl("let", S); statement(E) -> expr(E). get_elifs(Expr) -> get_elifs(Expr, []). diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index b720594..a1085b2 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -57,8 +57,7 @@ -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. -type letval() :: {letval, ann(), pat(), expr()}. --type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()} - | {letfun, ann(), id(), [pat()], type(), expr(), expr()}. +-type letfun() :: {letfun, ann(), id(), [pat()], type(), [expr()], [expr()]}. -type letpat() :: {letpat, ann(), id(), pat()}. -type fundecl() :: {fun_decl, ann(), id(), type()}. @@ -146,8 +145,7 @@ -type stmt() :: letbind() | expr(). --type alt() :: {'case', ann(), pat(), expr()} - | {'case', ann(), pat(), expr(), expr()}. +-type alt() :: {'case', ann(), pat(), [expr()], [expr()]}. -type lvalue() :: nonempty_list(elim()). diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index 1110ac3..6f9c16c 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -41,15 +41,16 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> Top = Fun(K, X), Rec = case X of %% lists (bound things in head scope over tail) - [A | As] -> Scoped(Same(A), Same(As)); + [A | As] -> Scoped(Same(A), Same(As)); %% decl() - {contract, _, _, Ds} -> Decl(Ds); - {namespace, _, _, Ds} -> Decl(Ds); - {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); - {fun_decl, _, _, T} -> Type(T); - {letval, _, P, E} -> Scoped(BindExpr(P), Expr(E)); - {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]); - {fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]); + {contract, _, _, Ds} -> Decl(Ds); + {namespace, _, _, Ds} -> Decl(Ds); + {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); + {fun_decl, _, _, T} -> Type(T); + {letval, _, P, E} -> Scoped(BindExpr(P), Expr(E)); + {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]); + {letfun, _, F, Xs, T, Gs, Es} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ Gs ++ Es)]); + {fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]); %% typedef() {alias_t, T} -> Type(T); {record_t, Fs} -> Type(Fs); @@ -78,7 +79,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> Plus(Expr(E), Expr({list_comp, A, Y, R})); {list_comp, A, Y, [D = {letval, _, Pat, _} | R]} -> Plus(Decl(D), Scoped(BindExpr(Pat), Expr({list_comp, A, Y, R}))); - {list_comp, A, Y, [D = {letfun, _, F, _, _, _} | R]} -> + {list_comp, A, Y, [D = {letfun, _, F, _, _, _, _} | R]} -> Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R}))); {typed, _, E, T} -> Plus(Expr(E), Type(T)); {record, _, Fs} -> Expr(Fs); @@ -95,7 +96,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> %% arg() {arg, _, Y, T} -> Plus(BindExpr(Y), Type(T)); %% alt() - {'case', _, P, E} -> Scoped(BindExpr(P), Expr(E)); + {'case', _, P, Gs, Es} -> Scoped(BindExpr(P), Expr(Gs ++ Es)); %% elim() {proj, _, _} -> Zero; {map_get, _, E} -> Expr(E); -- 2.30.2 From 0d81af771e12efe802bd7a6ef8ef19accac11d9e Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 4 Oct 2021 08:22:49 +0300 Subject: [PATCH 11/23] Fix tests --- src/aeso_aci.erl | 8 ++++---- src/aeso_ast_infer_types.erl | 24 +++++++++++------------- src/aeso_ast_to_fcode.erl | 8 ++++---- src/aeso_compiler.erl | 10 +++++----- src/aeso_syntax_utils.erl | 1 - test/aeso_calldata_tests.erl | 2 +- test/aeso_parser_tests.erl | 4 ++-- 7 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 0382593..ceac831 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -115,7 +115,7 @@ encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> %% Encode a function definition. Currently we are only interested in %% the interface and type. -encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) -> +encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _, _}) -> #{name => encode_name(Name), arguments => encode_args(Args), returns => encode_type(Type), @@ -347,9 +347,9 @@ contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace -> contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace -> [ D || D <- Decls, is_type(D) ]. -is_fun({letfun, _, _, _, _, _}) -> true; -is_fun({fun_decl, _, _, _}) -> true; -is_fun(_) -> false. +is_fun({letfun, _, _, _, _, _, _}) -> true; +is_fun({fun_decl, _, _, _}) -> true; +is_fun(_) -> false. is_type({type_def, _, _, _, _}) -> true; is_type(_) -> false. diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 96dc09f..bc9e87e 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -285,7 +285,7 @@ bind_contract({Contract, Ann, Id, Contents}, Env) contract_call_type( {fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT}) } - || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents, + || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [], [{typed, _, _, RetT}]} <- Contents, Name =/= "init" ] ++ %% Predefined fields @@ -293,7 +293,7 @@ bind_contract({Contract, Ann, Id, Contents}, Env) [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, contract_call_type( case [ [ArgT || {typed, _, _, ArgT} <- Args] - || {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents, + || {letfun, AnnF, {id, _, "init"}, Args, _, _, _} <- Contents, aeso_syntax:get_ann(entrypoint, AnnF, false)] ++ [ Args || {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents, @@ -1376,9 +1376,7 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> _ -> true end, case NeedDesugar of - false -> - [{letfun, AnnF, FunF, Args, RetTypeF, [], [Expr]}] = Clauses, - {letfun, AnnF, FunF, Args, RetTypeF, Expr}; + false -> [Clause] = Clauses, Clause; true -> NoAnn = [{origin, system}], Args = [ {typed, NoAnn, {id, NoAnn, "x#" ++ integer_to_list(I)}, Type} @@ -1386,11 +1384,11 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> Tuple = fun([X]) -> X; (As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}} end, - {letfun, Ann, Fun, Args, RetType, - {typed, NoAnn, - {switch, NoAnn, Tuple(Args), - [ {'case', AnnC, Tuple(ArgsC), Guards, Bodies} - || {letfun, AnnC, _, ArgsC, _, Guards, Bodies} <- Clauses ]}, RetType}} + {letfun, Ann, Fun, Args, RetType, [], + [{typed, NoAnn, + {switch, NoAnn, Tuple(Args), + [ {'case', AnnC, Tuple(ArgsC), Guards, Bodies} + || {letfun, AnnC, _, ArgsC, _, Guards, Bodies} <- Clauses ]}, RetType}]} end. print_typesig({Name, TypeSig}) -> @@ -1458,7 +1456,7 @@ check_state_dependencies(Env, Defs) -> SetState = Top ++ ["put"], Init = Top ++ ["init"], UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end, - Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ], + Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, [], _Bodies} <- Defs ], Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]), case maps:get(Init, Deps, false) of false -> ok; %% No init, so nothing to check @@ -2435,8 +2433,8 @@ unfold_types(Env, {type_def, Ann, Name, Args, Def}, Options) -> {type_def, Ann, Name, Args, unfold_types_in_type(Env, Def, Options)}; unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) -> {fun_decl, Ann, Name, unfold_types(Env, Type, Options)}; -unfold_types(Env, {letfun, Ann, Name, Args, Type, Body}, Options) -> - {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Body, Options)}; +unfold_types(Env, {letfun, Ann, Name, Args, Type, [], [Body]}, Options) -> + {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), [], [unfold_types(Env, Body, Options)]}; unfold_types(Env, T, Options) when is_tuple(T) -> list_to_tuple(unfold_types(Env, tuple_to_list(T), Options)); unfold_types(Env, [H|T], Options) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index b486467..fa8ae0f 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -385,7 +385,7 @@ decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) -> 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 = {id, _, Name}, Args, Ret, Body}) -> +decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [], [Body]}) -> Attrs = get_attributes(Ann), FName = lookup_fun(Env, qname(Env, Name)), FArgs = args_to_fcode(Env, Args), @@ -673,7 +673,7 @@ expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Res ); 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}]}); -expr_to_fcode(Env, Type, {list_comp, As, Yield, [LF = {letfun, _, _, _, _, _}|Rest]}) -> +expr_to_fcode(Env, Type, {list_comp, As, Yield, [LF = {letfun, _, _, _, _, _, _}|Rest]}) -> expr_to_fcode(Env, Type, {block, As, [LF, {list_comp, As, Yield, Rest}]}); %% Conditionals @@ -1111,7 +1111,7 @@ 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, Pat, Expr} | Stmts]) -> expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [], [{block, Ann, Stmts}]}]}); -stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, Expr} | Stmts]) -> +stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [], [Expr]} | Stmts]) -> LamArgs = [ case Arg of {typed, Ann1, Id, T} -> {arg, Ann1, Id, T}; _ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared @@ -1706,7 +1706,7 @@ add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) -> -spec add_fun_env(env(), [aeso_syntax:decl()]) -> env(). add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> - Entry = fun({letfun, Ann, {id, _, Name}, Args, _, _}) -> + Entry = fun({letfun, Ann, {id, _, Name}, Args, _, _, _}) -> [{qname(Env, Name), {make_fun_name(Env, Ann, Name), length(Args)}}]; ({fun_decl, Ann, {id, _, Name}, {fun_t, _, _, ArgTypes, _}}) -> [{qname(Env, Name), {make_fun_name(Env, Ann, Name), length(ArgTypes)}}]; diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index dad3faa..1f06759 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -474,10 +474,10 @@ error_missing_call_function() -> get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) -> case [ {lists:last(QFunName), FunType} - || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, - {typed, _, - {app, _, - {typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of + || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, [], + [{typed, _, + {app, _, + {typed, _, {qid, _, QFunName}, FunType}, _}, _}]} <- Defs ] of [Call] -> {ok, Call}; [] -> error_missing_call_function() end; @@ -487,7 +487,7 @@ get_call_type([_ | Contracts]) -> -dialyzer({nowarn_function, get_decode_type/2}). get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) -> - GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}]; + GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _, _}) when Name == FunName -> [{Args, Ret}]; ({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}]; (_) -> [] end, case lists:flatmap(GetType, Defs) of diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index 6f9c16c..a1f4b13 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -48,7 +48,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); {fun_decl, _, _, T} -> Type(T); {letval, _, P, E} -> Scoped(BindExpr(P), Expr(E)); - {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]); {letfun, _, F, Xs, T, Gs, Es} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ Gs ++ Es)]); {fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]); %% typedef() diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index f10fc3e..fa96803 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -59,7 +59,7 @@ calldata_aci_test_() -> end} || {ContractName, Fun, Args} <- compilable_contracts()]. parse_args(Fun, Args) -> - [{contract_main, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] = + [{contract_main, _, _, [{letfun, _, _, _, _, _, [{app, _, _, AST}]}]}] = aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"), strip_ann(AST). diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index f8e2eee..4d16aea 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -16,8 +16,8 @@ simple_contracts_test_() -> " function id(x) = x\n", ?assertMatch( [{contract_main, _, {con, _, "Identity"}, - [{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"}, - {id, _, "x"}}]}], parse_string(Text)), + [{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"}, [], + [{id, _, "x"}]}]}], parse_string(Text)), ok end}, {"Operator precedence test.", -- 2.30.2 From 878f18e9ebf33beb9c02b4c8d9bb0d94df7a9669 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 4 Oct 2021 08:39:36 +0300 Subject: [PATCH 12/23] Disable aevm related tests --- test/aeso_abi_tests.erl | 192 ++++++++++++++++++----------------- test/aeso_calldata_tests.erl | 12 --- test/aeso_compiler_tests.erl | 4 +- 3 files changed, 101 insertions(+), 107 deletions(-) diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 9306657..2b127f6 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -56,36 +56,37 @@ encode_decode_test() -> 0 = encode_decode(word, 0), ok. -encode_decode_sophia_test() -> - Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of - {X, X} -> ok; - Other -> Other - end end, - ok = Check("int", "42"), - ok = Check("int", "- 42"), - ok = Check("bool", "true"), - ok = Check("bool", "false"), - ok = Check("string", "\"Hello\""), - ok = Check("string * list(int) * option(bool)", - "(\"Hello\", [1, 2, 3], Some(true))"), - ok = Check("variant", "Blue({[\"x\"] = 1})"), - ok = Check("r", "{x = (\"foo\", 0), y = Red}"), - ok. - -to_sophia_value_neg_test() -> - Code = [ "contract Foo =\n" - " entrypoint x(y : int) : string = \"hello\"\n" ], - - {error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)), - ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)), - {error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]), - ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)), - - {error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)), - ?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)), - {error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]), - ?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)), - ok. +encode_decode_sophia_test() -> ok. +%encode_decode_sophia_test() -> +% Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of +% {X, X} -> ok; +% Other -> Other +% end end, +% ok = Check("int", "42"), +% ok = Check("int", "- 42"), +% ok = Check("bool", "true"), +% ok = Check("bool", "false"), +% ok = Check("string", "\"Hello\""), +% ok = Check("string * list(int) * option(bool)", +% "(\"Hello\", [1, 2, 3], Some(true))"), +% ok = Check("variant", "Blue({[\"x\"] = 1})"), +% ok = Check("r", "{x = (\"foo\", 0), y = Red}"), +% ok. +% +%to_sophia_value_neg_test() -> +% Code = [ "contract Foo =\n" +% " entrypoint x(y : int) : string = \"hello\"\n" ], +% +% {error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)), +% ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)), +% {error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]), +% ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)), +% +% {error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)), +% ?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)), +% {error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]), +% ?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)), +% ok. encode_calldata_neg_test() -> Code = [ "contract Foo =\n" @@ -101,27 +102,28 @@ encode_calldata_neg_test() -> ok. -decode_calldata_neg_test() -> - Code1 = [ "contract Foo =\n" - " entrypoint x(y : int) : string = \"hello\"\n" ], - Code2 = [ "contract Foo =\n" - " entrypoint x(y : string) : int = 42\n" ], - - {ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]), - {ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]), - - {error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM), - ?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)), - {error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]), - ?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)), - {error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]), - ?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)), - - {error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM), - ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)), - {error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]), - ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)), - ok. +decode_calldata_neg_test() -> ok. +%decode_calldata_neg_test() -> +% Code1 = [ "contract Foo =\n" +% " entrypoint x(y : int) : string = \"hello\"\n" ], +% Code2 = [ "contract Foo =\n" +% " entrypoint x(y : string) : int = 42\n" ], +% +% {ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]), +% {ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]), +% +% {error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM), +% ?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)), +% {error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]), +% ?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)), +% {error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]), +% ?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)), +% +% {error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM), +% ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)), +% {error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]), +% ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)), +% ok. encode_decode_sophia_string(SophiaType, String) -> @@ -148,40 +150,43 @@ encode_decode_sophia_string(SophiaType, String) -> {error, Err} end. -calldata_test() -> - [42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]), - Map = #{ <<"a">> => 4 }, - [{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] = - encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]), - [?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"], - [?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]), - [?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] = - encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]), +calldata_test() -> ok. +%calldata_test() -> +% [42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]), +% Map = #{ <<"a">> => 4 }, +% [{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] = +% encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]), +% [?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"], +% [?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]), +% [?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] = +% encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]), +% +% [119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]), +% +% [16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]), +% +% ok. - [119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]), +calldata_init_test() -> ok. +%calldata_init_test() -> +% encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}), +% +% Code = parameterized_contract("foo", ["int"]), +% encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}). - [16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]), - - ok. - -calldata_init_test() -> - encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}), - - Code = parameterized_contract("foo", ["int"]), - encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}). - -calldata_indent_test() -> - Test = fun(Extra) -> - Code = parameterized_contract(Extra, "foo", ["int"]), - encode_decode_calldata_(Code, "foo", ["42"], word) - end, - Test(" stateful entrypoint bla() = ()"), - Test(" type x = int"), - Test(" stateful entrypoint bla(x : int) =\n" - " x + 1"), - Test(" stateful entrypoint bla(x : int) : int =\n" - " x + 1"), - ok. +calldata_indent_test() -> ok. +%calldata_indent_test() -> +% Test = fun(Extra) -> +% Code = parameterized_contract(Extra, "foo", ["int"]), +% encode_decode_calldata_(Code, "foo", ["42"], word) +% end, +% Test(" stateful entrypoint bla() = ()"), +% Test(" type x = int"), +% Test(" stateful entrypoint bla(x : int) =\n" +% " x + 1"), +% Test(" stateful entrypoint bla(x : int) : int =\n" +% " x + 1"), +% ok. parameterized_contract(FunName, Types) -> parameterized_contract([], FunName, Types). @@ -197,16 +202,17 @@ parameterized_contract(ExtraCode, FunName, Types) -> " datatype variant = Red | Blue(map(string, int))\n" " entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]). -oracle_test() -> - Contract = - "contract OracleTest =\n" - " entrypoint question(o, q : oracle_query(list(string), option(int))) =\n" - " Oracle.get_question(o, q)\n", - {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = - aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", - "oq_1111111111111111111111111111113AFEFpt5"], [no_code]), - - ok. +oracle_test() -> ok. +%oracle_test() -> +% Contract = +% "contract OracleTest =\n" +% " entrypoint question(o, q : oracle_query(list(string), option(int))) =\n" +% " Oracle.get_question(o, q)\n", +% {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = +% aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", +% "oq_1111111111111111111111111111113AFEFpt5"], [no_code]), +% +% ok. permissive_literals_fail_test() -> Contract = diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index fa96803..1c1878c 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -19,18 +19,12 @@ calldata_test_() -> [ {"Testing " ++ ContractName ++ " contract calling " ++ Fun, fun() -> ContractString = aeso_test_utils:read_contract(ContractName), - AevmExprs = - case not lists:member(ContractName, not_yet_compilable(aevm)) of - true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]); - false -> undefined - end, FateExprs = case not lists:member(ContractName, not_yet_compilable(fate)) of true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]); false -> undefined end, ParsedExprs = parse_args(Fun, Args), - [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ], [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ], ok end} || {ContractName, Fun, Args} <- compilable_contracts()]. @@ -42,18 +36,12 @@ calldata_aci_test_() -> {ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString), ContractACI = binary_to_list(ContractACIBin), io:format("ACI:\n~s\n", [ContractACIBin]), - AevmExprs = - case not lists:member(ContractName, not_yet_compilable(aevm)) of - true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]); - false -> undefined - end, FateExprs = case not lists:member(ContractName, not_yet_compilable(fate)) of true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]); false -> undefined end, ParsedExprs = parse_args(Fun, Args), - [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ], [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ], ok end} || {ContractName, Fun, Args} <- compilable_contracts()]. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 8ad091b..26804a1 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -65,7 +65,7 @@ simple_compile_test_() -> check_errors(Expect, Errors) end} || {ContractName, ExpectedError} <- failing_code_gen_contracts(), - Backend <- [aevm, fate] ] ++ + Backend <- [fate] ] ++ [ {"Testing include with explicit files", fun() -> FileSystem = maps:from_list( @@ -87,7 +87,7 @@ simple_compile_test_() -> Backend == fate -> 20 end, ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), ok - end} || Backend <- [aevm, fate] ] ++ + end} || Backend <- [fate] ] ++ []. %% Check if all modules in the standard library compile -- 2.30.2 From 751fd2e15b8afabc8f45db22568f45d63b61d07c Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 4 Oct 2021 08:55:00 +0300 Subject: [PATCH 13/23] Split the sentence before if and otherwise --- docs/sophia_features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 246ac14..266626d 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -485,8 +485,8 @@ function g(p : int * option(int)) : int = ``` Guards are boolean expressions that can be used on patterns in both switch -statements and functions definitions, if a guard expression evalutaes to -`true`, then the corresponding body will be used, otherwise, the next pattern +statements and functions definitions. If a guard expression evalutaes to +`true`, then the corresponding body will be used. Otherwise, the next pattern will be checked: ```sophia -- 2.30.2 From c6acb94921cbb1d170296f19517205b8a27e8e3b Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 4 Oct 2021 09:05:50 +0300 Subject: [PATCH 14/23] Fix type in docs --- docs/sophia_features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sophia_features.md b/docs/sophia_features.md index 266626d..53468e1 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -485,7 +485,7 @@ function g(p : int * option(int)) : int = ``` Guards are boolean expressions that can be used on patterns in both switch -statements and functions definitions. If a guard expression evalutaes to +statements and functions definitions. If a guard expression evaluates to `true`, then the corresponding body will be used. Otherwise, the next pattern will be checked: -- 2.30.2 From c30201cdba50174c823b293fb070d590d1aa995b Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Wed, 6 Oct 2021 13:17:33 +0300 Subject: [PATCH 15/23] Implement multiple exprs in the same guard --- src/aeso_aci.erl | 8 +-- src/aeso_ast_infer_types.erl | 100 +++++++++++++++++++---------------- src/aeso_ast_to_fcode.erl | 29 +++++----- src/aeso_compiler.erl | 8 +-- src/aeso_parser.erl | 38 +++++-------- src/aeso_pretty.erl | 8 +-- src/aeso_syntax.erl | 7 ++- src/aeso_syntax_utils.erl | 7 +-- test/aeso_calldata_tests.erl | 2 +- test/aeso_parser_tests.erl | 4 +- 10 files changed, 108 insertions(+), 103 deletions(-) diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index ceac831..0382593 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -115,7 +115,7 @@ encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> %% Encode a function definition. Currently we are only interested in %% the interface and type. -encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _, _}) -> +encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) -> #{name => encode_name(Name), arguments => encode_args(Args), returns => encode_type(Type), @@ -347,9 +347,9 @@ contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace -> contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace -> [ D || D <- Decls, is_type(D) ]. -is_fun({letfun, _, _, _, _, _, _}) -> true; -is_fun({fun_decl, _, _, _}) -> true; -is_fun(_) -> false. +is_fun({letfun, _, _, _, _, _}) -> true; +is_fun({fun_decl, _, _, _}) -> true; +is_fun(_) -> false. is_type({type_def, _, _, _, _}) -> true; is_type(_) -> false. diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index bc9e87e..a0772c4 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -285,7 +285,7 @@ bind_contract({Contract, Ann, Id, Contents}, Env) contract_call_type( {fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT}) } - || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [], [{typed, _, _, RetT}]} <- Contents, + || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], {typed, _, _, RetT}}]} <- Contents, Name =/= "init" ] ++ %% Predefined fields @@ -293,7 +293,7 @@ bind_contract({Contract, Ann, Id, Contents}, Env) [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, contract_call_type( case [ [ArgT || {typed, _, _, ArgT} <- Args] - || {letfun, AnnF, {id, _, "init"}, Args, _, _, _} <- Contents, + || {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents, aeso_syntax:get_ann(entrypoint, AnnF, false)] ++ [ Args || {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents, @@ -896,12 +896,12 @@ infer_contract(Env0, What, Defs0, Options) -> end, destroy_and_report_type_errors(Env0), Env = Env0#env{ what = What }, - Kind = fun({type_def, _, _, _, _}) -> type; - ({letfun, _, _, _, _, _, _}) -> function; - ({fun_clauses, _, _, _, _}) -> function; - ({fun_decl, _, _, _}) -> prototype; - ({using, _, _, _, _}) -> using; - (_) -> unexpected + Kind = fun({type_def, _, _, _, _}) -> type; + ({letfun, _, _, _, _, _}) -> function; + ({fun_clauses, _, _, _, _}) -> function; + ({fun_decl, _, _, _}) -> prototype; + ({using, _, _, _, _}) -> using; + (_) -> unexpected end, Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end, OldUsedNamespaces = Env#env.used_namespaces, @@ -919,8 +919,8 @@ infer_contract(Env0, What, Defs0, Options) -> Env3 = bind_funs(ProtoSigs, Env2), Functions = Get(function, Defs), %% Check for duplicates in Functions (we turn it into a map below) - FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; - ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, + FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; + ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, FunName = fun(Def) -> {Name, _} = FunBind(Def), Name end, _ = bind_funs(lists:map(FunBind, Functions), #env{}), FunMap = maps:from_list([ {FunName(Def), Def} || Def <- Functions ]), @@ -947,14 +947,14 @@ process_blocks(Decls) -> process_block(_, []) -> []; process_block(_, [Decl]) -> [Decl]; process_block(_Ann, [Decl | Decls]) -> - IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _, _}) -> Name == Name1; + IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1; (_) -> false end end, case Decl of {fun_decl, Ann1, Id = {id, _, Name}, Type} -> {Clauses, Rest} = lists:splitwith(IsThis(Name), Decls), [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], [{fun_clauses, Ann1, Id, Type, Clauses}]; - {letfun, Ann1, Id = {id, _, Name}, _, _, _, _} -> + {letfun, Ann1, Id = {id, _, Name}, _, _, _} -> {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest], [{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}] @@ -1087,7 +1087,7 @@ check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest]) [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of {true, []} -> type_error({contract_has_no_entrypoints, Con}); _ when IsInterface -> - case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _, _} <- Decls ] of + case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of [{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id}); [] -> ok end; @@ -1324,8 +1324,8 @@ typesig_to_fun_t({type_sig, Ann, _Constr, Named, Args, Res}) -> infer_letrec(Env, Defs) -> create_constraints(), - Funs = lists:map(fun({letfun, _, {id, Ann, Name}, _, _, _, _}) -> {Name, fresh_uvar(Ann)}; - ({fun_clauses, _, {id, Ann, Name}, _, _}) -> {Name, fresh_uvar(Ann)} + Funs = lists:map(fun({letfun, _, {id, Ann, Name}, _, _, _}) -> {Name, fresh_uvar(Ann)}; + ({fun_clauses, _, {id, Ann, Name}, _, _}) -> {Name, fresh_uvar(Ann)} end, Defs), ExtendEnv = bind_funs(Funs, Env), Inferred = @@ -1354,26 +1354,32 @@ infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) -> unify(Env, ClauseT, Type1, {check_typesig, Name, ClauseT, Type1}) end || ClauseSig <- Sigs ], {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, Clauses1)}; -infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _, _}) -> +infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _}) -> {{Name, Sig}, Clause} = infer_letfun1(Env, LetFun), {{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}. -infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Guards, Bodies}) -> +infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, GuardedBodies}) -> Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false), current_function = Fun }, {NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}), - NewGuards = lists:map(fun(Guard) -> check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"}) end, Guards), ExpectedType = check_type(Env, arg_type(NameAttrib, What)), - NewBodies = [{typed, _, _, ResultType} | _] = lists:map(fun(Body) -> check_expr(NewEnv, Body, ExpectedType) end, Bodies), + InferGuardedBodies = fun({guarded, Ann, Guards, Body}) -> + NewGuards = lists:map(fun(Guard) -> + check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"}) + end, Guards), + NewBody = check_expr(NewEnv, Body, ExpectedType), + {guarded, Ann, NewGuards, NewBody} + end, + NewGuardedBodies = [{guarded, _, _, {typed, _, _, ResultType}} | _] = lists:map(InferGuardedBodies, GuardedBodies), NamedArgs = [], TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType}, {{Name, TypeSig}, - {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuards, NewBodies}}. + {letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuardedBodies}}. desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> NeedDesugar = case Clauses of - [{letfun, _, _, As, _, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); - _ -> true + [{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); + _ -> true end, case NeedDesugar of false -> [Clause] = Clauses, Clause; @@ -1384,11 +1390,10 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> Tuple = fun([X]) -> X; (As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}} end, - {letfun, Ann, Fun, Args, RetType, [], - [{typed, NoAnn, + {letfun, Ann, Fun, Args, RetType, [{guarded, NoAnn, [], {typed, NoAnn, {switch, NoAnn, Tuple(Args), - [ {'case', AnnC, Tuple(ArgsC), Guards, Bodies} - || {letfun, AnnC, _, ArgsC, _, Guards, Bodies} <- Clauses ]}, RetType}]} + [ {'case', AnnC, Tuple(ArgsC), GuardedBodies} + || {letfun, AnnC, _, ArgsC, _, GuardedBodies} <- Clauses ]}, RetType}}]} end. print_typesig({Name, TypeSig}) -> @@ -1456,7 +1461,7 @@ check_state_dependencies(Env, Defs) -> SetState = Top ++ ["put"], Init = Top ++ ["init"], UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end, - Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, [], _Bodies} <- Defs ], + Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ], Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]), case maps:get(Init, Deps, false) of false -> ok; %% No init, so nothing to check @@ -1560,13 +1565,12 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> NewE = {typed, _, _, PatType} = infer_expr(Env, E), BlockType = fresh_uvar(AsLV), - {'case', _, NewPattern, _, [NewRest]} = + {'case', _, NewPattern, [{guarded, _, [], NewRest}]} = infer_case( Env , AsLC , Pattern - , [] , PatType - , [{list_comp, AsLC, Yield, Rest}] + , [{guarded, AsLC, [], {list_comp, AsLC, Yield, Rest}}] , BlockType), {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = NewRest, { typed @@ -1574,7 +1578,7 @@ infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) -> , {list_comp, AsLC, TypedYield, [{letval, AsLV, NewPattern, NewE}|TypedRest]} , ResType }; -infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _, _}|Rest]}) -> +infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, AsLF, Name}, FunT, Env), @@ -1624,8 +1628,8 @@ infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> infer_expr(Env, {switch, Attrs, Expr, Cases}) -> NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr), SwitchType = fresh_uvar(Attrs), - NewCases = [infer_case(Env, As, Pattern, Guards, ExprType, Branches, SwitchType) - || {'case', As, Pattern, Guards, Branches} <- Cases], + NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType) + || {'case', As, Pattern, GuardedBranches} <- Cases], {typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType}; infer_expr(Env, {record, Attrs, Fields}) -> RecordType = fresh_uvar(Attrs), @@ -1707,8 +1711,8 @@ infer_expr(Env, {lam, Attrs, Args, Body}) -> ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args], ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args], ResultType = fresh_uvar(Attrs), - {'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, _, [NewBody]} = - infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, [], {tuple_t, Attrs, ArgTypes}, [Body], ResultType), + {'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, [{guarded, _, [], NewBody}]} = + infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, [{guarded, Attrs, [], Body}], ResultType), NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> @@ -1717,7 +1721,7 @@ infer_expr(Env, {letpat, Attrs, Id, Pattern}) -> infer_expr(Env, Let = {letval, Attrs, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); -infer_expr(Env, Let = {letfun, Attrs, _, _, _, _, _}) -> +infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). @@ -1844,27 +1848,33 @@ infer_pattern(Env, Pattern) -> NewPattern = infer_expr(NewEnv, Pattern), {NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}. -infer_case(Env, Attrs, Pattern, Guards, ExprType, Branches, SwitchType) -> +infer_case(Env, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) -> {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), - NewGuards = lists:map(fun(Guard) -> check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}) end, Guards), - NewBranches = lists:map(fun(Branch) -> check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType) end, Branches), + InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) -> + NewGuards = lists:map(fun(Guard) -> + check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}) + end, Guards), + NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType), + {guarded, Ann, NewGuards, NewBranch} + end, + NewGuardedBranches = lists:map(InferGuardedBranches, GuardedBranches), unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}), - {'case', Attrs, NewPattern, NewGuards, NewBranches}. + {'case', Attrs, NewPattern, NewGuardedBranches}. %% NewStmts = infer_block(Env, Attrs, Stmts, BlockType) infer_block(_Env, Attrs, [], BlockType) -> error({impossible, empty_block, Attrs, BlockType}); infer_block(Env, _, [E], BlockType) -> [check_expr(Env, E, BlockType)]; -infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _, _}|Rest], BlockType) -> +infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), FunT = typesig_to_fun_t(TypeSig), NewE = bind_var({id, Ann, Name}, FunT, Env), [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) -> NewE = {typed, _, _, PatType} = infer_expr(Env, E), - {'case', _, NewPattern, _, [{typed, _, {block, _, NewRest}, _}]} = - infer_case(Env, Attrs, Pattern, [], PatType, [{block, Attrs, Rest}], BlockType), + {'case', _, NewPattern, [{guarded, _, [], {typed, _, {block, _, NewRest}, _}}]} = + infer_case(Env, Attrs, Pattern, PatType, [{guarded, Attrs, [], {block, Attrs, Rest}}], BlockType), [{letval, Attrs, NewPattern, NewE}|NewRest]; infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) -> infer_block(check_usings(Env, Using), Attrs, Rest, BlockType); @@ -2433,8 +2443,8 @@ unfold_types(Env, {type_def, Ann, Name, Args, Def}, Options) -> {type_def, Ann, Name, Args, unfold_types_in_type(Env, Def, Options)}; unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) -> {fun_decl, Ann, Name, unfold_types(Env, Type, Options)}; -unfold_types(Env, {letfun, Ann, Name, Args, Type, [], [Body]}, Options) -> - {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), [], [unfold_types(Env, Body, Options)]}; +unfold_types(Env, {letfun, Ann, Name, Args, Type, [{guarded, AnnG, [], Body}]}, Options) -> + {letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), [{guarded, AnnG, [], unfold_types(Env, Body, Options)}]}; unfold_types(Env, T, Options) when is_tuple(T) -> list_to_tuple(unfold_types(Env, tuple_to_list(T), Options)); unfold_types(Env, [H|T], Options) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index fa8ae0f..45358bc 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -385,7 +385,7 @@ decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) -> 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 = {id, _, Name}, Args, Ret, [], [Body]}) -> +decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) -> Attrs = get_attributes(Ann), FName = lookup_fun(Env, qname(Env, Name)), FArgs = args_to_fcode(Env, Args), @@ -662,8 +662,8 @@ expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {ty 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, [], [{list_comp, As, Yield, Rest}]}, - {'case', As, {id, As, "_"}, [], [{list, As, []}]}]})}, + [{'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)]}; expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) -> @@ -673,7 +673,7 @@ expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Res ); 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}]}); -expr_to_fcode(Env, Type, {list_comp, As, Yield, [LF = {letfun, _, _, _, _, _, _}|Rest]}) -> +expr_to_fcode(Env, Type, {list_comp, As, Yield, [LF = {letfun, _, _, _, _, _}|Rest]}) -> expr_to_fcode(Env, Type, {block, As, [LF, {list_comp, As, Yield, Rest}]}); %% Conditionals @@ -888,15 +888,18 @@ alts_to_fcode(Env, Type, X, Alts, Switch) -> remove_guards(_Env, [], _Switch) -> []; -remove_guards(Env, [Alt = {'case', _, _, [], [_Expr]} | Rest], Switch) -> +remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) -> [alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)]; -remove_guards(Env, [{'case', AnnC, Pat, [Guard | Guards], [Body | Bodies]} | Rest], {switch, Ann, Expr, _}) -> +remove_guards(Env, [{'case', AnnC, Pat, [{guarded, AnnG, [Guard | Guards], Body} | GuardedBodies]} | Rest], {switch, Ann, Expr, _}) -> FPat = pat_to_fcode(Env, Pat), FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard), FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body), - R = case {Guards, Bodies} of - {[], []} -> Rest; - _ -> [{'case', AnnC, Pat, Guards, Bodies} | Rest] + R = case Guards of + [] -> case GuardedBodies of + [] -> Rest; + _ -> [{'case', AnnC, Pat, GuardedBodies} | Rest] + end; + _ -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body} | GuardedBodies]} | Rest] end, case R of [] -> @@ -1032,7 +1035,7 @@ next_split(Pats) -> end. -spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt(). -alt_to_fcode(Env, {'case', _, Pat, _, [Expr]}) -> +alt_to_fcode(Env, {'case', _, Pat, [{guarded, _, [], Expr}]}) -> FPat = pat_to_fcode(Env, Pat), FExpr = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Expr), {'case', [FPat], FExpr}. @@ -1110,8 +1113,8 @@ decision_tree_to_fcode({'if', A, Then, Else}) -> 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, Pat, Expr} | Stmts]) -> - expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [], [{block, Ann, Stmts}]}]}); -stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [], [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]) -> LamArgs = [ case Arg of {typed, Ann1, Id, T} -> {arg, Ann1, Id, T}; _ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared @@ -1706,7 +1709,7 @@ add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) -> -spec add_fun_env(env(), [aeso_syntax:decl()]) -> env(). add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> - Entry = fun({letfun, Ann, {id, _, Name}, Args, _, _, _}) -> + Entry = fun({letfun, Ann, {id, _, Name}, Args, _, _}) -> [{qname(Env, Name), {make_fun_name(Env, Ann, Name), length(Args)}}]; ({fun_decl, Ann, {id, _, Name}, {fun_t, _, _, ArgTypes, _}}) -> [{qname(Env, Name), {make_fun_name(Env, Ann, Name), length(ArgTypes)}}]; diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 1f06759..28ee133 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -474,10 +474,10 @@ error_missing_call_function() -> get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) -> case [ {lists:last(QFunName), FunType} - || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, [], - [{typed, _, + || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, + [{guarded, _, [], {typed, _, {app, _, - {typed, _, {qid, _, QFunName}, FunType}, _}, _}]} <- Defs ] of + {typed, _, {qid, _, QFunName}, FunType}, _}, _}}]} <- Defs ] of [Call] -> {ok, Call}; [] -> error_missing_call_function() end; @@ -487,7 +487,7 @@ get_call_type([_ | Contracts]) -> -dialyzer({nowarn_function, get_decode_type/2}). get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) -> - GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _, _}) when Name == FunName -> [{Args, Ret}]; + GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}]; ({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}]; (_) -> [] end, case lists:flatmap(GetType, Defs) of diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index e3e1bef..3d388c2 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -211,23 +211,16 @@ letdef() -> choice(valdef(), fundef()). valdef() -> ?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}). -fundef() -> choice(unguarded_fundef(), guarded_fundef()). - -unguarded_fundef() -> +guarded_fundefs() -> choice( - [ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), [], [_4]}) - , ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, [], [_6]}) + [ ?RULE(keyword('='), body(), [{guarded, _1, [], _2}]) + , maybe_block(?RULE(keyword('|'), comma_sep(expr()), tok('='), body(), {guarded, _1, _2, _4})) ]). -fundef_guard() -> - ?RULE(tok('|'), expr(), tok('='), body(), {_2, _4}). - -guarded_fundef() -> - GetGuards = fun(Xs) -> lists:map(fun({Guard, _}) -> Guard end, Xs) end, - GetBodies = fun(Xs) -> lists:map(fun({_, Body}) -> Body end, Xs) end, +fundef() -> choice( - [ ?RULE(id(), args(), maybe_block(fundef_guard()), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), GetGuards(_3), GetBodies(_3)}) - , ?RULE(id(), args(), tok(':'), type(), maybe_block(fundef_guard()), {letfun, get_ann(_1), _1, _2, _4, GetGuards(_5), GetBodies(_5)}) + [ ?RULE(id(), args(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _3}) + , ?RULE(id(), args(), tok(':'), type(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, _4, _5}) ]). args() -> paren_list(pattern()). @@ -295,19 +288,14 @@ stmt() -> , {else, keyword(else), body()} ])). -branch() -> choice(unguarded_branch(), guarded_branch()). +branch() -> + ?RULE(pattern(), guarded_branches(), {'case', get_ann(lists:nth(1, _2)), _1, _2}). -unguarded_branch() -> - ?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, [], [_3]}). - -branch_guard() -> - ?RULE(tok('|'), expr(), keyword('=>'), body(), {_3, _2, _4}). - -guarded_branch() -> - GetFirstAnn = fun([{Ann, _, _} | _]) -> Ann end, - GetGuards = fun(Xs) -> lists:map(fun({_, Guard, _}) -> Guard end, Xs) end, - GetBodies = fun(Xs) -> lists:map(fun({_, _, Body}) -> Body end, Xs) end, - ?RULE(pattern(), maybe_block(branch_guard()), {'case', GetFirstAnn(_2), _1, GetGuards(_2), GetBodies(_2)}). +guarded_branches() -> + choice( + [ ?RULE(keyword('=>'), body(), [{guarded, _1, [], _2}]) + , maybe_block(?RULE(tok('|'), comma_sep(expr()), keyword('=>'), body(), {guarded, _3, _2, _4})) + ]). pattern() -> ?LET_P(E, expr(), parse_pattern(E)). diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index e3121bd..56330b3 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -173,7 +173,7 @@ decl({fun_decl, Ann, F, T}) -> false -> text("function") end, hsep(lists:map(Mod, Ann) ++ [Fun, typed(name(F), T)]); -decl(D = {letfun, Attrs, _, _, _, _, _}) -> +decl(D = {letfun, Attrs, _, _, _, _}) -> Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable -> text(atom_to_list(Mod)); (_) -> empty() end, @@ -212,7 +212,7 @@ name({typed, _, Name, _}) -> name(Name). -spec letdecl(string(), aeso_syntax:letbind()) -> doc(). letdecl(Let, {letval, _, P, E}) -> block_expr(0, hsep([text(Let), expr(P), text("=")]), E); -letdecl(Let, {letfun, _, F, Args, T, _, [E | _]}) -> +letdecl(Let, {letfun, _, F, Args, T, [{guarded, _, _Guards, E} | _]}) -> block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E). -spec args([aeso_syntax:arg()]) -> doc(). @@ -482,7 +482,7 @@ elim1(Proj={proj, _, _}) -> beside(text("."), elim(Proj)); elim1(Get={map_get, _, _}) -> elim(Get); elim1(Get={map_get, _, _, _}) -> elim(Get). -alt({'case', _, Pat, _, [Body | _]}) -> +alt({'case', _, Pat, [{guarded, _, _Guards, Body} | _]}) -> block_expr(0, hsep(expr(Pat), text("=>")), Body). block_expr(_, Header, {block, _, Ss}) -> @@ -494,7 +494,7 @@ statements(Stmts) -> above([ statement(S) || S <- Stmts ]). statement(S = {letval, _, _, _}) -> letdecl("let", S); -statement(S = {letfun, _, _, _, _, _, _}) -> letdecl("let", S); +statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S); statement(E) -> expr(E). get_elifs(Expr) -> get_elifs(Expr, []). diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index a1085b2..1e01153 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -56,8 +56,11 @@ -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. +-type guard() :: expr(). +-type guarded_expr() :: {guarded, ann(), [guard()], expr()}. + -type letval() :: {letval, ann(), pat(), expr()}. --type letfun() :: {letfun, ann(), id(), [pat()], type(), [expr()], [expr()]}. +-type letfun() :: {letfun, ann(), id(), [pat()], type(), [guarded_expr(),...]}. -type letpat() :: {letpat, ann(), id(), pat()}. -type fundecl() :: {fun_decl, ann(), id(), type()}. @@ -145,7 +148,7 @@ -type stmt() :: letbind() | expr(). --type alt() :: {'case', ann(), pat(), [expr()], [expr()]}. +-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}. -type lvalue() :: nonempty_list(elim()). diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index a1f4b13..e50f2d4 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -48,7 +48,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); {fun_decl, _, _, T} -> Type(T); {letval, _, P, E} -> Scoped(BindExpr(P), Expr(E)); - {letfun, _, F, Xs, T, Gs, Es} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ Gs ++ Es)]); + {letfun, _, F, Xs, T, GEs} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ GEs)]); {fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]); %% typedef() {alias_t, T} -> Type(T); @@ -78,7 +78,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> Plus(Expr(E), Expr({list_comp, A, Y, R})); {list_comp, A, Y, [D = {letval, _, Pat, _} | R]} -> Plus(Decl(D), Scoped(BindExpr(Pat), Expr({list_comp, A, Y, R}))); - {list_comp, A, Y, [D = {letfun, _, F, _, _, _, _} | R]} -> + {list_comp, A, Y, [D = {letfun, _, F, _, _, _} | R]} -> Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R}))); {typed, _, E, T} -> Plus(Expr(E), Type(T)); {record, _, Fs} -> Expr(Fs); @@ -89,13 +89,14 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> {map_get, _, A, B, C} -> Expr([A, B, C]); {block, _, Ss} -> Expr(Ss); {letpat, _, X, P} -> Plus(BindExpr(X), Expr(P)); + {guarded, _, Gs, E} -> Expr([E | Gs]); %% field() {field, _, LV, E} -> Expr([LV, E]); {field, _, LV, _, E} -> Expr([LV, E]); %% arg() {arg, _, Y, T} -> Plus(BindExpr(Y), Type(T)); %% alt() - {'case', _, P, Gs, Es} -> Scoped(BindExpr(P), Expr(Gs ++ Es)); + {'case', _, P, GEs} -> Scoped(BindExpr(P), Expr(GEs)); %% elim() {proj, _, _} -> Zero; {map_get, _, E} -> Expr(E); diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 1c1878c..a01fb7f 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -47,7 +47,7 @@ calldata_aci_test_() -> end} || {ContractName, Fun, Args} <- compilable_contracts()]. parse_args(Fun, Args) -> - [{contract_main, _, _, [{letfun, _, _, _, _, _, [{app, _, _, AST}]}]}] = + [{contract_main, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] = aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"), strip_ann(AST). diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index 4d16aea..6c577c8 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -16,8 +16,8 @@ simple_contracts_test_() -> " function id(x) = x\n", ?assertMatch( [{contract_main, _, {con, _, "Identity"}, - [{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"}, [], - [{id, _, "x"}]}]}], parse_string(Text)), + [{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"}, + [{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)), ok end}, {"Operator precedence test.", -- 2.30.2 From 06118542f23bea1fdeebe367007a4ae43cbcdfdf Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 10 Oct 2021 19:16:12 +0300 Subject: [PATCH 16/23] Fix pretty printing --- src/aeso_pretty.erl | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 56330b3..0ee05f2 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -212,8 +212,10 @@ name({typed, _, Name, _}) -> name(Name). -spec letdecl(string(), aeso_syntax:letbind()) -> doc(). letdecl(Let, {letval, _, P, E}) -> block_expr(0, hsep([text(Let), expr(P), text("=")]), E); -letdecl(Let, {letfun, _, F, Args, T, [{guarded, _, _Guards, E} | _]}) -> - block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E). +letdecl(Let, {letfun, _, F, Args, T, [GuardedBody]}) -> + beside(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), guarded_body(GuardedBody, "=")); +letdecl(Let, {letfun, _, F, Args, T, GuardedBodies}) -> + block(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), above(lists:map(fun(GB) -> guarded_body(GB, "=") end, GuardedBodies))). -spec args([aeso_syntax:arg()]) -> doc(). args(Args) -> @@ -482,8 +484,18 @@ elim1(Proj={proj, _, _}) -> beside(text("."), elim(Proj)); elim1(Get={map_get, _, _}) -> elim(Get); elim1(Get={map_get, _, _, _}) -> elim(Get). -alt({'case', _, Pat, [{guarded, _, _Guards, Body} | _]}) -> - block_expr(0, hsep(expr(Pat), text("=>")), Body). +alt({'case', _, Pat, [GuardedBody]}) -> + beside(expr(Pat), guarded_body(GuardedBody, "=>")); +alt({'case', _, Pat, GuardedBodies}) -> + block(expr(Pat), above(lists:map(fun(GB) -> guarded_body(GB, "=>") end, GuardedBodies))). + +guarded_body({guarded, _, Guards, Body}, Then) -> + block_expr(0, hsep(guards(Guards), text(Then)), Body). + +guards([]) -> + text(""); +guards(Guards) -> + hsep([text(" |"), par(punctuate(text(","), lists:map(fun expr/1, Guards)), 0)]). block_expr(_, Header, {block, _, Ss}) -> block(Header, statements(Ss)); -- 2.30.2 From 2a15626c3c5cc78811a34b8e9e34856cf39051fa Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 10 Oct 2021 20:08:08 +0300 Subject: [PATCH 17/23] Change tests to include multiple guards --- test/aeso_compiler_tests.erl | 5 +++-- test/contracts/patterns_guards.aes | 10 +++++++--- test/contracts/stateful_pattern_guard.aes | 2 -- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 26804a1..934441d 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -810,8 +810,9 @@ failing_contracts() -> "Unbound variable g at line 8, column 23">> ]) , ?TYPE_ERROR(stateful_pattern_guard, - [<> + [<> + ]) ]) ]. diff --git a/test/contracts/patterns_guards.aes b/test/contracts/patterns_guards.aes index 6c6db60..081749b 100644 --- a/test/contracts/patterns_guards.aes +++ b/test/contracts/patterns_guards.aes @@ -6,10 +6,14 @@ contract C = entrypoint init() = f([1, 2, 3, 4]) function - f(x::[]) = 1 + f(x::[]) + | x > 1, x < 10 = 1 + | x < 1 = 9 f(x::y::[]) = 2 f(x::y::z) = switch(z) [] => 4 - a::[] | a > 10 => 5 + a::[] + | a > 10, a < 20 => 5 + | a > 5 => 8 b | List.length(b) > 5 => 6 - c => 7 \ No newline at end of file + c => 7 diff --git a/test/contracts/stateful_pattern_guard.aes b/test/contracts/stateful_pattern_guard.aes index e3706b4..a295ca1 100644 --- a/test/contracts/stateful_pattern_guard.aes +++ b/test/contracts/stateful_pattern_guard.aes @@ -1,5 +1,3 @@ -include "List.aes" - contract C = type state = int -- 2.30.2 From 2d8db5f4ce6dbec9e3ce53ceb8731de98c3ed195 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 10 Oct 2021 20:08:39 +0300 Subject: [PATCH 18/23] Add test for non-boolean guards --- test/aeso_compiler_tests.erl | 3 +++ test/contracts/non_boolean_pattern_guard.aes | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 test/contracts/non_boolean_pattern_guard.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 934441d..926268f 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -813,6 +813,9 @@ failing_contracts() -> [<> ]) + , ?TYPE_ERROR(non_boolean_pattern_guard, + [<> ]) ]. diff --git a/test/contracts/non_boolean_pattern_guard.aes b/test/contracts/non_boolean_pattern_guard.aes new file mode 100644 index 0000000..72f7ca0 --- /dev/null +++ b/test/contracts/non_boolean_pattern_guard.aes @@ -0,0 +1,4 @@ +contract C = + type state = int + + entrypoint init(x) | "y" = 1 -- 2.30.2 From 1653a1ff1de64ec0b1a44762e21042e2f06c1ba7 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 11 Oct 2021 12:52:13 +0300 Subject: [PATCH 19/23] Desugar clauses with guards --- src/aeso_ast_infer_types.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index a0772c4..b188a21 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1378,8 +1378,8 @@ infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, G desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> NeedDesugar = case Clauses of - [{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); - _ -> true + [{letfun, _, _, As, _, [{guarded, _, [], _}]}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As); + _ -> true end, case NeedDesugar of false -> [Clause] = Clauses, Clause; -- 2.30.2 From 303ca2131790f538284500245abf16b13671ce3c Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 12 Oct 2021 12:10:52 +0300 Subject: [PATCH 20/23] Fix incomplete patterns bug --- src/aeso_ast_to_fcode.erl | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 45358bc..f0ca66a 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -890,23 +890,35 @@ remove_guards(_Env, [], _Switch) -> []; remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) -> [alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)]; -remove_guards(Env, [{'case', AnnC, Pat, [{guarded, AnnG, [Guard | Guards], Body} | GuardedBodies]} | Rest], {switch, Ann, Expr, _}) -> +remove_guards(Env, [{'case', AnnC, Pat, [{guarded, AnnG, [Guard | Guards], Body} | GuardedBodies]} | Rest], Switch = {switch, Ann, Expr, _}) -> FPat = pat_to_fcode(Env, Pat), FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard), FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body), - R = case Guards of - [] -> case GuardedBodies of - [] -> Rest; - _ -> [{'case', AnnC, Pat, GuardedBodies} | Rest] - end; - _ -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body} | GuardedBodies]} | Rest] - end, - case R of + case Guards of [] -> - [{'case', [FPat], make_if_no_else(FGuard, FBody)}]; + R = case GuardedBodies of + [] -> Rest; + _ -> [{'case', AnnC, Pat, GuardedBodies} | Rest] + end, + case R of + [] -> + [{'case', [FPat], make_if_no_else(FGuard, FBody)} | remove_guards(Env, Rest, Switch)]; + _ -> + FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, R}), + [{'case', [FPat], make_if(FGuard, FBody, FSwitch)} | remove_guards(Env, Rest, Switch)] + end; _ -> - FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, R}), - [{'case', [FPat], make_if(FGuard, FBody, FSwitch)}] + R1 = case GuardedBodies of + [] -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body}]} | Rest]; + _ -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body} | GuardedBodies]} | Rest] + end, + R2 = case GuardedBodies of + [] -> Rest; + _ -> [{'case', AnnC, Pat, GuardedBodies} | Rest] + end, + FSwitch1 = expr_to_fcode(Env, {switch, Ann, Expr, R1}), + FSwitch2 = expr_to_fcode(Env, {switch, Ann, Expr, R2}), + [{'case', [FPat], make_if(FGuard, FSwitch1, FSwitch2)} | remove_guards(Env, Rest, Switch)] end. %% %% Invariant: the number of variables matches the number of patterns in each falt. -- 2.30.2 From 6fa6dfbbc0d147d3d7a6e188f0e887589449fe29 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 18 Oct 2021 12:32:58 +0300 Subject: [PATCH 21/23] Fix docs --- 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 53468e1..9697c6e 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -490,7 +490,7 @@ statements and functions definitions. If a guard expression evaluates to will be checked: ```sophia -function get_left_if_positive(x : one_or_both('a, 'b)) : option('a) = +function get_left_if_positive(x : one_or_both(int, 'b)) : option(int) = switch(x) Left(x) | x > 0 => Some(x) Both(x, _) | x > 0 => Some(x) @@ -499,10 +499,10 @@ function get_left_if_positive(x : one_or_both('a, 'b)) : option('a) = ```sophia function - get_left_if_positive : one_or_both('a, 'b) => option('a) - get_left(Left(x)) | x > 0 = Some(x) - get_left(Both(x, _)) | x > 0 = Some(x) - get_left(_) = None + get_left_if_positive : one_or_both(int, 'b) => option(int) + get_left_if_positive(Left(x)) | x > 0 = Some(x) + get_left_if_positive(Both(x, _)) | x > 0 = Some(x) + get_left_if_positive(_) = None ``` Guards cannot be stateful even when used inside a stateful function. -- 2.30.2 From a715cccd03d2526bea3c50647491725e34fc8ecf Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 18 Oct 2021 18:10:02 +0300 Subject: [PATCH 22/23] Compile to icode when no guards are used --- src/aeso_ast_to_icode.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 8cace86..c2bbd50 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -96,7 +96,7 @@ contract_to_icode([Decl = {type_def, _Attrib, Id = {id, _, Name}, Args, Def} | R _ -> Icode1 end, contract_to_icode(Rest, Icode2); -contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) -> +contract_to_icode([{letfun, Attrib, Name, Args, _What, [{guarded, _, [], Body={typed,_,_,T}}]}|Rest], Icode) -> FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++ [ payable || proplists:get_value(payable, Attrib, false) ] ++ [ private || is_private(Attrib, Icode) ], @@ -323,8 +323,8 @@ ast_body({list_comp, _, Yield, []}, Icode) -> ast_body({list_comp, As, Yield, [{comprehension_bind, {typed, _, Pat, ArgType}, BindExpr}|Rest]}, Icode) -> Arg = "%lc", Body = {switch, As, {typed, As, {id, As, Arg}, ArgType}, - [{'case', As, Pat, {list_comp, As, Yield, Rest}}, - {'case', As, {id, As, "_"}, {list, As, []}}]}, + [{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]}, + {'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]}, #funcall { function = #var_ref{ name = ["ListInternal", "flat_map"] } , args = @@ -349,14 +349,14 @@ ast_body({switch,_,A,Cases}, Icode) -> %% patterns appear in cases. #switch{expr=ast_body(A, Icode), cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)} - || {'case',_,Pat,Body} <- Cases]}; + || {'case',_,Pat,[{guarded, _, [], Body}]} <- Cases]}; ast_body({block, As, [{letval, _, Pat, E} | Rest]}, Icode) -> E1 = ast_body(E, Icode), Pat1 = ast_body(Pat, Icode), Rest1 = ast_body({block, As, Rest}, Icode), #switch{expr = E1, cases = [{Pat1, Rest1}]}; -ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) -> +ast_body({block, As, [{letfun, Ann, F, Args, _Type, [{guarded, _, [], Expr}]} | Rest]}, Icode) -> ToArg = fun({typed, Ann1, Id, T}) -> {arg, Ann1, Id, T} end, %% Pattern matching has been desugared LamArgs = lists:map(ToArg, Args), ast_body({block, As, [{letval, Ann, F, {lam, Ann, LamArgs, Expr}} | Rest]}, Icode); -- 2.30.2 From 8f54fe4facf55cb60a0d4d99c64cacac5d2edd7b Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Mon, 18 Oct 2021 18:10:23 +0300 Subject: [PATCH 23/23] Revert "Disable aevm related tests" This reverts commit e828099bd97dffe11438f2e48f3a92ce3641e85b. --- test/aeso_abi_tests.erl | 192 +++++++++++++++++------------------ test/aeso_calldata_tests.erl | 12 +++ test/aeso_compiler_tests.erl | 4 +- 3 files changed, 107 insertions(+), 101 deletions(-) diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 2b127f6..9306657 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -56,37 +56,36 @@ encode_decode_test() -> 0 = encode_decode(word, 0), ok. -encode_decode_sophia_test() -> ok. -%encode_decode_sophia_test() -> -% Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of -% {X, X} -> ok; -% Other -> Other -% end end, -% ok = Check("int", "42"), -% ok = Check("int", "- 42"), -% ok = Check("bool", "true"), -% ok = Check("bool", "false"), -% ok = Check("string", "\"Hello\""), -% ok = Check("string * list(int) * option(bool)", -% "(\"Hello\", [1, 2, 3], Some(true))"), -% ok = Check("variant", "Blue({[\"x\"] = 1})"), -% ok = Check("r", "{x = (\"foo\", 0), y = Red}"), -% ok. -% -%to_sophia_value_neg_test() -> -% Code = [ "contract Foo =\n" -% " entrypoint x(y : int) : string = \"hello\"\n" ], -% -% {error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)), -% ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)), -% {error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]), -% ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)), -% -% {error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)), -% ?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)), -% {error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]), -% ?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)), -% ok. +encode_decode_sophia_test() -> + Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of + {X, X} -> ok; + Other -> Other + end end, + ok = Check("int", "42"), + ok = Check("int", "- 42"), + ok = Check("bool", "true"), + ok = Check("bool", "false"), + ok = Check("string", "\"Hello\""), + ok = Check("string * list(int) * option(bool)", + "(\"Hello\", [1, 2, 3], Some(true))"), + ok = Check("variant", "Blue({[\"x\"] = 1})"), + ok = Check("r", "{x = (\"foo\", 0), y = Red}"), + ok. + +to_sophia_value_neg_test() -> + Code = [ "contract Foo =\n" + " entrypoint x(y : int) : string = \"hello\"\n" ], + + {error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)), + ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)), + {error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]), + ?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)), + + {error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)), + ?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)), + {error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]), + ?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)), + ok. encode_calldata_neg_test() -> Code = [ "contract Foo =\n" @@ -102,28 +101,27 @@ encode_calldata_neg_test() -> ok. -decode_calldata_neg_test() -> ok. -%decode_calldata_neg_test() -> -% Code1 = [ "contract Foo =\n" -% " entrypoint x(y : int) : string = \"hello\"\n" ], -% Code2 = [ "contract Foo =\n" -% " entrypoint x(y : string) : int = 42\n" ], -% -% {ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]), -% {ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]), -% -% {error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM), -% ?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)), -% {error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]), -% ?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)), -% {error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]), -% ?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)), -% -% {error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM), -% ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)), -% {error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]), -% ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)), -% ok. +decode_calldata_neg_test() -> + Code1 = [ "contract Foo =\n" + " entrypoint x(y : int) : string = \"hello\"\n" ], + Code2 = [ "contract Foo =\n" + " entrypoint x(y : string) : int = 42\n" ], + + {ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]), + {ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]), + + {error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM), + ?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)), + {error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]), + ?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)), + {error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]), + ?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)), + + {error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM), + ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)), + {error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]), + ?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)), + ok. encode_decode_sophia_string(SophiaType, String) -> @@ -150,43 +148,40 @@ encode_decode_sophia_string(SophiaType, String) -> {error, Err} end. -calldata_test() -> ok. -%calldata_test() -> -% [42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]), -% Map = #{ <<"a">> => 4 }, -% [{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] = -% encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]), -% [?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"], -% [?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]), -% [?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] = -% encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]), -% -% [119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]), -% -% [16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]), -% -% ok. +calldata_test() -> + [42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]), + Map = #{ <<"a">> => 4 }, + [{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] = + encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]), + [?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"], + [?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]), + [?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] = + encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]), -calldata_init_test() -> ok. -%calldata_init_test() -> -% encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}), -% -% Code = parameterized_contract("foo", ["int"]), -% encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}). + [119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]), -calldata_indent_test() -> ok. -%calldata_indent_test() -> -% Test = fun(Extra) -> -% Code = parameterized_contract(Extra, "foo", ["int"]), -% encode_decode_calldata_(Code, "foo", ["42"], word) -% end, -% Test(" stateful entrypoint bla() = ()"), -% Test(" type x = int"), -% Test(" stateful entrypoint bla(x : int) =\n" -% " x + 1"), -% Test(" stateful entrypoint bla(x : int) : int =\n" -% " x + 1"), -% ok. + [16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]), + + ok. + +calldata_init_test() -> + encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}), + + Code = parameterized_contract("foo", ["int"]), + encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}). + +calldata_indent_test() -> + Test = fun(Extra) -> + Code = parameterized_contract(Extra, "foo", ["int"]), + encode_decode_calldata_(Code, "foo", ["42"], word) + end, + Test(" stateful entrypoint bla() = ()"), + Test(" type x = int"), + Test(" stateful entrypoint bla(x : int) =\n" + " x + 1"), + Test(" stateful entrypoint bla(x : int) : int =\n" + " x + 1"), + ok. parameterized_contract(FunName, Types) -> parameterized_contract([], FunName, Types). @@ -202,17 +197,16 @@ parameterized_contract(ExtraCode, FunName, Types) -> " datatype variant = Red | Blue(map(string, int))\n" " entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]). -oracle_test() -> ok. -%oracle_test() -> -% Contract = -% "contract OracleTest =\n" -% " entrypoint question(o, q : oracle_query(list(string), option(int))) =\n" -% " Oracle.get_question(o, q)\n", -% {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = -% aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", -% "oq_1111111111111111111111111111113AFEFpt5"], [no_code]), -% -% ok. +oracle_test() -> + Contract = + "contract OracleTest =\n" + " entrypoint question(o, q : oracle_query(list(string), option(int))) =\n" + " Oracle.get_question(o, q)\n", + {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = + aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", + "oq_1111111111111111111111111111113AFEFpt5"], [no_code]), + + ok. permissive_literals_fail_test() -> Contract = diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index a01fb7f..80ef24f 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -19,12 +19,18 @@ calldata_test_() -> [ {"Testing " ++ ContractName ++ " contract calling " ++ Fun, fun() -> ContractString = aeso_test_utils:read_contract(ContractName), + AevmExprs = + case not lists:member(ContractName, not_yet_compilable(aevm)) of + true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]); + false -> undefined + end, FateExprs = case not lists:member(ContractName, not_yet_compilable(fate)) of true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]); false -> undefined end, ParsedExprs = parse_args(Fun, Args), + [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ], [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ], ok end} || {ContractName, Fun, Args} <- compilable_contracts()]. @@ -36,12 +42,18 @@ calldata_aci_test_() -> {ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString), ContractACI = binary_to_list(ContractACIBin), io:format("ACI:\n~s\n", [ContractACIBin]), + AevmExprs = + case not lists:member(ContractName, not_yet_compilable(aevm)) of + true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]); + false -> undefined + end, FateExprs = case not lists:member(ContractName, not_yet_compilable(fate)) of true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]); false -> undefined end, ParsedExprs = parse_args(Fun, Args), + [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ], [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ], ok end} || {ContractName, Fun, Args} <- compilable_contracts()]. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 926268f..19d9ac1 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -65,7 +65,7 @@ simple_compile_test_() -> check_errors(Expect, Errors) end} || {ContractName, ExpectedError} <- failing_code_gen_contracts(), - Backend <- [fate] ] ++ + Backend <- [aevm, fate] ] ++ [ {"Testing include with explicit files", fun() -> FileSystem = maps:from_list( @@ -87,7 +87,7 @@ simple_compile_test_() -> Backend == fate -> 20 end, ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), ok - end} || Backend <- [fate] ] ++ + end} || Backend <- [aevm, fate] ] ++ []. %% Check if all modules in the standard library compile -- 2.30.2