From b3767071a83902747251fb5dcf0581c3a0aaa530 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Fri, 3 Jun 2022 13:12:23 +0400 Subject: [PATCH] Allow binary operators to be used as lambdas (#385) * Add operator lambdas * Do not register anonymous functions as called functions * Add tests * Update CHANGELOG * Update the docs * Do not allow (..) to be used as a lambda * Rename the function sum to any --- CHANGELOG.md | 5 +++++ docs/sophia_syntax.md | 1 + src/aeso_ast_infer_types.erl | 5 ++++- src/aeso_parser.erl | 15 ++++++++++++++- test/aeso_compiler_tests.erl | 8 ++++++++ test/contracts/operator_lambdas.aes | 14 ++++++++++++++ 6 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/contracts/operator_lambdas.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 486c81d..218866c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ``` [1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3])) ``` +- Allow binary operators to be used as lambdas + ``` + function sum(l : list(int)) : int = foldl((+), 0, l) + function logical_and(x, y) = (&&)(x, y) + ``` ### Changed - Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added. - Ban empty record definitions (e.g. `record r = {}` would give an error). diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index b9c6aa0..a55fa60 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -200,6 +200,7 @@ switch(f(x)) ```c Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1 + | '(' BinOp ')' // Operator lambda (+) | 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x | Expr ':' Type // Type annotation 5 : int | Expr BinOp Expr // Binary operator x + y diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 8af28ae..d7fd152 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1641,7 +1641,10 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> GeneralResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann), when_warning(warn_unused_functions, - fun() -> register_function_call(Namespace ++ qname(CurrentFun), Name) end), + fun() -> if element(1, Name) == lam -> ok; + true -> register_function_call(Namespace ++ qname(CurrentFun), Name) + end + end), unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end), add_constraint( diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 7e5fa62..9175e0f 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -335,7 +335,7 @@ exprAtom() -> ?LAZY_P(begin Expr = ?LAZY_P(expr()), choice( - [ id_or_addr(), con(), token(qid), token(qcon) + [ id_or_addr(), con(), token(qid), token(qcon), binop_as_lam() , token(bytes), token(string), token(char) , token(int) , ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int))) @@ -472,6 +472,19 @@ id() -> token(id). tvar() -> token(tvar). str() -> token(string). +binop_as_lam() -> + BinOps = ['&&', '||', + '+', '-', '*', '/', '^', 'mod', + '==', '!=', '<', '>', '<=', '=<', '>=', + '::', '++', '|>'], + OpToLam = fun(Op = {_, Ann}) -> + IdL = {id, Ann, "l"}, + IdR = {id, Ann, "r"}, + Arg = fun(Id) -> {arg, Ann, Id, type_wildcard(Ann)} end, + {lam, Ann, [Arg(IdL), Arg(IdR)], infix(IdL, Op, IdR)} + end, + ?RULE(parens(choice(lists:map(fun token/1, BinOps))), OpToLam(_1)). + token(Tag) -> ?RULE(tok(Tag), case _1 of diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index fd1c9a1..13f07d0 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -795,6 +795,14 @@ failing_contracts() -> [<> ]) + , ?TYPE_ERROR(operator_lambdas, + [< int` and `(int) => 'a`\n" + "when checking the application of\n" + " `(l : _, r : _) => l + r : (int, int) => int`\n" + "to arguments\n" + " `1 : int`">> + ]) , ?TYPE_ERROR(warnings, [<>, diff --git a/test/contracts/operator_lambdas.aes b/test/contracts/operator_lambdas.aes new file mode 100644 index 0000000..9f5b41e --- /dev/null +++ b/test/contracts/operator_lambdas.aes @@ -0,0 +1,14 @@ +include "List.aes" + +contract C = + type state = int + + function any(l : list(bool)) : bool = List.foldl((||), false, l) + + entrypoint init() = + let bad_application = (+)(1) + let good_application = (-)(3, 4) + let op_var = (+) + let op_var_application = op_var(3, 4) + + good_application + op_var_application \ No newline at end of file