From 54663766c2fa0070f9aa1361190792d44b8b1ec9 Mon Sep 17 00:00:00 2001
From: Dan Korostelev <nadako@gmail.com>
Date: Fri, 6 Oct 2017 02:03:26 +0300
Subject: [PATCH] add new function type syntax (closes #4799)

---
 src/core/ast.ml                         |   6 +-
 src/macro/macroApi.ml                   |   4 +
 src/syntax/grammar.mly                  | 110 ++++++++++++++++--------
 src/syntax/reification.ml               |   1 +
 src/typing/typeload.ml                  |   4 +-
 std/haxe/macro/Expr.hx                  |   5 ++
 std/haxe/macro/Printer.hx               |   1 +
 tests/unit/src/unit/HelperMacros.hx     |   6 ++
 tests/unit/src/unit/issues/Issue2958.hx |   9 +-
 tests/unit/src/unit/issues/Issue4799.hx |  29 +++++++
 10 files changed, 128 insertions(+), 47 deletions(-)
 create mode 100644 tests/unit/src/unit/issues/Issue4799.hx

diff --git a/src/core/ast.ml b/src/core/ast.ml
index 745320c981c..d63858cbc2b 100644
--- a/src/core/ast.ml
+++ b/src/core/ast.ml
@@ -161,6 +161,7 @@ and complex_type =
 	| CTParent of type_hint
 	| CTExtend of placed_type_path list * class_field list
 	| CTOptional of type_hint
+	| CTNamed of placed_name * type_hint
 
 and type_hint = complex_type * pos
 
@@ -559,7 +560,9 @@ let map_expr loop (e,p) =
 			let tl = List.map tpath tl in
 			let fl = List.map cfield fl in
 			CTExtend (tl,fl)
-		| CTOptional t -> CTOptional (type_hint t)),p
+		| CTOptional t -> CTOptional (type_hint t)
+		| CTNamed (n,t) -> CTNamed (n,type_hint t)
+		),p
 	and tparamdecl t =
 		let constraints = List.map type_hint t.tp_constraints in
 		let params = List.map tparamdecl t.tp_params in
@@ -757,6 +760,7 @@ let s_expr e =
 		| CTAnonymous fl -> "{ " ^ String.concat "; " (List.map (s_class_field tabs) fl) ^ "}";
 		| CTParent(t,_) -> "(" ^ s_complex_type tabs t ^ ")"
 		| CTOptional(t,_) -> "?" ^ s_complex_type tabs t
+		| CTNamed((n,_),(t,_)) -> n ^ ":" ^ s_complex_type tabs t
 		| CTExtend (tl, fl) -> "{> " ^ String.concat " >, " (List.map (s_complex_type_path tabs) tl) ^ ", " ^ String.concat ", " (List.map (s_class_field tabs) fl) ^ " }"
 	and s_class_field tabs f =
 		match f.cff_doc with
diff --git a/src/macro/macroApi.ml b/src/macro/macroApi.ml
index 87fec9cad19..19c2dd6c757 100644
--- a/src/macro/macroApi.ml
+++ b/src/macro/macroApi.ml
@@ -433,6 +433,8 @@ and encode_ctype t =
 		4, [encode_array (List.map encode_path tl); encode_array (List.map encode_field fields)]
 	| CTOptional t ->
 		5, [encode_ctype t]
+	| CTNamed (n,t) ->
+		6, [encode_placed_name n; encode_ctype t]
 	in
 	encode_enum ~pos:(Some (pos t)) ICType tag pl
 
@@ -732,6 +734,8 @@ and decode_ctype t =
 		CTExtend (List.map decode_path (decode_array tl), List.map decode_field (decode_array fl))
 	| 5, [t] ->
 		CTOptional (decode_ctype t)
+	| 6, [n;t] ->
+		CTNamed ((decode_string n,p), decode_ctype t)
 	| _ ->
 		raise Invalid_expr),p
 
diff --git a/src/syntax/grammar.mly b/src/syntax/grammar.mly
index bcaeabc5035..53f50371cfe 100644
--- a/src/syntax/grammar.mly
+++ b/src/syntax/grammar.mly
@@ -377,15 +377,31 @@ and parse_type_opt = parser
 	| [< t = parse_type_hint >] -> Some t
 	| [< >] -> None
 
-and parse_complex_type s =
-	let t = parse_complex_type_inner s in
-	parse_complex_type_next t s
+and parse_complex_type s = parse_complex_type_maybe_named false s
+
+and parse_complex_type_maybe_named allow_named = parser
+	| [< '(POpen,p1); tl = psep Comma (parse_complex_type_maybe_named true); '(PClose,p2); s >] ->
+		begin match tl with
+		| [] | [(CTNamed _,_)] ->
+			(* it was () or (a:T) - clearly a new function type syntax, proceed with parsing return type *)
+			parse_function_type_next tl p1 s
+		| [t] ->
+			(* it was some single unnamed type in parenthesis - use old function type syntax  *)
+			let t = CTParent t,punion p1 p2 in
+			parse_complex_type_next t s
+		| _ ->
+			(* it was multiple arguments - clearly a new function type syntax, proceed with parsing return type  *)
+			parse_function_type_next tl p1 s
+		end
+	| [< s >] ->
+		let t = parse_complex_type_inner allow_named s in
+		parse_complex_type_next t s
 
 and parse_structural_extension = parser
 	| [< '(Binop OpGt,_); t = parse_type_path; '(Comma,_); s >] ->
 		t
 
-and parse_complex_type_inner = parser
+and parse_complex_type_inner allow_named = parser
 	| [< '(POpen,p1); t = parse_complex_type; '(PClose,p2) >] -> CTParent t,punion p1 p2
 	| [< '(BrOpen,p1); s >] ->
 		(match s with parser
@@ -397,8 +413,18 @@ and parse_complex_type_inner = parser
 			| [< l,p2 = parse_class_fields true p1 >] -> CTExtend (tl,l),punion p1 p2)
 		| [< l,p2 = parse_class_fields true p1 >] -> CTAnonymous l,punion p1 p2
 		| [< >] -> serror())
-	| [< '(Question,p1); t,p2 = parse_complex_type_inner >] ->
+	| [< '(Question,p1); t,p2 = parse_complex_type_inner allow_named >] ->
 		CTOptional (t,p2),punion p1 p2
+	| [< n = dollar_ident; s >] ->
+		(match s with parser
+		| [< '(DblDot,_) when allow_named; t = parse_complex_type >] ->
+			let p1 = snd n in
+			let p2 = snd t in
+			CTNamed (n,t),punion p1 p2
+		| [< s >] ->
+			let n,p = n in
+			let t,p = parse_type_path2 None [] n p s in
+			CTPath t,p)
 	| [< t,p = parse_type_path >] ->
 		CTPath t,p
 
@@ -406,42 +432,45 @@ and parse_type_path s = parse_type_path1 None [] s
 
 and parse_type_path1 p0 pack = parser
 	| [< name, p1 = dollar_ident_macro pack; s >] ->
-		if is_lower_ident name then
-			(match s with parser
-			| [< '(Dot,p) >] ->
-				if is_resuming p then
-					raise (TypePath (List.rev (name :: pack),None,false))
-				else
-					parse_type_path1 (match p0 with None -> Some p1 | Some _ -> p0) (name :: pack) s
-			| [< '(Semicolon,_) >] ->
-				error (Custom "Type name should start with an uppercase letter") p1
-			| [< >] -> serror())
-		else
-			let sub,p2 = (match s with parser
-				| [< '(Dot,p); s >] ->
-					(if is_resuming p then
-						raise (TypePath (List.rev pack,Some (name,false),false))
-					else match s with parser
-						| [< '(Const (Ident name),p2) when not (is_lower_ident name) >] -> Some name,p2
-						| [< '(Binop OpOr,_) when do_resume() >] ->
-							set_resume p;
-							raise (TypePath (List.rev pack,Some (name,false),false))
-						| [< >] -> serror())
-				| [< >] -> None,p1
-			) in
-			let params,p2 = (match s with parser
-				| [< '(Binop OpLt,_); l = psep Comma parse_type_path_or_const; '(Binop OpGt,p2) >] -> l,p2
-				| [< >] -> [],p2
-			) in
-			{
-				tpackage = List.rev pack;
-				tname = name;
-				tparams = params;
-				tsub = sub;
-			},punion (match p0 with None -> p1 | Some p -> p) p2
+		parse_type_path2 p0 pack name p1 s
 	| [< '(Binop OpOr,_) when do_resume() >] ->
 		raise (TypePath (List.rev pack,None,false))
 
+and parse_type_path2 p0 pack name p1 s =
+	if is_lower_ident name then
+		(match s with parser
+		| [< '(Dot,p) >] ->
+			if is_resuming p then
+				raise (TypePath (List.rev (name :: pack),None,false))
+			else
+				parse_type_path1 (match p0 with None -> Some p1 | Some _ -> p0) (name :: pack) s
+		| [< '(Semicolon,_) >] ->
+			error (Custom "Type name should start with an uppercase letter") p1
+		| [< >] -> serror())
+	else
+		let sub,p2 = (match s with parser
+			| [< '(Dot,p); s >] ->
+				(if is_resuming p then
+					raise (TypePath (List.rev pack,Some (name,false),false))
+				else match s with parser
+					| [< '(Const (Ident name),p2) when not (is_lower_ident name) >] -> Some name,p2
+					| [< '(Binop OpOr,_) when do_resume() >] ->
+						set_resume p;
+						raise (TypePath (List.rev pack,Some (name,false),false))
+					| [< >] -> serror())
+			| [< >] -> None,p1
+		) in
+		let params,p2 = (match s with parser
+			| [< '(Binop OpLt,_); l = psep Comma parse_type_path_or_const; '(Binop OpGt,p2) >] -> l,p2
+			| [< >] -> [],p2
+		) in
+		{
+			tpackage = List.rev pack;
+			tname = name;
+			tparams = params;
+			tsub = sub;
+		},punion (match p0 with None -> p1 | Some p -> p) p2
+
 and type_name = parser
 	| [< '(Const (Ident name),p) >] ->
 		if is_lower_ident name then
@@ -467,6 +496,11 @@ and parse_complex_type_next (t : type_hint) = parser
 			CTFunction ([t] , (t2,p2)),punion (pos t) p2)
 	| [< >] -> t
 
+and parse_function_type_next tl p1 = parser
+	| [< '(Arrow,_); tret = parse_complex_type_inner false >] ->
+		CTFunction (tl,tret), punion p1 (snd tret)
+	| [< >] -> serror ()
+
 and parse_type_anonymous opt final = parser
 	| [< '(Question,_) when not opt; s >] -> parse_type_anonymous true final s
 	| [< '(Kwd Final,_) when not opt && not final; s >] -> parse_type_anonymous opt true s
diff --git a/src/syntax/reification.ml b/src/syntax/reification.ml
index 52c4ad45c9b..13ca8d75bed 100644
--- a/src/syntax/reification.ml
+++ b/src/syntax/reification.ml
@@ -136,6 +136,7 @@ let reify in_macro =
 		| CTParent t -> ct "TParent" [to_type_hint t p]
 		| CTExtend (tl,fields) -> ct "TExtend" [to_array to_tpath tl p; to_array to_cfield fields p]
 		| CTOptional t -> ct "TOptional" [to_type_hint t p]
+		| CTNamed (n,t) -> ct "TNamed" [to_placed_name n; to_type_hint t p]
 	and to_type_hint (t,p) _ =
 		(* to_obj ["type",to_ctype t p;"pos",to_pos p] p *)
 		to_ctype (t,p) p
diff --git a/src/typing/typeload.ml b/src/typing/typeload.ml
index c2beadb83fe..b3a14cf5644 100644
--- a/src/typing/typeload.ml
+++ b/src/typing/typeload.ml
@@ -546,6 +546,7 @@ and load_complex_type ctx allow_display p (t,pn) =
 	| CTParent t -> load_complex_type ctx allow_display p t
 	| CTPath t -> load_instance ~allow_display ctx (t,pn) false p
 	| CTOptional _ -> error "Optional type not allowed here" p
+	| CTNamed _ -> error "Named type not allowed here" p
 	| CTExtend (tl,l) ->
 		(match load_complex_type ctx allow_display p (CTAnonymous l,p) with
 		| TAnon a as ta ->
@@ -685,7 +686,8 @@ and load_complex_type ctx allow_display p (t,pn) =
 		| _ ->
 			TFun (List.map (fun t ->
 				let t, opt = (match fst t with CTOptional t -> t, true | _ -> t,false) in
-				"",opt,load_complex_type ctx allow_display p t
+				let n,t = (match fst t with CTNamed (n,t) -> (fst n), t | _ -> "", t) in
+				n,opt,load_complex_type ctx allow_display p t
 			) args,load_complex_type ctx allow_display p r)
 
 and init_meta_overloads ctx co cf =
diff --git a/std/haxe/macro/Expr.hx b/std/haxe/macro/Expr.hx
index f0ce9a13453..ff54336cf9f 100644
--- a/std/haxe/macro/Expr.hx
+++ b/std/haxe/macro/Expr.hx
@@ -570,6 +570,11 @@ enum ComplexType {
 		Represents an optional type.
 	**/
 	TOptional( t : ComplexType );
+
+	/**
+		Represents a type with a name.
+	**/
+	TNamed( n : String, t : ComplexType );
 }
 
 /**
diff --git a/std/haxe/macro/Printer.hx b/std/haxe/macro/Printer.hx
index 88181cba4bb..ccf8cdc912b 100644
--- a/std/haxe/macro/Printer.hx
+++ b/std/haxe/macro/Printer.hx
@@ -120,6 +120,7 @@ class Printer {
 		case TAnonymous(fields): "{ " + [for (f in fields) printField(f) + "; "].join("") + "}";
 		case TParent(ct): "(" + printComplexType(ct) + ")";
 		case TOptional(ct): "?" + printComplexType(ct);
+		case TNamed(n,ct): n + ":" + printComplexType(ct);
 		case TExtend(tpl, fields): '{> ${tpl.map(printTypePath).join(" >, ")}, ${fields.map(printField).join(", ")} }';
 	}
 
diff --git a/tests/unit/src/unit/HelperMacros.hx b/tests/unit/src/unit/HelperMacros.hx
index 1876ca8ebc1..4259a0f6df7 100644
--- a/tests/unit/src/unit/HelperMacros.hx
+++ b/tests/unit/src/unit/HelperMacros.hx
@@ -7,6 +7,12 @@ class HelperMacros {
 		return macro $v { Std.string(Date.now()) };
 	}
 
+	static public macro function typeString(e) {
+		var typed = haxe.macro.Context.typeExpr(e);
+		var s = haxe.macro.TypeTools.toString(typed.t);
+		return macro $v{s};
+	}
+
 	static public macro function typedAs(actual:haxe.macro.Expr, expected:haxe.macro.Expr) {
 		var tExpected = haxe.macro.Context.typeof(expected);
 		var tActual = haxe.macro.Context.typeof(actual);
diff --git a/tests/unit/src/unit/issues/Issue2958.hx b/tests/unit/src/unit/issues/Issue2958.hx
index 96450984181..7afc365c18b 100644
--- a/tests/unit/src/unit/issues/Issue2958.hx
+++ b/tests/unit/src/unit/issues/Issue2958.hx
@@ -1,5 +1,7 @@
 package unit.issues;
 
+import unit.HelperMacros.typeString;
+
 private typedef Asset<@:const T> = String;
 
 class Issue2958 extends Test {
@@ -9,11 +11,4 @@ class Issue2958 extends Test {
 		   "unit.issues._Issue2958.Asset<[\"test\", 1]>"
 		);
 	}
-
-	static macro function typeString(e)
-	{
-		var typed = haxe.macro.Context.typeExpr(e);
-		var s = haxe.macro.TypeTools.toString(typed.t);
-		return macro $v{s};
-	}
 }
\ No newline at end of file
diff --git a/tests/unit/src/unit/issues/Issue4799.hx b/tests/unit/src/unit/issues/Issue4799.hx
new file mode 100644
index 00000000000..3627947bf2f
--- /dev/null
+++ b/tests/unit/src/unit/issues/Issue4799.hx
@@ -0,0 +1,29 @@
+package unit.issues;
+
+import unit.HelperMacros.typeString;
+
+class Issue4799 extends Test {
+	function test() {
+		eq(typeString((null : Int)), "Int");
+		eq(typeString((null : (Int))), "Int");
+		eq(typeString((null : (Int,Int)->Int)), "Int -> Int -> Int");
+		eq(typeString((null : (a:Int,Int)->Int)), "a : Int -> Int -> Int");
+		eq(typeString((null : (a:Int, b:Int)->Int)), "a : Int -> b : Int -> Int");
+		eq(typeString((null : (a:Int, Int, ?String->Void, ?b:(Int->Int)->Int, c:(a:Int)->Void)->Int)),
+		                      "a : Int -> Int -> (?String -> Void) -> ?b : ((Int -> Int) -> Int) -> c : (a : Int -> Void) -> Int");
+		t(parseError("(Int,Int)->Int->Int"));
+		t(parseError("(a:Int)->Int->Int"));
+		eq(typeString((null : (Int,Int)->(Int->Int))), "Int -> Int -> (Int -> Int)");
+		eq(typeString((null : (a:Int)->(Int->Int))), "a : Int -> (Int -> Int)");
+	}
+
+	static macro function parseError(s:String) {
+		var s = "(null : " + s + ")";
+		try {
+			haxe.macro.Context.parseInlineString(s, haxe.macro.Context.currentPos());
+			return macro false;
+		} catch (e:Any) {
+			return macro true;
+		}
+	}
+}