Browse Source

Allow using anonymous functions in operator expressions (#12015)

* Add more tests for immediately invoked functions

Adds more cases that shouldn't be parsed as immediately invoked
functions.

* Allow functions to be used in expressions

* Handle block level functions separately

Inline block functions are already handled here, so it is more
consistent to handle all block leve functions at this point.

* Add test for using functions expressions

* Disallow function in array access and postfix expr
tobil4sk 4 months ago
parent
commit
61d7dfec5f

+ 1 - 1
src/core/ast.ml

@@ -466,7 +466,7 @@ let gen_doc_text_opt = Option.map gen_doc_text
 
 let get_own_doc_opt = Option.map_default (fun d -> d.doc_own) None
 
-let is_postfix (e,_) op = match op with
+let is_postfix op = match op with
 	| Increment | Decrement | Not -> true
 	| Neg | NegBits | Spread -> false
 

+ 11 - 2
src/syntax/grammar.ml

@@ -1172,6 +1172,7 @@ and parse_block_var ctx = function%parser
 and parse_block_elt ctx s = match%parser s with
 	| [ [%let vl,p = parse_block_var ctx] ] ->
 		(EVars vl,p)
+	| [ (Kwd Function,p1); [%let e = parse_function ctx p1 false]; [%let _s = semicolon ctx] ]  -> e
 	| [ (Kwd Inline,p1) ] ->
 		begin match%parser s with
 		| [ (Kwd Function,_); [%let e = parse_function ctx p1 true]; [%let _s = semicolon ctx] ] -> e
@@ -1494,7 +1495,15 @@ and expr (ctx : parser_ctx) s = match%parser s with
 				syntax_error ctx (Expected [")";",";":"]) s (expr_next ctx (EParenthesis e, punion p1 (pos e)) s))
 		)
 	| [ (BkOpen,p1); [%let e = parse_array_decl ctx p1] ] -> expr_next ctx e s
-	| [ (Kwd Function,p1); [%let e = parse_function ctx p1 false]; ] -> e
+	| [ (Kwd Function,p1); [%let e = parse_function ctx p1 false]; [%s s]; ] ->
+		begin match Stream.peek s with
+		| Some (POpen,_) | Some (BkOpen,_) ->
+			e
+		| Some (Unop op, _) when is_postfix op ->
+			e
+		| _ ->
+			expr_next ctx e s
+		end
 	| [ (Unop op,p1); [%let e = expr ctx] ] -> make_unop op e p1
 	| [ (Spread,p1); [%let e = expr ctx] ] -> make_unop Spread e (punion p1 (pos e))
 	| [ (Binop OpSub,p1); [%let e = expr ctx] ] ->
@@ -1618,7 +1627,7 @@ and expr_next' ctx e1 s = match%parser s with
 			make_binop OpGt e1 e2)
 	| [ (Binop op,_); [%let e2 = secure_expr ctx] ] -> make_binop op e1 e2
 	| [ (Spread,_); [%let e2 = secure_expr ctx] ] -> make_binop OpInterval e1 e2
-	| [ (Unop op,p) ] when is_postfix e1 op ->
+	| [ (Unop op,p) ] when is_postfix op ->
 		expr_next ctx (EUnop (op,Postfix,e1), punion (pos e1) p) s
 	| [ (Question,_); [%let e2 = expr ctx] ] ->
 		begin match%parser s with

+ 14 - 0
tests/misc/projects/Issue10782/ArrayAccess.hx

@@ -0,0 +1,14 @@
+class C {
+	public function new() {}
+
+	public function call() {
+		trace("called!");
+	}
+}
+
+function main() {
+	final f = function() {
+		trace("f!");
+	}
+	[new C()][0].call();
+}

+ 22 - 0
tests/misc/projects/Issue10782/Main.hx

@@ -0,0 +1,22 @@
+@:callable
+abstract Function(Int->Int) to Int->Int from Int->Int {
+	@:op(a * b)
+	static inline function chain(a:Function, b:Function):Function {
+		return function(i) {
+			return a(b(i));
+		};
+	}
+}
+
+class Main {
+	static function main() {
+		var f:Function = function(a) {
+			return a + 1;
+		};
+
+		var g = function(a) { return a * a; } * f;
+		if (g(10) != 121) {
+			throw "Incorrect function return value";
+		}
+	}
+}

+ 7 - 0
tests/misc/projects/Issue10782/NoPostfix.hx

@@ -0,0 +1,7 @@
+function main() {
+	var i = 0;
+	final f = function() {
+		trace("f!");
+	}
+	--i;
+}

+ 1 - 0
tests/misc/projects/Issue10782/compile-array-access.hxml

@@ -0,0 +1 @@
+--run ArrayAccess

+ 1 - 0
tests/misc/projects/Issue10782/compile-array-access.hxml.stdout

@@ -0,0 +1 @@
+ArrayAccess.hx:5: called!

+ 1 - 0
tests/misc/projects/Issue10782/compile-no-postfix.hxml

@@ -0,0 +1 @@
+--run NoPostfix

+ 1 - 0
tests/misc/projects/Issue10782/compile.hxml

@@ -0,0 +1 @@
+--run Main

+ 24 - 0
tests/misc/projects/Issue5854/Main.hx

@@ -0,0 +1,24 @@
+class Main {
+	static function main() {
+		final f = (_) -> {
+			throw "This function shouldn't have been called!!";
+		}
+		("hello");
+
+		final f = function(_) {
+			throw "This function shouldn't have been called!!";
+		}
+		("hello");
+
+		// named locals
+		function f(_) {
+			throw "This function shouldn't have been called!!";
+		}
+		("hello");
+
+		final g = function f(_) {
+			throw "This function shouldn't have been called!!";
+		}
+		("hello");
+	}
+}

+ 0 - 10
tests/misc/projects/Issue5854/NamedLocal.hx

@@ -1,10 +0,0 @@
-class NamedLocal {
-	static function main() {
-		// shouldn't be parsed as a call
-		function test(str) {
-			trace(str);
-			throw "This function shouldn't have been called!!";
-		}
-		("hello");
-	}
-}

+ 1 - 0
tests/misc/projects/Issue5854/Nested.hx

@@ -0,0 +1 @@
+final a = (function() {return 1;} ());

+ 6 - 1
tests/misc/projects/Issue5854/UnnamedLocal.hx → tests/misc/projects/Issue5854/UnnamedLValue.hx

@@ -1,8 +1,13 @@
-class UnnamedLocal {
+class UnnamedLValue {
 	static function main() {
 		function(str) {
 			trace(str);
 		}
 		("hello");
+
+		(str) -> {
+			trace(str);
+		}
+		("hello");
 	}
 }

+ 1 - 1
tests/misc/projects/Issue5854/compile-fail.hxml

@@ -1 +1 @@
---main UnnamedLocal
+--main UnnamedLValue

+ 2 - 1
tests/misc/projects/Issue5854/compile-fail.hxml.stderr

@@ -1 +1,2 @@
-UnnamedLocal.hx:3: lines 3-5 : Unnamed lvalue functions are not supported
+UnnamedLValue.hx:3: lines 3-5 : Unnamed lvalue functions are not supported
+UnnamedLValue.hx:8: lines 8-10 : Unnamed lvalue functions are not supported

+ 1 - 0
tests/misc/projects/Issue5854/compile-nested-fail.hxml

@@ -0,0 +1 @@
+--main Nested

+ 1 - 0
tests/misc/projects/Issue5854/compile-nested-fail.hxml.stderr

@@ -0,0 +1 @@
+Nested.hx:1: characters 37-38 : Expected ) or , or :

+ 1 - 1
tests/misc/projects/Issue5854/compile.hxml

@@ -1 +1 @@
---run NamedLocal
+--main Main

+ 0 - 0
tests/misc/projects/Issue5854/compile.hxml.stderr