Bläddra i källkod

Add arrow functions (#6209)

* add arrow functions

* add arrow function syntax tests

* exclude HL from arrow function syntax tests for now

* also exclude AS3 from arrow function syntax tests for now

* make completion work in arrow function bodies

* change body parse result type

* add some display tests for arrow functions

* add a toplevel display test case
ousado 8 år sedan
förälder
incheckning
4aa212701f

+ 61 - 5
src/syntax/parser.mly

@@ -1292,6 +1292,34 @@ and parse_function p1 inl = parser
 		with
 			Display e -> display (make e))
 
+and arrow_expr = parser
+	| [< '(Arrow,_); s >] -> try let e = expr s in e,false with Display e -> e,true
+	| _ -> serror()
+
+and arrow_function p1 al er =
+	let make e =
+		EFunction(None, { f_params = []; f_type = None; f_args = al; f_expr = Some (EReturn(Some e), (snd e));  }), punion p1 (pos e)
+	in
+	let e,display_error = er in
+	if display_error then display (make e) else make e
+
+and arrow_ident_checktype e = (match e with
+	| EConst(Ident n),p -> (n,p),None
+	| ECheckType((EConst(Ident n),p),(t,pt)),_ -> (n,p),(Some (t,pt))
+	| _ -> serror())
+
+and arrow_first_param e =
+	(match fst e with
+	| EConst(Ident n) ->
+		(n,snd e),false,[],None,None
+	| EBinop(OpAssign,e1,e2)
+	| EParenthesis(EBinop(OpAssign,e1,e2),_) ->
+		let (np,tpt) = arrow_ident_checktype e1 in np,true,[],tpt,(Some e2)
+	| EParenthesis(e) ->
+		let (np,tpt) = arrow_ident_checktype e in np,false,[],tpt,None
+	| _ ->
+		serror())
+
 and expr = parser
 	| [< (name,params,p) = parse_meta_entry; s >] ->
 		begin try
@@ -1352,11 +1380,36 @@ and expr = parser
 			if do_resume() then (ENew(t,[]),punion p1 (pos t))
 			else serror()
 		end
-	| [< '(POpen,p1); e = expr; s >] -> (match s with parser
-		| [< '(PClose,p2); s >] -> expr_next (EParenthesis e, punion p1 p2) s
-		| [< t,pt = parse_type_hint_with_pos; '(PClose,p2); s >] -> expr_next (EParenthesis (ECheckType(e,(t,pt)),punion p1 p2), punion p1 p2) s
-		| [< '(Const (Ident "is"),p_is); t = parse_type_path; '(PClose,p2); >] -> expr_next (make_is e t (punion p1 p2) p_is) s
-		| [< >] -> serror())
+	| [< '(POpen,p1); s >] -> (match s with parser
+		| [< '(PClose,p2); er = arrow_expr; s >] ->
+			arrow_function p1 [] er
+		| [< '(Question,p2); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
+			let al = (match al with | (np,_,_,topt,e) :: al -> (np,true,[],topt,e) :: al | _ -> assert false ) in
+			arrow_function p1 al er
+		| [<  e = expr; s >] -> (match s with parser
+			| [< '(PClose,p2); s >] -> expr_next (EParenthesis e, punion p1 p2) s
+			| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
+				arrow_function p1 ((arrow_first_param e) :: al) er
+			| [< t,pt = parse_type_hint_with_pos; s >] -> (match s with parser
+				| [< '(PClose,p2); s >] -> expr_next (EParenthesis (ECheckType(e,(t,pt)),punion p1 p2), punion p1 p2) s
+				| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; s >] ->
+					arrow_function p1 ((arrow_first_param e) :: al) er
+				| [< '((Binop OpAssign),p2); ea1 = expr; s >] ->
+					let with_args al er = (match fst e with
+						| EConst(Ident n) ->
+							arrow_function p1 (((n,snd e),true,[],(Some(t,pt)),(Some ea1)) :: al) er
+						| _ -> serror())
+					in
+					(match s with parser
+					| [< '(PClose,p2); er = arrow_expr; >] ->
+						with_args [] er
+					| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
+						with_args al er
+					| [< >] -> serror())
+				| [< >] -> serror())
+			| [< '(Const (Ident "is"),p_is); t = parse_type_path; '(PClose,p2); >] -> expr_next (make_is e t (punion p1 p2) p_is) s
+			| [< >] -> serror())
+		)
 	| [< '(BkOpen,p1); l = parse_array_decl; '(BkClose,p2); s >] -> expr_next (EArrayDecl l, punion p1 p2) s
 	| [< '(Kwd Function,p1); e = parse_function p1 false; >] -> e
 	| [< '(Unop op,p1) when is_prefix op; e = expr >] -> make_unop op e p1
@@ -1428,6 +1481,9 @@ and expr_next e1 = parser
 	| [< '(POpen,p1); e = parse_call_params (fun el p2 -> (ECall(e1,el)),punion (pos e1) p2) p1; s >] -> expr_next e s
 	| [< '(BkOpen,_); e2 = expr; '(BkClose,p2); s >] ->
 		expr_next (EArray (e1,e2), punion (pos e1) p2) s
+	| [< '(Arrow,pa); s >] ->
+		let er = try let e = expr s in e,false with Display e -> e,true
+		in arrow_function (snd e1) [arrow_first_param e1] er
 	| [< '(Binop OpGt,p1); s >] ->
 		(match s with parser
 		| [< '(Binop OpGt,p2) when p1.pmax = p2.pmin; s >] ->

+ 49 - 0
tests/display/src/cases/ArrowFunctions.hx

@@ -0,0 +1,49 @@
+package cases;
+
+class ArrowFunctions extends DisplayTestCase {
+
+    /**
+    var obj = { foo : 1 };
+    var f = () -> obj.{-1-}
+    var f = () -> {
+        [1].{-2-}
+    }
+    **/
+    @:funcCode function testBodyCompletion1(){
+        eq(true, hasField(fields(pos(1)), "foo", "Int"));
+        eq(true, hasField(fields(pos(2)), "copy", "Void -> Array<Int>"));
+    }
+
+    /**
+    enum E { EA; EB; EC; }
+    class SomeClass {
+        static function sf(){
+            (e:E) -> e.{-1-}
+        }
+    }
+    **/
+    function testBodyCompletion2(){
+        eq(true, hasField(fields(pos(1)), "getName", "Void -> String"));
+    }
+
+    /**
+    var arr = [1,2,3,4,5];
+    arr.map( a{-1-} -> a{-2-} + 1 )
+    **/
+    @:funcCode function testHover(){
+        eq("Int", type(pos(1)));
+        eq("Int", type(pos(2)));
+    }
+
+
+    /**
+    x -> { {-1-}
+    **/
+    @:funcCode function testTopLevel(){
+        eq(true, Toplevel.hasToplevel(toplevel(pos(1)), "local", "x"));
+    }
+
+    /*public static function hasToplevel(a:Array<ToplevelElement>, kind:String, name:String):Bool {
+		return a.exists(function(t) return t.kind == kind && t.name == name);
+    }*/
+}

+ 153 - 0
tests/unit/src/unit/TestArrowFunctions.hx

@@ -0,0 +1,153 @@
+package unit;
+
+abstract W(Int) from Int {
+	@:to inline public function toString():String return '$this';
+}
+
+class TestArrowFunctions extends Test {
+
+	var f0_0: Void -> Int;
+	var f0_1: Void -> W;
+
+	var f1_0: Int->Int;
+	var f1_1: ?Int->Int;
+
+	var f2_0: Int->Int;
+
+	var f3_0: Int->Int->Int;
+	var f3_1: ?Int->String->Int;
+	var f3_2: Int->?Int->Int;
+
+	var f4:   Int->(Int->Int);
+	var f5:   Int->Int->(Int->Int);
+	var f6_a: Int->(Int->(Int->Int));
+	var f6_b: Int->(Int->(Int->Int));
+	var f7:   (Int->Int)->(Int->Int);
+	var f8:   Int -> String;
+
+	var arr: Array<Int->Int> = [];
+	var map: Map<Int,Int->Int> = new Map();
+	var obj: { f : Int->Int };
+
+	var v0:   Int;
+	var v1:   String;
+
+	var maybe : Void -> Bool;
+
+	function testSyntax(){
+
+		// skipping hl for now due to variance errors:
+		// Don't know how to cast ref(i32) to null(i32) see issue #6210
+		#if !(hl || as3)
+
+		maybe = () -> Math.random() > 0.5;
+
+		v0 = (123);
+		v0 = (123:Int);
+
+		f0_0 = function () return 1;
+		f0_0 = () -> 1;
+
+		f0_0 = (() -> 1);
+		f0_0 = (() -> 1:Void->Int);
+		f0_0 = cast (() -> 1:Void->Int);
+
+		v0 = f0_0();
+
+		f0_1 = function () : W return 1;
+		v1 = f0_1();
+
+		f0_1 = () -> (1:W);
+		v1 = f0_1();
+
+		f1_0 = function (a:Int) return a;
+		f1_1 = function (?a:Int) return a;
+
+		f1_0 = a -> a;
+		v0 = f1_0(1);
+
+		f1_1 = (?a) -> a;
+		v0 = f1_1(1);
+
+		f1_1 = (?a:Int) -> a;
+		v0 = f1_1(1);
+
+		f1_1 = (a:Int=1) -> a;
+		v0 = f1_1();
+
+		f1_1 = (?a:Int=1) -> a;
+		v0 = f1_1();
+
+		f1_1 = function (a=2) return a;
+		eq(f1_1(),2);
+
+		f1_1 = (a=2) -> a;
+		eq(f1_1(),2);
+
+		f3_0 = function (a:Int, b:Int) return a + b;
+		f3_1 = function (?a:Int, b:String) return a + b.length;
+		f3_2 = function (a:Int, ?b:Int) return a + b;
+
+		f3_0 = (a:Int, b:Int)  -> a + b;
+		f3_1 = (?a:Int, b:String) -> a + b.length;
+		f3_2 = (a:Int, ?b:Int) -> a + b;
+
+		#if !flash
+		f3_1 = function (a=1, b:String) return a + b.length;
+		eq(f3_1("--"),3);
+
+		f3_1 = function (?a:Int=1, b:String) return a + b.length;
+		eq(f3_1("--"),3);
+
+		f3_2 = function (a:Int, b=2) return a + b;
+		eq(f3_2(1),3);
+
+		f3_1 = (a=1, b:String) -> a + b.length;
+		eq(f3_1("--"),3);
+
+		f3_1 = (a:Int=1, b:String) -> a + b.length;
+		eq(f3_1("--"),3);
+
+		f3_1 = (?a:Int=1, b:String) -> a + b.length;
+		eq(f3_1("--"),3);
+
+		f3_2 = (a:Int, b=2) -> a + b;
+		eq(f3_2(1),3);
+		#end
+
+		f4 = function (a) return function (b) return a + b;
+		f4 = a -> b -> a + b;
+
+		f5 = function (a,b) return function (c) return a + b + c;
+		f5 = (a, b) -> c -> a + b + c;
+
+		f6_a = function (a) return function (b) return function (c) return a + b + c;
+		f6_b = a -> b -> c -> a + b + c;
+		eq(f6_a(1)(2)(3),f6_b(1)(2)(3));
+
+		f7 = function (f:Int->Int) return f;
+		f7 = f -> f;
+		f7 = (f:Int->Int) -> f;
+		f7 = maybe() ? f -> f : f -> g -> f(g);
+		f7 = switch maybe() {
+			case true:  f -> f;
+			case false: f -> g -> f(g);
+		};
+
+		f8 = (a:Int) -> ('$a':String);
+
+		arr = [for (i in 0...5) a -> a * i];
+		arr = [a -> a + a, b -> b + b, c -> c + c];
+		arr.map( f -> f(2) );
+
+		var arr2:Array<Int->W> = [for (f in arr) x -> f(x)];
+
+		map = [1 => a -> a + a, 2 => a -> a + a, 3 => a -> a + a];
+
+		obj = { f : a -> a + a };
+
+		#end
+
+	}
+
+}

+ 2 - 1
tests/unit/src/unit/TestMain.hx

@@ -61,6 +61,7 @@ class TestMain {
 			new TestOrder(),
 			new TestGADT(),
 			new TestGeneric(),
+			new TestArrowFunctions(),
 			#if !no_pattern_matching
 			new TestMatch(),
 			#end
@@ -144,4 +145,4 @@ class TestMain {
 		}
 		#end
 	}
-}
+}