浏览代码

Variable / Rest arguments (#9961)

* js, php, lua, python

* spread operator

* extract iterators into separate modules in haxe.iterators

* more tests

* better error msg

* [php] update for additional API

* [js] additional API; spread operator support

* removed @:coreType from haxe.Rest

* [inliner] support inlining of functions with rest arguments

* [php] optimize array decl if immediately converted to native array

* [inliner] fix rest args inlining

* [php] don't optimize ArrayDecl -> NativeArray if passed to php.Ref<T>

* more tests

* [python] support spread operator and additional API

* added a warning to the doc of `haxe.Rest.of(array)`

* [lua] update to the new API; support spread operator; fixed `haxe.Rest` as a return type

* [jvm] wip

* [eval] implementation

* Allow `...a:Int` in arg declaration instead of `a:haxe.Rest<Int>`

* and in function types

* [tests] also test static/instance closures with rest args.
Some  targets have quirks with that.

* [cs] wip

* [cs] support static/instance closures

* [cs] finally tamed ctors & overloading

* [jvm] finished

* [java] wip

* [java] done

* [neko] Array-based implementation

* [flash] based on a giant `if(rest.length == N)...` switch

* [flash] keep `function.apply(this, [arg1, arg2].concat(rest))` approach for non-`new` calls  for unlimited rest arguments amount with spread operator

* more tests

* test inlining with spread op

* [hl] implementation on top of `Array<T>`

* added pf_supports_rest_args; removed specific implementations from hl, neko, eval

* [cpp] Array-based implementation for haxe-generated code; cpp.Rest<T> for externs

* workaround cppia issue

* cleanup
Aleksandr Kuzmenko 4 年之前
父节点
当前提交
683b22440e
共有 50 个文件被更改,包括 1135 次插入127 次删除
  1. 52 15
      src/codegen/gencommon/castDetect.ml
  2. 1 2
      src/codegen/gencommon/closuresToClass.ml
  3. 36 0
      src/codegen/gencommon/gencommon.ml
  4. 3 1
      src/codegen/gencommon/normalize.ml
  5. 1 1
      src/codegen/swfLoader.ml
  6. 10 0
      src/context/common.ml
  7. 5 5
      src/core/ast.ml
  8. 1 0
      src/core/json/genjson.ml
  9. 5 0
      src/core/tOther.ml
  10. 3 1
      src/generators/gencpp.ml
  11. 5 1
      src/generators/gencs.ml
  12. 2 0
      src/generators/genhl.ml
  13. 31 6
      src/generators/genjava.ml
  14. 128 18
      src/generators/genjs.ml
  15. 11 3
      src/generators/genjvm.ml
  16. 48 18
      src/generators/genlua.ml
  17. 2 1
      src/generators/genneko.ml
  18. 57 4
      src/generators/genphp7.ml
  19. 2 1
      src/generators/genpy.ml
  20. 96 13
      src/generators/genswf9.ml
  21. 1 1
      src/macro/eval/evalDebugMisc.ml
  22. 4 0
      src/macro/eval/evalJit.ml
  23. 2 0
      src/macro/macroApi.ml
  24. 36 0
      src/optimization/inline.ml
  25. 13 1
      src/syntax/grammar.mly
  26. 1 1
      src/syntax/lexer.ml
  27. 1 0
      src/syntax/reification.ml
  28. 44 3
      src/typing/callUnification.ml
  29. 5 8
      src/typing/functionArguments.ml
  30. 7 0
      src/typing/operators.ml
  31. 13 10
      src/typing/overloadResolution.ml
  32. 25 0
      std/cpp/Rest.hx
  33. 0 2
      std/cpp/Stdio.hx
  34. 50 0
      std/cs/_std/haxe/Rest.hx
  35. 70 0
      std/haxe/Rest.hx
  36. 4 4
      std/haxe/extern/Rest.hx
  37. 19 0
      std/haxe/iterators/RestIterator.hx
  38. 19 0
      std/haxe/iterators/RestKeyValueIterator.hx
  39. 5 0
      std/haxe/macro/Expr.hx
  40. 1 0
      std/haxe/macro/Printer.hx
  41. 60 0
      std/java/_std/haxe/Rest.hx
  42. 51 0
      std/lua/_std/haxe/Rest.hx
  43. 1 1
      std/php/Syntax.hx
  44. 46 0
      std/php/_std/haxe/Rest.hx
  45. 0 4
      tests/misc/projects/Issue3238/NonExtern.hx
  46. 0 1
      tests/misc/projects/Issue3238/non-extern-fail.hxml
  47. 0 1
      tests/misc/projects/Issue3238/non-extern-fail.hxml.stderr
  48. 1 0
      tests/unit/src/unit/TestMain.hx
  49. 1 0
      tests/unit/src/unit/TestMatch.hx
  50. 156 0
      tests/unit/src/unit/TestRest.hx

+ 52 - 15
src/codegen/gencommon/castDetect.ml

@@ -543,8 +543,11 @@ let select_overload gen applied_f overloads types params =
 	let rec check_arg arglist elist =
 		match arglist, elist with
 			| [], [] -> true (* it is valid *)
-			| (_,_,TAbstract({ a_path = (["haxe";"extern"],"Rest") }, [t])) :: [], elist ->
+			| (_,_,t) :: [], elist when ExtType.is_rest t ->
+				(match follow t with
+				| TAbstract({ a_path = (["haxe"],"Rest") }, [t]) ->
 				List.for_all (fun (_,_,et) -> Type.type_iseq (clean_t et) (clean_t t)) elist
+				| _ -> die "" __LOC__)
 			| (_,_,t) :: arglist, (_,_,et) :: elist when Type.type_iseq (clean_t et) (clean_t t) ->
 				check_arg arglist elist
 			| _ -> false
@@ -615,19 +618,39 @@ let choose_ctor gen cl tparams etl maybe_empty_t p =
 			ret, !count > 1
 		| _ ->
 			let len = List.length etl in
-			let ret = List.filter (fun cf -> List.length (fst (get_fun cf.cf_type)) = len) ctors in
+			let ret = List.filter (fun cf -> List.length (fst (get_fun cf.cf_type)) <= len) ctors in
 			ret, (match ret with | _ :: [] -> false | _ -> true)
 	in
 	let rec check_arg arglist elist =
 		match arglist, elist with
 		| [], [] -> true
-		| (_,_,t) :: arglist, et :: elist -> (try
-			let t = run_follow gen t in
-			unify et t;
-			check_arg arglist elist
-		with | Unify_error el ->
-			(* List.iter (fun el -> gen.gcon.warning (Error.unify_error_msg (print_context()) el) p) el; *)
-			false)
+		| [(_,_,t)], elist when ExtType.is_rest (follow t) ->
+			let is_rest_array arg_t =
+				Type.fast_eq (Abstract.follow_with_abstracts t) (Abstract.follow_with_abstracts arg_t)
+			in
+			(match elist with
+			| [arg_t] when is_rest_array arg_t -> true
+			| _ ->
+				match follow t with
+				| TAbstract ({ a_path = ["haxe"],"Rest" }, [t1]) ->
+					let t1 = run_follow gen t1 in
+					(try
+						List.iter (fun et -> unify et t1) elist;
+						true
+					with Unify_error _ ->
+						false
+					)
+				| _ -> die "" __LOC__
+			)
+		| (_,_,t) :: arglist, et :: elist ->
+			(try
+				let t = run_follow gen t in
+				unify et t;
+				check_arg arglist elist
+			with Unify_error el ->
+				(* List.iter (fun el -> gen.gcon.warning (Error.unify_error_msg (print_context()) el) p) el; *)
+				false
+			)
 		| _ ->
 			false
 	in
@@ -645,8 +668,22 @@ let choose_ctor gen cl tparams etl maybe_empty_t p =
 
 let change_rest tfun elist =
 	let rec loop acc arglist elist = match arglist, elist with
-		| (_,_,TAbstract({ a_path = (["haxe";"extern"],"Rest") },[t])) :: [], elist ->
-			List.rev (List.map (fun _ -> "rest",false,t) elist @ acc)
+		| (_,_,t) as arg :: [], elist when ExtType.is_rest t ->
+			(match elist with
+			| [{ eexpr = TUnop (Spread,Prefix,e) }] ->
+				List.rev (arg :: acc)
+			| _ ->
+				(match follow t with
+				| TAbstract({ a_path = (["haxe"],"Rest") },[t1]) ->
+					let is_rest_array e =
+						Type.fast_eq (Abstract.follow_with_abstracts t) (Abstract.follow_with_abstracts e.etype)
+					in
+					(match elist with
+					| [e] when is_rest_array e -> List.rev (("rest",false,t) :: acc)
+					| _ -> List.rev (List.map (fun _ -> "rest",false,t1) elist @ acc)
+					)
+				| _ -> die "" __LOC__)
+			)
 		| (n,o,t) :: arglist, _ :: elist ->
 			loop ((n,o,t) :: acc) arglist elist
 		| _, _ ->
@@ -1118,10 +1155,10 @@ let configure gen ?(overloads_cast_to_base = false) maybe_empty_t calls_paramete
 							handle e t1 t2
 					in
 					let stl = gen.greal_type_param (TClassDecl sup) stl in
-					let args, _ = get_fun (apply_params sup.cl_params stl cf.cf_type) in
+					let args,rt = get_fun (apply_params sup.cl_params stl cf.cf_type) in
 					let eparams = List.map2 (fun e (_,_,t) ->
 						handle (run e) t e.etype
-					) eparams args in
+					) (wrap_rest_args gen (TFun (args,rt)) eparams e.epos) args in
 					{ e with eexpr = TCall(ef, eparams) }
 				with | Not_found ->
 					gen.gcon.warning "No overload found for this constructor call" e.epos;
@@ -1146,10 +1183,10 @@ let configure gen ?(overloads_cast_to_base = false) maybe_empty_t calls_paramete
 						handle e t1 t2
 				in
 				let stl = gen.greal_type_param (TClassDecl sup) stl in
-				let args, _ = get_fun (apply_params sup.cl_params stl cf.cf_type) in
+				let args,rt = get_fun (apply_params sup.cl_params stl cf.cf_type) in
 				let eparams = List.map2 (fun e (_,_,t) ->
 					handle (run e) t e.etype
-				) eparams args in
+				) (wrap_rest_args gen (TFun (args,rt)) eparams e.epos) args in
 				{ e with eexpr = TNew(cl, tparams, eparams) }
 			with | Not_found ->
 				gen.gcon.warning "No overload found for this constructor call" e.epos;

+ 1 - 2
src/codegen/gencommon/closuresToClass.ml

@@ -653,7 +653,6 @@ let configure gen ft =
 	in
 	gen.gexpr_filters#add name (PCustom priority) run
 
-
 (*
 	this submodule will provide the default implementation for the C# and Java targets.
 
@@ -853,7 +852,7 @@ struct
 
 		let dynamic_fun_call call_expr =
 			let tc, params = match call_expr.eexpr with
-				| TCall(tc, params) -> tc, params
+				| TCall(tc, params) -> tc,wrap_rest_args gen tc.etype params tc.epos
 				| _ -> die "" __LOC__
 			in
 			let ct = gen.greal_type call_expr.etype in

+ 36 - 0
src/codegen/gencommon/gencommon.ml

@@ -996,6 +996,42 @@ let get_real_fun gen t =
 let mk_nativearray_decl gen t el pos =
 	mk (TCall (mk (TIdent "__array__") t_dynamic pos, el)) (gen.gclasses.nativearray t) pos
 
+
+(**
+	Wraps rest arguments into a native array.
+	E.g. transforms params from `callee(param, rest1, rest2, ..., restN)` into
+	`callee(param, untyped __array__(rest1, rest2, ..., restN))`
+*)
+let wrap_rest_args gen callee_type params p =
+	match follow callee_type with
+	| TFun(args, _) ->
+		let rec loop args params =
+			match args, params with
+			(* last argument expects rest parameters *)
+			| [(_,_,t)], params when ExtType.is_rest (follow t) ->
+				(match params with
+				(* In case of `...rest` just use `rest` *)
+				| [{ eexpr = TUnop(Spread,Prefix,e) }] -> [e]
+				(* In other cases: `untyped __array__(param1, param2, ...)` *)
+				| _ ->
+					match Abstract.follow_with_abstracts t with
+					| TInst ({ cl_path = _,"NativeArray" }, [t1]) ->
+						let pos = punion_el (List.map (fun e -> ((),e.epos)) params) in
+						let t1 = if Common.defined gen.gcon Define.EraseGenerics then t_dynamic else t1 in
+						[mk_nativearray_decl gen t1 params pos]
+					| _ ->
+						die ~p "Unexpected rest arguments type" __LOC__
+				)
+			| a :: args, e :: params ->
+				e :: loop args params
+			| [], params ->
+				params
+			| _ :: _, [] ->
+				[]
+		in
+		loop args params
+	| _ -> params
+
 let ensure_local com block name e =
 	match e.eexpr with
 	| TLocal _ -> e

+ 3 - 1
src/codegen/gencommon/normalize.ml

@@ -39,13 +39,15 @@ let rec filter_param (stack:t list) t =
 		| Some t -> filter_param stack t)
 	| TInst(_,[]) | TEnum(_,[]) | TAbstract(_,[]) ->
 		t
+	| TType({ t_path = (["haxe";"extern"],"Rest") },_) ->
+		filter_param stack (follow t)
 	| TType(td,tl) ->
 		TType(td,List.map (filter_param stack) tl)
 	| TInst(c,tl) ->
 		TInst(c,List.map (filter_param stack) tl)
 	| TEnum(e,tl) ->
 		TEnum(e,List.map (filter_param stack) tl)
-	| TAbstract({ a_path = (["haxe";"extern"],"Rest") } as a,tl) ->
+	| TAbstract({ a_path = (["haxe"],"Rest") } as a,tl) ->
 		TAbstract(a, List.map (filter_param stack) tl)
 	| TAbstract({a_path = [],"Null"} as a,[t]) ->
 		TAbstract(a,[filter_param stack t])

+ 1 - 1
src/codegen/swfLoader.ml

@@ -47,7 +47,7 @@ let tp_dyn = { tpackage = []; tname = "Dynamic"; tparams = []; tsub = None; }
 let ct_dyn = CTPath tp_dyn
 
 let ct_rest = CTPath {
-	tpackage = ["haxe"; "extern"];
+	tpackage = ["haxe"];
 	tname = "Rest";
 	tparams = [TPType (ct_dyn,null_pos)];
 	tsub = None;

+ 10 - 0
src/context/common.ml

@@ -178,6 +178,8 @@ type platform_config = {
 	pf_supports_threads : bool;
 	(** target supports Unicode **)
 	pf_supports_unicode : bool;
+	(** target supports rest arguments **)
+	pf_supports_rest_args : bool;
 	(** exceptions handling config **)
 	pf_exceptions : exceptions_config;
 	(** the scoping of local variables *)
@@ -411,6 +413,7 @@ let default_config =
 		pf_this_before_super = true;
 		pf_supports_threads = false;
 		pf_supports_unicode = true;
+		pf_supports_rest_args = false;
 		pf_exceptions = {
 			ec_native_throws = [];
 			ec_native_catches = [];
@@ -438,6 +441,7 @@ let get_config com =
 			pf_capture_policy = if es6 then CPNone else CPLoopVars;
 			pf_reserved_type_paths = [([],"Object");([],"Error")];
 			pf_this_before_super = not es6; (* cannot access `this` before `super()` when generating ES6 classes *)
+			pf_supports_rest_args = true;
 			pf_exceptions = { default_config.pf_exceptions with
 				ec_native_throws = [
 					["js";"lib"],"Error";
@@ -457,6 +461,7 @@ let get_config com =
 			pf_static = false;
 			pf_capture_policy = CPLoopVars;
 			pf_uses_utf16 = false;
+			pf_supports_rest_args = true;
 		}
 	| Neko ->
 		{
@@ -477,6 +482,7 @@ let get_config com =
 			pf_capture_policy = CPLoopVars;
 			pf_can_skip_non_nullable_argument = false;
 			pf_reserved_type_paths = [([],"Object");([],"Error")];
+			pf_supports_rest_args = true;
 			pf_exceptions = { default_config.pf_exceptions with
 				ec_native_throws = [
 					["flash";"errors"],"Error";
@@ -497,6 +503,7 @@ let get_config com =
 			default_config with
 			pf_static = false;
 			pf_uses_utf16 = false;
+			pf_supports_rest_args = true;
 			pf_exceptions = { default_config.pf_exceptions with
 				ec_native_throws = [
 					["php"],"Throwable";
@@ -533,6 +540,7 @@ let get_config com =
 			pf_pad_nulls = true;
 			pf_overload = true;
 			pf_supports_threads = true;
+			pf_supports_rest_args = true;
 			pf_exceptions = {
 				ec_native_throws = [
 					["cs";"system"],"Exception";
@@ -558,6 +566,7 @@ let get_config com =
 			pf_pad_nulls = true;
 			pf_overload = true;
 			pf_supports_threads = true;
+			pf_supports_rest_args = true;
 			pf_this_before_super = false;
 			pf_exceptions = { default_config.pf_exceptions with
 				ec_native_throws = [
@@ -587,6 +596,7 @@ let get_config com =
 			pf_capture_policy = CPLoopVars;
 			pf_uses_utf16 = false;
 			pf_supports_threads = true;
+			pf_supports_rest_args = true;
 			pf_exceptions = { default_config.pf_exceptions with
 				ec_native_throws = [
 					["python";"Exceptions"],"BaseException";

+ 5 - 5
src/core/ast.ml

@@ -98,6 +98,7 @@ type unop =
 	| Not
 	| Neg
 	| NegBits
+	| Spread
 
 type string_literal_kind =
 	| SDoubleQuotes
@@ -135,6 +136,7 @@ type token =
 	| Question
 	| At
 	| Dollar of string
+	| Spread
 
 type unop_flag =
 	| Prefix
@@ -429,11 +431,7 @@ let get_own_doc_opt = Option.map_default (fun d -> d.doc_own) None
 
 let rec is_postfix (e,_) op = match op with
 	| Increment | Decrement | Not -> true
-	| Neg | NegBits -> false
-
-let is_prefix = function
-	| Increment | Decrement -> true
-	| Not | Neg | NegBits -> true
+	| Neg | NegBits | Spread -> false
 
 let base_class_name = snd
 
@@ -562,6 +560,7 @@ let s_unop = function
 	| Not -> "!"
 	| Neg -> "-"
 	| NegBits -> "~"
+	| Spread -> "..."
 
 let s_token = function
 	| Eof -> "<end of file>"
@@ -587,6 +586,7 @@ let s_token = function
 	| Question -> "?"
 	| At -> "@"
 	| Dollar v -> "$" ^ v
+	| Spread -> "..."
 
 exception Invalid_escape_sequence of char * int * (string option)
 

+ 1 - 0
src/core/json/genjson.ml

@@ -158,6 +158,7 @@ let generate_unop ctx op =
 		| Not -> "OpNot"
 		| Neg -> "OpNeg"
 		| NegBits -> "OpNegBits"
+		| Spread -> "OpSpread"
 	in
 	jstring name
 

+ 5 - 0
src/core/tOther.ml

@@ -180,6 +180,11 @@ module ExtType = struct
 		| TAbstract({a_path=[],"Bool"},_) -> true
 		| _ -> false
 
+	let is_rest t = match t with
+		| TType({t_path=["haxe"; "extern"],"Rest"},_)
+		| TAbstract({a_path=["haxe"],"Rest"},_) -> true
+		| _ -> false
+
 	let is_type_param t =
 		match t with
 		| TInst({ cl_kind = KTypeParameter _ }, _) -> true

+ 3 - 1
src/generators/gencpp.ml

@@ -1819,7 +1819,7 @@ let rec cpp_type_of stack ctx haxe_type =
       | (("cpp"::["objc"]),"ObjcBlock"), [function_type] ->
             let args,ret = (cpp_function_type_of_args_ret stack ctx function_type) in
             TCppObjCBlock(args,ret)
-      | (["haxe";"extern"], "Rest"),[rest] ->
+      | ((["cpp"]), "Rest"),[rest] ->
             TCppRest(cpp_type_of stack ctx rest)
       | (("cpp"::["objc"]),"Protocol"), [interface_type] ->
             (match follow interface_type with
@@ -2986,6 +2986,7 @@ let retype_expression ctx request_type function_args function_type expression_tr
                | Neg -> CppUnop(CppNeg,e1)
                | Not -> CppUnop(CppNot,e1)
                | NegBits -> CppUnop(CppNegBits,e1)
+               | Spread -> die ~p:expr.epos "Unexpected spread operator" __LOC__
             in reference, cpp_type_of expr.etype
 
          | TFor (v,init,block) ->
@@ -7731,6 +7732,7 @@ class script_writer ctx filename asciiOut =
       | Decrement, _ -> IaMinusMinusPost
       | Not, _ -> IaLogicNot
       | Neg, _ -> IaNeg
+      | Spread, _ -> die ~p:e.epos "Unexpected spread operator" __LOC__
       | NegBits, _ -> IaBitNot );
       this#gen_expression e;
    (* TODO - lval op-assign local/member/array *)

+ 5 - 1
src/generators/gencs.ml

@@ -952,7 +952,8 @@ let generate con =
 					| TInst( { cl_path = (["haxe"], "Int64") }, [] ) -> ti64
 					| TAbstract( { a_path = [],"Class" }, _ )
 					| TAbstract( { a_path = [],"Enum" }, _ )
-					| TAbstract( { a_path = ["haxe";"extern"],"Rest" }, _ )
+					| TAbstract( { a_path = (["haxe"]),"Rest" }, _ )
+					| TType( { t_path = (["haxe";"extern"]),"Rest" }, _ )
 					| TInst( { cl_path = ([], "Class") }, _ )
 					| TInst( { cl_path = ([], "Enum") }, _ ) -> TInst(ttype,[])
 					| TInst( ({ cl_kind = KTypeParameter _ } as cl), _ ) when erase_generics && not (Meta.has Meta.NativeGeneric cl.cl_meta) ->
@@ -1637,6 +1638,8 @@ let generate con =
 						(match flag with
 							| Ast.Prefix -> write w ( " " ^ (Ast.s_unop op) ^ " " ); expr_s w e
 							| Ast.Postfix -> expr_s w e; write w (Ast.s_unop op))
+					| TUnop (Spread, Prefix, e) ->
+						expr_s w e
 					| TUnop (op, flag, e) ->
 						(match flag with
 							| Ast.Prefix -> write w ( " " ^ (Ast.s_unop op) ^ " (" ); expr_s w e; write w ") "
@@ -1955,6 +1958,7 @@ let generate con =
 				| TAbstract ({ a_path = (["cs"], "Ref") },[t]) -> "ref " ^ t_s t
 				| TType ({ t_path = (["cs"], "Out") }, [t])
 				| TAbstract ({ a_path = (["cs"], "Out") },[t]) -> "out " ^ t_s t
+				| _ when ExtType.is_rest (Type.follow t) -> "params " ^ (t_s (Abstract.follow_with_abstracts t))
 				| t -> t_s t
 			in
 			let c = contents w in

+ 2 - 0
src/generators/genhl.ml

@@ -2497,6 +2497,8 @@ and eval_expr ctx e =
 		let r = eval_to ctx v t in
 		op ctx (ONeg (tmp,r));
 		tmp
+	| TUnop (Spread,_,_) ->
+		die ~p:e.epos "Unexpected spread operator" __LOC__
 	| TUnop (NegBits,_,v) ->
 		let t = to_type ctx e.etype in
 		let tmp = alloc_tmp ctx t in

+ 31 - 6
src/generators/genjava.ml

@@ -1666,6 +1666,7 @@ let generate con =
 						acc + 1
 					) 0 el);
 					write w ")"
+				| TUnop (Ast.Spread, Prefix, e) -> expr_s w e
 				| TUnop ((Ast.Increment as op), flag, e)
 				| TUnop ((Ast.Decrement as op), flag, e) ->
 					(match flag with
@@ -1994,9 +1995,19 @@ let generate con =
 				let cf_type = if is_override && not is_overload && not (has_class_field_flag cf CfOverload) then match field_access gen (TInst(cl, List.map snd cl.cl_params)) cf.cf_name with | FClassField(_,_,_,_,_,actual_t,_) -> actual_t | _ -> die "" __LOC__ else cf.cf_type in
 
 				let params = List.map snd cl.cl_params in
-				let ret_type, args = match follow cf_type, follow cf.cf_type with
+				let ret_type, args, has_rest_args = match follow cf_type, follow cf.cf_type with
 					| TFun (strbtl, t), TFun(rargs, _) ->
-							(apply_params cl.cl_params params (real_type t), List.map2 (fun(_,_,t) (n,o,_) -> (n,o,apply_params cl.cl_params params (real_type t))) strbtl rargs)
+						let ret_type = apply_params cl.cl_params params (real_type t)
+						and args =
+							List.map2 (fun(_,_,t) (n,o,_) ->
+								(n,o,apply_params cl.cl_params params (real_type t))
+							) strbtl rargs
+						and rest =
+							match List.rev rargs with
+							| (_,_,t) :: _ -> ExtType.is_rest (follow t)
+							| _ -> false
+						in
+						ret_type,args,rest
 					| _ -> die "" __LOC__
 				in
 
@@ -2008,12 +2019,26 @@ let generate con =
 				write_parts w (visibility :: v_n :: modifiers @ [params; (if is_new then "" else rett_s cf.cf_pos (run_follow gen ret_type)); (change_field name)]);
 
 				(* <T>(string arg1, object arg2) with T : object *)
-				(match cf.cf_expr with
+				let arg_names =
+					match cf.cf_expr with
 					| Some { eexpr = TFunction tf } ->
-							print w "(%s)" (String.concat ", " (List.map2 (fun (var,_) (_,_,t) -> sprintf "%s %s" (argt_s cf.cf_pos (run_follow gen t)) (change_id var.v_name)) tf.tf_args args))
+						List.map (fun (var,_) -> change_id var.v_name) tf.tf_args
 					| _ ->
-							print w "(%s)" (String.concat ", " (List.map (fun (name, _, t) -> sprintf "%s %s" (argt_s cf.cf_pos (run_follow gen t)) (change_id name)) args))
-				);
+						List.map (fun (name,_,_) -> change_id name) args
+				in
+				let rec loop acc names args =
+					match names, args with
+					| [], [] -> acc
+					| _, [] | [], _ ->
+						die "" __LOC__
+					| [name], [_,_,TInst ({ cl_path = ["java"],"NativeArray" }, [t])] when has_rest_args ->
+						let arg = sprintf "%s ...%s" (argt_s cf.cf_pos (run_follow gen t)) name in
+						arg :: acc
+					| name :: names, (_,_,t) :: args ->
+						let arg = sprintf "%s %s" (argt_s cf.cf_pos (run_follow gen t)) name in
+						loop (arg :: acc) names args
+				in
+				print w "(%s)" (String.concat ", " (List.rev (loop [] arg_names args)));
 				if is_interface || List.mem "native" modifiers || is_abstract then
 					write w ";"
 				else begin

+ 128 - 18
src/generators/genjs.ml

@@ -326,6 +326,42 @@ let rec concat ctx s f = function
 		spr ctx s;
 		concat ctx s f l
 
+(**
+	Produce expressions to declare arguments of a function with `Rest<T>` trailing argument.
+	Used for ES5 and older standards, which don't support "rest parameters" syntax.
+	`args` is a list of explicitly defined arguments.
+	`rest_arg` is the argument of `Rest<T>` type.
+
+	This implementation copies rest arguments into a new array in a loop.
+	It's the only way to avoid disabling javascript VM optimizations of functions
+	with rest arguments.
+*)
+let declare_rest_args_legacy com args rest_arg =
+	let arguments = mk (TIdent "arguments") t_dynamic null_pos
+	and index i = mk (TConst (TInt (Int32.of_int i))) com.basic.tint null_pos in
+	let rec loop args i =
+		match args with
+		| [] -> []
+		| (v,e_opt) :: rest ->
+			(* var v = arguments[i]; *)
+			let value = mk (TArray (arguments,index i)) v.v_type null_pos in
+			let decl = mk (TVar (v,Some value)) com.basic.tvoid v.v_pos in
+
+			match e_opt with
+			| None -> decl :: loop rest (i + 1)
+			| Some e -> decl :: Texpr.set_default com.basic v e v.v_pos :: loop rest (i + 1)
+	in
+	let i = string_of_int (List.length args) in
+	let new_array = mk (TIdent ("new Array($l-"^ i ^")")) t_dynamic rest_arg.v_pos
+	and populate = mk (TIdent ("for(var $i=" ^ i ^ ";$i<$l;++$i){" ^ (ident rest_arg.v_name) ^ "[$i-" ^ i ^ "]=arguments[$i];}")) com.basic.tvoid rest_arg.v_pos
+	in
+	loop args 0
+	@ [
+		mk (TIdent ("var $l=arguments.length")) com.basic.tvoid rest_arg.v_pos;
+		mk (TVar (rest_arg,Some new_array)) com.basic.tvoid rest_arg.v_pos;
+		populate
+	]
+
 let fun_block ctx f p =
 	let e = List.fold_left (fun e (a,c) ->
 		match c with
@@ -396,12 +432,28 @@ let var ctx =
 	if ctx.es_version >= 6 then "let" else "var"
 
 let rec gen_call ctx e el in_value =
+	let apply,el =
+		if ctx.es_version < 6 then
+			match List.rev el with
+			| [{ eexpr = TUnop (Spread,Ast.Prefix,rest) }] ->
+				true,[rest]
+			| { eexpr = TUnop (Spread,Ast.Prefix,rest) } :: args_rev ->
+				(* [arg1, arg2, ..., argN].concat(rest) *)
+				let arr = mk (TArrayDecl (List.rev args_rev)) t_dynamic null_pos in
+				let concat = mk (TField (arr, FDynamic "concat")) t_dynamic null_pos in
+				true,[mk (TCall (concat, [rest])) t_dynamic null_pos]
+			| _ ->
+				false,el
+		else
+			false,el
+	in
 	match e.eexpr , el with
 	| TConst TSuper , params when ctx.es_version < 6 ->
 		(match ctx.current.cl_super with
 		| None -> abort "Missing api.setCurrentClass" e.epos
 		| Some (c,_) ->
-			print ctx "%s.call(%s" (ctx.type_accessor (TClassDecl c)) (this ctx);
+			let call = if apply then "apply" else "call" in
+			print ctx "%s.%s(%s" (ctx.type_accessor (TClassDecl c)) call (this ctx);
 			List.iter (fun p -> print ctx ","; gen_value ctx p) params;
 			spr ctx ")";
 		);
@@ -410,17 +462,22 @@ let rec gen_call ctx e el in_value =
 		| None -> abort "Missing api.setCurrentClass" e.epos
 		| Some (c,_) ->
 			let name = field_name f in
-			print ctx "%s.prototype%s.call(%s" (ctx.type_accessor (TClassDecl c)) (field name) (this ctx);
+			let call = if apply then "apply" else "call" in
+			print ctx "%s.prototype%s.%s(%s" (ctx.type_accessor (TClassDecl c)) (field name) call (this ctx);
 			List.iter (fun p -> print ctx ","; gen_value ctx p) params;
 			spr ctx ")";
 		);
 	| TCall (x,_) , el when not (is_code_injection_function x) ->
-		spr ctx "(";
-		gen_value ctx e;
-		spr ctx ")";
-		spr ctx "(";
-		concat ctx "," (gen_value ctx) el;
-		spr ctx ")";
+		if apply then
+			gen_call_with_apply ctx e el
+		else begin
+			spr ctx "(";
+			gen_value ctx e;
+			spr ctx ")";
+			spr ctx "(";
+			concat ctx "," (gen_value ctx) el;
+			spr ctx ")";
+		end
 	| TField (_, FStatic ({ cl_path = ["js"],"Syntax" }, { cf_name = meth })), args ->
 		gen_syntax ctx meth args e.epos
 	| TField (_, FStatic ({ cl_path = ["js"],"Lib" }, { cf_name = "rethrow" })), [] ->
@@ -507,9 +564,32 @@ let rec gen_call ctx e el in_value =
 		gen_value ctx x;
 		print ctx ")";
 	| _ ->
-		gen_value ctx e;
-		spr ctx "(";
-		concat ctx "," (gen_value ctx) el;
+		if apply then
+			gen_call_with_apply ctx e el
+		else begin
+			gen_value ctx e;
+			spr ctx "(";
+			concat ctx "," (gen_value ctx) el;
+			spr ctx ")"
+		end
+
+and gen_call_with_apply ctx target args =
+	(match args with
+	| [_] -> ()
+	| _ -> die ~p:target.epos "`args` for `gen_call_with_apply` must contain exactly one item" __LOC__
+	);
+	match target.eexpr with
+	| TField (this, (FInstance (_,_,{ cf_name = field }) | FAnon { cf_name = field } | FDynamic field | FClosure (_,{ cf_name = field }))) ->
+		add_feature ctx "thisForCallWithRestArgs";
+		spr ctx "($_=";
+		gen_value ctx this;
+		spr ctx (",$_." ^ field ^ ".apply($_,");
+		concat ctx "," (gen_value ctx) args;
+		spr ctx "))"
+	| _ ->
+		gen_value ctx target;
+		spr ctx ".apply(null,";
+		concat ctx "," (gen_value ctx) args;
 		spr ctx ")"
 
 (*
@@ -822,10 +902,34 @@ and gen_function ?(keyword="function") ctx f pos =
 	let old = ctx.in_value, ctx.in_loop in
 	ctx.in_value <- None;
 	ctx.in_loop <- false;
-	let args = List.map (fun (v,_) ->
-		check_var_declaration v;
-		ident v.v_name
-	) f.tf_args in
+	let f,args =
+		match List.rev f.tf_args with
+		| (v,None) :: args_rev when ExtType.is_rest (follow v.v_type) ->
+			(* Use ES6 rest args syntax: `...arg` *)
+			if ctx.es_version >= 6 then
+				f, List.map (fun (a,_) ->
+					check_var_declaration a;
+					if a == v then ("..." ^ ident a.v_name)
+					else ident a.v_name
+				) f.tf_args
+			(* Resort to `arguments` special object for ES < 6 *)
+			else
+				let args_decl = declare_rest_args_legacy ctx.com (List.rev args_rev) v in
+				let body =
+					let el =
+						match f.tf_expr.eexpr with
+						| TBlock el -> args_decl @ el
+						| _ -> args_decl @ [f.tf_expr]
+					in
+					mk (TBlock el) f.tf_expr.etype f.tf_expr.epos
+				in
+				{ f with tf_args = []; tf_expr = body }, []
+		| _ ->
+			f, List.map (fun (v,_) ->
+				check_var_declaration v;
+				ident v.v_name
+			) f.tf_args
+	in
 	print ctx "%s(%s) " keyword (String.concat "," args);
 	gen_expr ctx (fun_block ctx f pos);
 	ctx.in_value <- fst old;
@@ -1876,9 +1980,9 @@ let generate com =
 	let vars = if (enums_as_objects && (has_feature ctx "has_enum" || has_feature ctx "Type.resolveEnum")) then "$hxEnums = $hxEnums || {}" :: vars else vars in
 	let vars,has_dollar_underscore =
 		if List.exists (function TEnumDecl { e_extern = false } -> true | _ -> false) com.types then
-			"$_" :: vars,true
+			"$_" :: vars,ref true
 		else
-			vars,false
+			vars,ref false
 	in
 	(match List.rev vars with
 	| [] -> ()
@@ -1938,9 +2042,10 @@ let generate com =
 	end;
 	if has_feature ctx "use.$bind" then begin
 		add_feature ctx "$global.$haxeUID";
-		if not has_dollar_underscore then begin
+		if not !has_dollar_underscore then begin
 			print ctx "var $_";
 			newline ctx;
+			has_dollar_underscore := true
 		end;
 		(if ctx.es_version < 5 then
 			print ctx "function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; o.hx__closures__[m.__id__] = f; } return f; }"
@@ -1957,6 +2062,11 @@ let generate com =
 		add_feature ctx "js.Lib.global";
 		print ctx "$global.$haxeUID |= 0;\n";
 	end;
+	if not !has_dollar_underscore && has_feature ctx "thisForCallWithRestArgs" then begin
+		print ctx "var $_";
+		newline ctx;
+		has_dollar_underscore := true
+	end;
 	List.iter (gen_block_element ~newline_after:true ~keep_blocks:(ctx.es_version >= 6) ctx) (List.rev ctx.inits);
 	List.iter (generate_static ctx) (List.rev ctx.statics);
 	(match com.main with

+ 11 - 3
src/generators/genjvm.ml

@@ -124,11 +124,12 @@ let rec jsignature_of_type gctx stack t =
 				| [t] -> get_boxed_type (jsignature_of_type t)
 				| _ -> die "" __LOC__
 				end
-			| (["haxe";"ds"],"Vector") | (["haxe";"extern"],"Rest") ->
+			| ["haxe";"ds"],"Vector" ->
 				begin match tl with
 				| [t] -> TArray(jsignature_of_type t,None)
 				| _ -> die "" __LOC__
 				end
+			| ["haxe"],"Rest" -> TArray(object_sig,None)
 			| [],"Dynamic" ->
 				object_sig
 			| [],("Class" | "Enum") ->
@@ -1359,6 +1360,8 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 				(self#condition false e)
 				(fun () -> code#bconst false)
 				(fun () -> code#bconst true)
+		| Spread, _ ->
+			self#texpr (rvalue_type gctx e.etype) e
 		| NegBits,_ ->
 			let jsig = jsignature_of_type gctx (follow e.etype) in
 			self#texpr rvalue_any e;
@@ -1388,8 +1391,13 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 			| (_,_,t) :: tl,e :: el ->
 				let jsig = jsignature_of_type gctx t in
 				begin match tl,Type.follow t with
-				| [],(TAbstract({a_path = ["haxe";"extern"],"Rest"},[t])) ->
-					self#new_native_array (jsignature_of_type gctx t) (e :: el);
+				| [],(TAbstract({a_path = ["haxe"],"Rest"},[t1])) ->
+					(match e.eexpr with
+					| TUnop (Spread,_,e) ->
+						self#texpr (rvalue_sig jsig) e
+					| _ ->
+						self#new_native_array (get_boxed_type (jsignature_of_type gctx t1)) (e :: el)
+					);
 					List.rev (jsig :: acc)
 				| _ ->
 					self#texpr (rvalue_sig jsig) e;

+ 48 - 18
src/generators/genlua.ml

@@ -185,13 +185,32 @@ let rec concat ctx s f = function
         spr ctx s;
         concat ctx s f l
 
+(* create a __lua__ call *)
+let mk_lua_code com code args t pos =
+    let lua_local = mk (TIdent "__lua__") t_dynamic pos in
+    let code_const = Texpr.Builder.make_string com code pos in
+    mk (TCall (lua_local, code_const :: args)) t pos
+
+let inject_rest_args ctx args e =
+    match List.rev args with
+    | (v,_) :: _ when ExtType.is_rest (follow v.v_type) ->
+        let rest = mk (TLocal v) v.v_type v.v_pos in
+        let init_rest = mk_lua_code ctx.com.basic "local {0} = {...}" [rest] ctx.com.basic.tvoid v.v_pos in
+        (match e.eexpr with
+        | TBlock el ->
+            { e with eexpr = TBlock (init_rest :: el) }
+        | _ ->
+            { e with eexpr = TBlock [init_rest; e] }
+        )
+    | _ -> e
+
 let fun_block ctx f p =
-    let e = List.fold_left (fun e (a,c) ->
+    let fn_body = inject_rest_args ctx f.tf_args f.tf_expr in
+    List.fold_left (fun e (a,c) ->
         match c with
         | None | Some {eexpr = TConst TNull} -> e
         | Some c -> Type.concat (Texpr.set_default ctx.com.basic a c p) e
-    ) f.tf_expr f.tf_args in
-    e
+    ) fn_body f.tf_args
 
 let open_block ctx =
     let oldt = ctx.tabs in
@@ -223,12 +242,6 @@ let index_of f l =
     in
     find l 0
 
-(* create a __lua__ call *)
-let mk_lua_code com code args t pos =
-    let lua_local = mk (TIdent "__lua__") t_dynamic pos in
-    let code_const = Texpr.Builder.make_string com code pos in
-    mk (TCall (lua_local, code_const :: args)) t pos
-
 (* create a multi-return boxing call for given expr *)
 let mk_mr_box ctx e =
     let s_fields =
@@ -618,13 +631,25 @@ and check_multireturn_param ctx t pos =
         | _ ->
                 ();
 
+(* for declaring identifiers in values/blocks *)
+and lua_ident_name a =
+    match a.v_name, a.v_kind, a.v_type with
+        | "this", _, _ -> "self";
+        | _, _, _ ->  ident a.v_name;
+
+
+(* for declaring arguments in function defintions *)
+and lua_arg_name(a,_) =
+    match a.v_name, a.v_kind, a.v_type with
+        | "this", _, _ -> "self";
+        | _, _, TAbstract({a_path=["haxe"],"Rest" },_) -> "...";
+        | _, _, _ ->  ident a.v_name;
+
 and gen_expr ?(local=true) ctx e = begin
     match e.eexpr with
       TConst c ->
         gen_constant ctx e.epos c;
-    | TLocal v when v.v_name = "this" ->
-        spr ctx "self";
-    | TLocal v -> spr ctx (ident v.v_name)
+    | TLocal v -> spr ctx (lua_ident_name v);
     | TArray (e1,{ eexpr = TConst (TString s) }) when valid_lua_ident s && (match e1.eexpr with TConst (TInt _|TFloat _) -> false | _ -> true) ->
         gen_value ctx e1;
         spr ctx (field s)
@@ -724,7 +749,7 @@ and gen_expr ?(local=true) ctx e = begin
         let old = ctx.in_value, ctx.in_loop in
         ctx.in_value <- None;
         ctx.in_loop <- false;
-        print ctx "function(%s) " (String.concat "," (List.map ident (List.map arg_name f.tf_args)));
+        print ctx "function(%s) " (String.concat "," (List.map lua_arg_name f.tf_args));
         let fblock = fun_block ctx f e.epos in
         (match fblock.eexpr with
          | TBlock el ->
@@ -906,6 +931,11 @@ and gen_expr ?(local=true) ctx e = begin
         spr ctx "_hx_bit.bnot(";
         gen_value ctx e;
         spr ctx ")";
+    | TUnop (Spread,Prefix,e) ->
+        add_feature ctx "use._hx_table";
+        spr ctx "_hx_table.unpack(";
+        gen_value ctx e;
+        spr ctx ")";
     | TUnop (op,Ast.Prefix,e) ->
         spr ctx (Ast.s_unop op);
         gen_value ctx e
@@ -1079,7 +1109,7 @@ and gen_anon_value ctx e =
         let old = ctx.in_value, ctx.in_loop in
         ctx.in_value <- None;
         ctx.in_loop <- false;
-        print ctx "function(%s) " (String.concat "," ("self" :: (List.map ident (List.map arg_name f.tf_args))));
+        print ctx "function(%s) " (String.concat "," ("self" :: (List.map lua_arg_name f.tf_args)));
         let fblock = fun_block ctx f e.epos in
         (match fblock.eexpr with
          | TBlock el ->
@@ -1510,7 +1540,7 @@ let gen_class_field ctx c f =
              ctx.in_value <- None;
              ctx.in_loop <- false;
              print ctx " = function";
-             print ctx "(%s) " (String.concat "," ("self" :: List.map ident (List.map arg_name f2.tf_args)));
+             print ctx "(%s) " (String.concat "," ("self" ::(List.map lua_arg_name f2.tf_args)));
              let fblock = fun_block ctx f2 e.epos in
              (match fblock.eexpr with
               | TBlock el ->
@@ -1572,7 +1602,7 @@ let generate_class ctx c =
                    let old = ctx.in_value, ctx.in_loop in
                    ctx.in_value <- None;
                    ctx.in_loop <- false;
-                   print ctx "function(%s) " (String.concat "," (List.map ident (List.map arg_name f.tf_args)));
+                   print ctx "function(%s) " (String.concat "," (List.map lua_arg_name f.tf_args));
                    let fblock = fun_block ctx f e.epos in
                    (match fblock.eexpr with
                     | TBlock el ->
@@ -1580,13 +1610,13 @@ let generate_class ctx c =
                         newline ctx;
                         if not (has_prototype ctx c) then println ctx "local self = _hx_new()" else
                             println ctx "local self = _hx_new(%s.prototype)" p;
-                        println ctx "%s.super(%s)" p (String.concat "," ("self" :: (List.map ident (List.map arg_name f.tf_args))));
+                        println ctx "%s.super(%s)" p (String.concat "," ("self" :: (List.map lua_arg_name f.tf_args)));
                         if p = "String" then println ctx "self = string";
                         spr ctx "return self";
                         bend(); newline ctx;
                         spr ctx "end"; newline ctx;
                         let bend = open_block ctx in
-                        print ctx "%s.super = function(%s) " p (String.concat "," ("self" :: (List.map ident (List.map arg_name f.tf_args))));
+                        print ctx "%s.super = function(%s) " p (String.concat "," ("self" :: (List.map lua_arg_name f.tf_args)));
                         List.iter (gen_block_element ctx) el;
                         bend();
                         newline ctx;

+ 2 - 1
src/generators/genneko.ml

@@ -189,6 +189,7 @@ and gen_unop ctx p op flag e =
 	| Not -> call p (builtin p "not") [gen_expr ctx e]
 	| Neg -> (EBinop ("-",int p 0, gen_expr ctx e),p)
 	| NegBits -> (EBinop ("-",int p (-1), gen_expr ctx e),p)
+	| Spread -> die ~p:e.epos "Unhandled spread operator" __LOC__
 
 and gen_call ctx p e el =
 	match e.eexpr , el with
@@ -266,7 +267,7 @@ and gen_expr ctx e =
 		call p (field p (ident p "Array") "new1") [array p (List.map (gen_expr ctx) el); int p (List.length el)]
 	| TCall (e,el) ->
 		gen_call ctx p e el
-	| TNew (c,_,params) ->
+	| TNew (c,tl,params) ->
 		call p (field p (gen_type_path p c.cl_path) "new") (List.map (gen_expr ctx) params)
 	| TUnop (op,flag,e) ->
 		gen_unop ctx p op flag e

+ 57 - 4
src/generators/genphp7.ml

@@ -308,6 +308,11 @@ let is_string expr = ExtType.is_string (follow expr.etype)
 *)
 let is_array_type t = match follow t with TInst ({ cl_path = ([], "Array") }, _) -> true | _ -> false
 
+(**
+	Check if specified type is haxe.Rest
+*)
+let is_rest_type t = ExtType.is_rest (Type.follow t)
+
 (**
 	Check if specified type represents a function
 *)
@@ -702,6 +707,15 @@ let is_access expr =
 		| TField _ | TArray _ -> true
 		| _ -> false
 
+(**
+	Check if specified field access is an access to the field `Array.arr`
+	It's a private field of the php-specific implementation of Haxe Array.
+*)
+let is_array_arr faccess =
+	match faccess with
+		| FInstance ({ cl_path = [],"Array" }, _, { cf_name = "arr" }) -> true
+		| _ -> false
+
 (**
 	Indicates if `expr` is actually a call to Haxe->PHP magic function
 	@see http://old.haxe.org/doc/advanced/magic#php-magic
@@ -1440,6 +1454,30 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 			match self#parent_expr with
 				| Some { eexpr = TCall _ } -> true
 				| _ -> false
+		(**
+			Indicates if current expression is passed to `php.Ref<T>`
+		*)
+		method current_expr_is_for_ref =
+			match expr_hierarchy with
+				| [] -> false
+				| current :: _ ->
+					match self#parent_expr with
+						| Some { eexpr = TCall (target, params) } when current != (reveal_expr target) ->
+							(match follow target.etype with
+								| TFun (args,_) ->
+									let rec check args params =
+										match args, params with
+										| (_, _, t) :: _, param :: _ when current == (reveal_expr param) ->
+											is_ref t
+										| _, [] | [], _ ->
+											false
+										| _ :: args, _ :: params ->
+											check args params
+									in
+									check args params
+								| _ -> false
+							)
+						| _ -> false
 		(**
 			Check if currently generated expression is located in a left part of assignment.
 		*)
@@ -1579,6 +1617,8 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 				| TBinop (operation, expr1, expr2) when needs_dereferencing (is_assignment_binop operation) expr1 ->
 					self#write_expr { expr with eexpr = TBinop (operation, self#dereference expr1, expr2) }
 				| TBinop (operation, expr1, expr2) -> self#write_expr_binop operation expr1 expr2
+				| TField ({ eexpr = TArrayDecl exprs }, faccess) when is_array_arr faccess && not self#current_expr_is_for_ref ->
+					self#write_native_array_decl exprs
 				| TField (fexpr, access) when is_php_global expr -> self#write_expr_php_global expr
 				| TField (fexpr, access) when is_php_class_const expr -> self#write_expr_php_class_const expr
 				| TField (fexpr, access) when needs_dereferencing (self#is_in_write_context) expr -> self#write_expr (self#dereference expr)
@@ -1660,16 +1700,27 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 						| _ ->
 							decl()
 					)
+				| _ ->
+					self#write ((self#use array_type_path) ^ "::wrap(");
+					self#write_native_array_decl exprs;
+					self#write ")"
+		(**
+			Writes native array declaration to output buffer
+		*)
+		method write_native_array_decl exprs =
+			match exprs with
+				| [] ->
+					self#write "[]";
 				| [expr] ->
-					self#write ((self#use array_type_path) ^ "::wrap([");
+					self#write "[";
 					self#write_expr expr;
-					self#write "])"
+					self#write "]"
 				| _ ->
-					self#write ((self#use array_type_path) ^ "::wrap([\n");
+					self#write "[\n";
 					self#indent_more;
 					List.iter (fun expr -> self#write_array_item ~separate_line:true expr) exprs;
 					self#indent_less;
-					self#write_with_indentation "])"
+					self#write_with_indentation "]"
 		(**
 			Write associative array declaration
 		*)
@@ -2123,6 +2174,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 				| Postfix ->
 					self#write_expr expr;
 					self#write (Ast.s_unop operation)
+
 		method private write_expr_for_field_access expr access_str field_str =
 			let access_str = ref access_str in
 			(match (reveal_expr expr).eexpr with
@@ -2845,6 +2897,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 			match arg with
 				| ({ v_name = arg_name; v_type = arg_type }, default_value) ->
 					vars#declared (vname arg_name);
+					if is_rest_type arg_type then self#write "...";
 					if is_ref arg_type then self#write "&";
 					self#write ("$" ^ (vname arg_name));
 					match default_value with

+ 2 - 1
src/generators/genpy.ml

@@ -1058,6 +1058,7 @@ module Printer = struct
 		| Not -> "not "
 		| Neg -> "-";
 		| NegBits -> "~"
+		| Spread -> "*"
 
 	let print_binop = function
 		| OpAdd -> "+"
@@ -1128,7 +1129,7 @@ module Printer = struct
 					if !had_kw_args then abort "Arguments after KwArgs are not allowed" p;
 					had_kw_args := true;
 					"**" ^ name
-				| TAbstract({a_path = ["python"],"VarArgs"},_) ->
+				| TAbstract({a_path = (["python"],"VarArgs" | ["haxe"],"Rest")},_) ->
 					check_err ();
 					had_var_args := true;
 					"*" ^ name

+ 96 - 13
src/generators/genswf9.ml

@@ -86,6 +86,22 @@ type context = {
 	boot : path;
 	swf_protected : bool;
 	need_ctor_skip : bool;
+	(*
+		Takes an argument list of `call(arg1, arg2, ...)` and if there is a trailing argument `...rest` then returns
+		```
+		if(rest.length == 0) call(arg1, arg2)
+		else if(rest.length == 1) call(arg1, arg2, rest[0])
+		else if(rest.length == 2) call(arg1, arg2, rest[0], rest[1])
+		<... up to 20 rest args ...>
+		else throw "Too many rest arguments"
+		```
+		otherwise returns `None`
+	*)
+	handle_spread_args : basic_types ->
+						texpr list (* arguments *) ->
+						t (* call result type *) ->
+						(texpr list -> texpr) (* a function which takes an argument list and should return a call expression *) ->
+						texpr option;
 	mutable cur_class : tclass;
 	mutable debug : bool;
 	mutable last_line : int;
@@ -762,12 +778,13 @@ let begin_fun ctx args tret el stat p =
 		| None -> if c <> None then dparams := Some [v]
 		| Some l -> dparams := Some (v :: l)
 	in
-
 	let args, varargs = (match List.rev args with
 		| (({ v_name = "__arguments__"; v_type = t } as v),_) :: l ->
 			(match follow t with
 			| TInst ({ cl_path = ([],"Array") },_) -> List.rev l, Some (v,true)
 			| _ -> List.rev l, Some(v,false))
+		| (v,_) :: l when ExtType.is_rest (Type.follow v.v_type) ->
+			List.rev l, Some (v,true)
 		| _ ->
 			args, None
 	) in
@@ -1112,7 +1129,12 @@ let rec gen_expr_content ctx retval e =
 	| TBinop (op,e1,e2) ->
 		gen_binop ctx retval op e1 e2 e.etype e.epos
 	| TCall (f,el) ->
-		gen_call ctx retval f el e.etype
+		(match ctx.handle_spread_args ctx.com.basic el e.etype (fun el -> { e with eexpr = TCall(f,el) }) with
+		| Some e ->
+			gen_expr ctx retval e
+		| None ->
+			gen_call ctx retval f el e.etype
+		)
 	| TNew ({ cl_path = [],"Array" },_,[]) ->
 		(* it seems that [] is 4 time faster than new Array() *)
 		write ctx (HArray 0)
@@ -1121,17 +1143,22 @@ let rec gen_expr_content ctx retval e =
 		write ctx HThrow;
 		no_value ctx retval
 	| TNew (c,tl,pl) ->
-		let id = type_id ctx (TInst (c,tl)) in
-		(match id with
-		| HMParams _ ->
-			gen_type ctx id;
-			List.iter (gen_expr ctx true) pl;
-			write ctx (HConstruct (List.length pl))
-		| _ ->
-			write ctx (HFindPropStrict id);
-			List.iter (gen_expr ctx true) pl;
-			write ctx (HConstructProperty (id,List.length pl))
-		);
+		(match ctx.handle_spread_args ctx.com.basic pl e.etype (fun pl -> { e with eexpr = TNew(c,tl,pl) }) with
+		| Some e ->
+			gen_expr ctx retval e
+		| None ->
+			let id = type_id ctx (TInst (c,tl)) in
+			(match id with
+			| HMParams _ ->
+				gen_type ctx id;
+				List.iter (gen_expr ctx true) pl;
+				write ctx (HConstruct (List.length pl))
+			| _ ->
+				write ctx (HFindPropStrict id);
+				List.iter (gen_expr ctx true) pl;
+				write ctx (HConstructProperty (id,List.length pl))
+			);
+		)
 	| TFunction f ->
 		write ctx (HFunction (generate_function ctx f true))
 	| TIf (e0,e1,e2) ->
@@ -1399,8 +1426,27 @@ let rec gen_expr_content ctx retval e =
 		end
 	| TIdent s ->
 		abort ("Unbound variable " ^ s) e.epos
+and args_as_array ctx mandatory_args spread_arg =
+	match mandatory_args with
+	| [] ->
+		spread_arg
+	| _ ->
+		let p = punion_el (List.map (fun e -> ((),e.epos)) mandatory_args) in
+		let array = mk (TArrayDecl mandatory_args) (ctx.com.basic.tarray t_dynamic) p in
+		let concat = mk (TField (array,FDynamic "concat")) t_dynamic spread_arg.epos in
+		mk (TCall (concat,[spread_arg])) (ctx.com.basic.tarray t_dynamic) (punion p spread_arg.epos)
 
 and gen_call ctx retval e el r =
+	match List.rev el with
+	(* generate a call with `...rest` as `callee.apply(null, [param1, param2].concat(rest))` *)
+	| { eexpr = TUnop (Spread, Prefix, rest) } :: el_rev ->
+		let null = mk (TConst TNull) t_dynamic null_pos
+		and t_array_dyn = ctx.com.basic.tarray t_dynamic in
+		let t = TFun (["thisArg",false,t_dynamic; "argArray",false,t_array_dyn],r) in
+		let apply = mk (TField (e,FDynamic "apply")) t e.epos in
+		gen_call ctx retval apply [null; args_as_array ctx (List.rev el_rev) rest] r
+	(* normal call without `...rest` *)
+	| _ ->
 	match e.eexpr , el with
 	| TIdent "__is__", [e;t] ->
 		gen_expr ctx true e;
@@ -1636,6 +1682,8 @@ and gen_unop ctx retval op flag e =
 	| NegBits ->
 		gen_expr ctx true e;
 		write ctx (HOp A3OBitNot);
+	| Spread ->
+		die ~p:e.epos "Unhandled spread operator" __LOC__
 	| Increment
 	| Decrement ->
 		let incr = (op = Increment) in
@@ -2785,6 +2833,41 @@ let generate com boot_name =
 	let ctx = {
 		com = com;
 		need_ctor_skip = Common.has_feature com "Type.createEmptyInstance";
+		handle_spread_args = (fun basic args t_result args_to_expr ->
+			match List.rev args with
+			| { eexpr = TUnop (Spread,Prefix,rest) } :: args_rev ->
+				let t_rest_item = match Type.follow rest.etype with TAbstract (_,[t]) -> t | _ -> die "" __LOC__ in
+				let t_array_dyn = basic.tarray t_dynamic in
+				let c_array = match t_array_dyn with TInst (c,_) -> c | _ -> die "" __LOC__ in
+				let length =
+					let faccess =
+						try
+							let cf = PMap.find "length" c_array.cl_fields in
+							FInstance (c_array,[t_dynamic],cf)
+						with Not_found ->
+							FDynamic "length"
+					in
+					mk (TField (rest,faccess)) basic.tint rest.epos
+				in
+				let const n = mk (TConst (TInt (Int32.of_int n))) basic.tint rest.epos in
+				let check n =
+					mk (TBinop (OpEq, length, const n)) basic.tbool rest.epos
+				in
+				let rec nargs n acc =
+					if n < 0 then acc
+					else nargs (n - 1) ((mk (TArray (rest, const n)) t_rest_item rest.epos) :: acc)
+				in
+				let rec loop n e_else =
+					let args = (nargs (n - 1) args_rev) in
+					let e = mk (TIf (check n, args_to_expr args, Some e_else)) t_result rest.epos in
+					if n = 0 then e
+					else loop (n - 1) e
+				in
+				let msg = mk (TConst (TString "Too many rest arguments")) basic.tstring rest.epos in
+				Some (loop 20 (mk (TThrow msg) t_dynamic rest.epos))
+			| _ ->
+				None
+		);
 		debug = com.Common.debug;
 		cur_class = null_class;
 		boot = ([],boot_name);

+ 1 - 1
src/macro/eval/evalDebugMisc.ml

@@ -285,7 +285,7 @@ let rec expr_to_value ctx env e =
 				end
 			| NegBits ->
 				op_sub (pos e) (vint32 (Int32.minus_one)) (loop e1)
-			| Increment | Decrement ->
+			| Increment | Decrement | Spread ->
 				raise Exit
 			end
 		| ECall(e1,el) ->

+ 4 - 0
src/macro/eval/evalJit.ml

@@ -172,6 +172,10 @@ and unop jit op flag e1 p =
 		end
 	| Decrement ->
 		op_decr jit e1 (flag = Prefix) p
+	| Spread ->
+		match flag with
+		| Postfix -> die ~p:p "Postfix spread operator is not supported" __LOC__
+		| Prefix -> jit_expr jit false e1
 
 and jit_default jit return def =
 	match def with

+ 2 - 0
src/macro/macroApi.ml

@@ -270,6 +270,7 @@ let encode_unop op =
 	| Not -> 2
 	| Neg -> 3
 	| NegBits -> 4
+	| Spread -> 5
 	in
 	encode_enum IUnop tag []
 
@@ -591,6 +592,7 @@ let decode_unop op =
 	| 2, [] -> Not
 	| 3, [] -> Neg
 	| 4, [] -> NegBits
+	| 5, [] -> Spread
 	| _ -> raise Invalid_expr
 
 let decode_import_mode t =

+ 36 - 0
src/optimization/inline.ml

@@ -650,6 +650,7 @@ let rec type_inline ctx cf f ethis params tret config p ?(self_calling_closure=f
 		| None -> raise Exit
 		| Some e -> Some e)
 	with Exit ->
+	let params = inline_rest_params ctx f params p in
 	let state = new inline_state ctx ethis params cf f p in
 	let vthis_opt = state#initialize in
 	let opt f = function
@@ -882,3 +883,38 @@ and type_inline_ctor ctx c cf tf ethis el po =
 			{tf with tf_expr = mk (TBlock (field_inits @ bl)) ctx.t.tvoid c.cl_pos}
 	in
 	type_inline ctx cf tf ethis el ctx.t.tvoid None po true
+
+and inline_rest_params ctx f params p =
+	if not ctx.com.config.pf_supports_rest_args then
+		params
+	else
+		let rec loop args params =
+			match args, params with
+			(* last argument expects rest parameters *)
+			| [(v,_)], params when ExtType.is_rest (follow v.v_type) ->
+				(match params with
+				(* In case of `...rest` just use `rest` *)
+				| [{ eexpr = TUnop(Spread,Prefix,e) }] -> [e]
+				(* In other cases: `haxe.Rest.of([param1, param2, ...])` *)
+				| _ ->
+					match follow v.v_type with
+					| TAbstract ({ a_path = ["haxe"],"Rest"; a_impl = Some c } as a, [t]) as rest_t ->
+						let cf =
+							try PMap.find "of" c.cl_statics
+							with Not_found -> die ~p:c.cl_name_pos "Can't find haxe.Rest.of function" __LOC__
+						and p = punion_el (List.map (fun e -> (),e.epos) params) in
+						(* [param1, param2, ...] *)
+						let array = mk (TArrayDecl params) (ctx.t.tarray t) p in
+						(* haxe.Rest.of(array) *)
+						[make_static_call ctx c cf (apply_params a.a_params [t]) [array] rest_t p]
+					| _ ->
+						die ~p:v.v_pos "Unexpected rest arguments type" __LOC__
+				)
+			| a :: args, e :: params ->
+				e :: loop args params
+			| [], params ->
+				params
+			| _ :: _, [] ->
+				[]
+		in
+		loop f.tf_args params

+ 13 - 1
src/syntax/grammar.mly

@@ -668,6 +668,13 @@ and parse_complex_type_inner allow_named = parser
 		| [< >] -> serror())
 	| [< '(Question,p1); t,p2 = parse_complex_type_inner allow_named >] ->
 		CTOptional (t,p2),punion p1 p2
+	| [< '(Spread,p1); t,p2 = parse_complex_type_inner allow_named >] ->
+		let hint =
+			match t with
+			| CTNamed (_,hint) -> hint
+			| _ -> (t,p2)
+		in
+		CTPath (mk_type_path ~params:[TPType hint] (["haxe"],"Rest")),punion p1 p2
 	| [< n = dollar_ident; s >] ->
 		(match s with parser
 		| [< '(DblDot,_) when allow_named; t = parse_complex_type >] ->
@@ -991,6 +998,9 @@ and parse_fun_param s =
 	match s with parser
 	| [< '(Question,_); name, pn = dollar_ident; t = popt parse_type_hint; c = parse_fun_param_value >] -> ((name,pn),true,meta,t,c)
 	| [< name, pn = dollar_ident; t = popt parse_type_hint; c = parse_fun_param_value >] -> ((name,pn),false,meta,t,c)
+	| [< '(Spread,_); name, pn = dollar_ident; t = parse_type_hint; c = parse_fun_param_value >] ->
+		let t = CTPath (mk_type_path ~params:[TPType t] (["haxe"],"Rest")), snd t in
+		((name,pn),false,meta,Some t,c)
 
 and parse_fun_param_value = parser
 	| [< '(Binop OpAssign,_); e = expr >] -> Some e
@@ -1361,7 +1371,8 @@ and expr = parser
 		)
 	| [< '(BkOpen,p1); e = parse_array_decl p1; s >] -> expr_next e s
 	| [< '(Kwd Function,p1); e = parse_function p1 false; >] -> e
-	| [< '(Unop op,p1) when is_prefix op; e = expr >] -> make_unop op e p1
+	| [< '(Unop op,p1); e = expr >] -> make_unop op e p1
+	| [< '(Spread,p1); e = expr >] -> make_unop Spread e (punion p1 (pos e))
 	| [< '(Binop OpSub,p1); e = expr >] ->
 		make_unop Neg e p1
 	(*/* removed unary + : this cause too much syntax errors go unnoticed, such as "a + + 1" (missing 'b')
@@ -1481,6 +1492,7 @@ and expr_next' e1 = parser
 		| [< e2 = secure_expr >] ->
 			make_binop OpGt e1 e2)
 	| [< '(Binop op,_); e2 = secure_expr >] -> make_binop op e1 e2
+	| [< '(Spread,_); e2 = secure_expr >] -> make_binop OpInterval e1 e2
 	| [< '(Unop op,p) when is_postfix e1 op; s >] ->
 		expr_next (EUnop (op,Postfix,e1), punion (pos e1) p) s
 	| [< '(Question,_); e2 = expr; s >] ->

+ 1 - 1
src/syntax/lexer.ml

@@ -355,7 +355,7 @@ let rec token lexbuf =
 	| "||" -> mk lexbuf (Binop OpBoolOr)
 	| "<<" -> mk lexbuf (Binop OpShl)
 	| "->" -> mk lexbuf Arrow
-	| "..." -> mk lexbuf (Binop OpInterval)
+	| "..." -> mk lexbuf Spread
 	| "=>" -> mk lexbuf (Binop OpArrow)
 	| "!" -> mk lexbuf (Unop Not)
 	| "<" -> mk lexbuf (Binop OpLt)

+ 1 - 0
src/syntax/reification.ml

@@ -277,6 +277,7 @@ let reify in_macro =
 				| Not -> "OpNot"
 				| Neg -> "OpNeg"
 				| NegBits -> "OpNegBits"
+				| Spread -> "OpSpread"
 			) [] p in
 			expr "EUnop" [op;to_bool (flag = Postfix) p;loop e]
 		| EVars vl ->

+ 44 - 3
src/typing/callUnification.ml

@@ -95,10 +95,51 @@ let rec unify_call_args ctx el args r callp inline force_inline in_overload =
 				| name :: _ -> call_error (Cannot_skip_non_nullable name) callp;
 			end;
 			[]
-		| _,[name,false,t] when (match follow t with TAbstract({a_path = ["haxe";"extern"],"Rest"},_) -> true | _ -> false) ->
+		| _,[name,false,TAbstract({ a_path = ["cpp"],"Rest" },[t])] ->
+			(try List.map (fun e -> type_against name t e) el
+			with WithTypeError(ul,p) -> arg_error ul name false p)
+		| _,[name,false,t] when ExtType.is_rest (follow t) ->
 			begin match follow t with
-				| TAbstract({a_path=(["haxe";"extern"],"Rest")},[t]) ->
-					(try List.map (fun e -> type_against name t e) el with WithTypeError(ul,p) -> arg_error ul name false p)
+				| TAbstract({a_path=(["haxe"],"Rest")},[arg_t]) ->
+					let unexpected_spread p =
+						arg_error (Custom "Cannot spread arguments with additional rest arguments") name false p
+					in
+					(* these platforms deal with rest args on their own *)
+					if ctx.com.config.pf_supports_rest_args then
+						match el with
+						| [(EUnop (Spread,Prefix,e),p)] ->
+							(try [mk (TUnop (Spread, Prefix, type_against name t e)) t p]
+							with WithTypeError(ul,p) -> arg_error ul name false p)
+						| _ ->
+							(try
+								List.map (fun e ->
+									match e with
+									| (EUnop (Spread,Prefix,_),p) ->
+										unexpected_spread p
+									| _ -> type_against name arg_t e
+								) el
+							with WithTypeError(ul,p) ->
+								arg_error ul name false p)
+					(* for other platforms make sure rest arguments are wrapped in an array *)
+					else begin
+						match el with
+						| [(EUnop (Spread,Prefix,e),p)] ->
+							(try [type_against name t e]
+							with WithTypeError(ul,p) -> arg_error ul name false p)
+						| [] ->
+							(try [type_against name t (EArrayDecl [],callp)]
+							with WithTypeError(ul,p) -> arg_error ul name false p)
+						| (_,p1) :: _ ->
+							let p =
+								List.fold_left (fun p (e1,p2) ->
+									match e1 with
+									| EUnop (Spread,Prefix,_) -> unexpected_spread p2
+									| _ -> punion p p2
+								) p1 el
+							in
+							(try [type_against name t (EArrayDecl el,p)]
+							with WithTypeError(ul,p) -> arg_error ul name false p)
+					end
 				| _ ->
 					die "" __LOC__
 			end

+ 5 - 8
src/typing/functionArguments.ml

@@ -90,14 +90,11 @@ object(self)
 			l
 
 	method private check_rest (is_last : bool) (eo : expr option) (opt : bool) (t : Type.t) (pn : pos) =
-		match follow t with
-			| TAbstract({a_path = ["haxe";"extern"],"Rest"},_) ->
-				if not is_extern then error "Rest argument are only supported for extern methods" pn;
-				if opt then error "Rest argument cannot be optional" pn;
-				begin match eo with None -> () | Some (_,p) -> error "Rest argument cannot have default value" p end;
-				if not is_last then error "Rest should only be used for the last function argument" pn;
-			| _ ->
-				()
+		if ExtType.is_rest (follow t) then begin
+			if opt then error "Rest argument cannot be optional" pn;
+			begin match eo with None -> () | Some (_,p) -> error "Rest argument cannot have default value" p end;
+			if not is_last then error "Rest should only be used for the last function argument" pn;
+		end
 
 	(* Returns the `(tvar * texpr option) list` for `tf_args`. Also checks the validity of argument names and whether or not
 	   an argument should be displayed. *)

+ 7 - 0
src/typing/operators.ml

@@ -801,6 +801,9 @@ let type_unop ctx op flag e with_type p =
 		| _ ->
 			raise Not_found
 	in
+	let unexpected_spread p =
+		error "Spread unary operator is only allowed for unpacking the last argument in a call with rest arguments" p
+	in
 	let make e =
 		let check_int () =
 			match classify e.etype with
@@ -825,6 +828,8 @@ let type_unop ctx op flag e with_type p =
 				check_int()
 			| Neg ->
 				check_int()
+			| Spread ->
+				unexpected_spread p
 		in
 		mk (TUnop (op,flag,e)) t p
 	in
@@ -835,6 +840,8 @@ let type_unop ctx op flag e with_type p =
 			make e
 	in
 	match op with
+	| Spread ->
+		unexpected_spread p
 	| Not | Neg | NegBits ->
 		let access_get = !type_access_ref ctx (fst e) (snd e) MGet WithType.value (* WITHTYPETODO *) in
 		let e = acc_get ctx access_get p in

+ 13 - 10
src/typing/overloadResolution.ml

@@ -13,16 +13,19 @@ let unify_cf map_type c cf el =
 						Type.unify e.etype t;
 						loop2 (e :: acc) el tl
 					with _ ->
-						match t,tl with
-						| TAbstract({a_path=["haxe";"extern"],"Rest"},[t]),[] ->
-							begin try
-								let el = List.map (fun e -> unify t e.etype; e) el in
-								let fcc = make_field_call_candidate ((List.rev acc) @ el) ret monos tf cf (c,cf,monos) in
-								Some fcc
-							with _ ->
-								None
-							end
-						| _ ->
+						if Type.ExtType.is_rest (follow t) then
+							match follow t,tl with
+							| TAbstract({a_path=["haxe"],"Rest"},[t]),[] ->
+								begin try
+									let el = List.map (fun e -> unify t e.etype; e) el in
+									let fcc = make_field_call_candidate ((List.rev acc) @ el) ret monos tf cf (c,cf,monos) in
+									Some fcc
+								with _ ->
+									None
+								end
+							| _ ->
+								Globals.die "" __LOC__
+						else
 							None
 					end
 				| [],[] ->

+ 25 - 0
std/cpp/Rest.hx

@@ -0,0 +1,25 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package cpp;
+
+abstract Rest<T>(Array<T>) {}

+ 0 - 2
std/cpp/Stdio.hx

@@ -22,8 +22,6 @@
 
 package cpp;
 
-import haxe.extern.Rest;
-
 @:include("stdio.h")
 extern class Stdio {
 	@:native("printf")

+ 50 - 0
std/cs/_std/haxe/Rest.hx

@@ -0,0 +1,50 @@
+package haxe;
+
+import haxe.iterators.RestIterator;
+import haxe.iterators.RestKeyValueIterator;
+import cs.NativeArray;
+import cs.system.Array as CsArray;
+
+private typedef NativeRest<T> = #if erase_generics NativeArray<Dynamic> #else NativeArray<T> #end;
+
+@:coreApi
+abstract Rest<T>(NativeRest<T>) {
+	public var length(get,never):Int;
+	inline function get_length():Int
+		return this.Length;
+
+	@:from static public inline function of<T>(array:Array<T>):Rest<T>
+		return new Rest(@:privateAccess array.__a);
+
+	inline function new(a:NativeRest<T>):Void
+		this = a;
+
+	@:arrayAccess inline function get(index:Int):T
+		return this[index];
+
+	@:to public function toArray():Array<T> {
+		var result = new NativeRest(this.Length);
+		CsArray.Copy(this, 0, result, 0, this.Length);
+		return @:privateAccess Array.ofNative(result);
+	}
+
+	public inline function iterator():RestIterator<T>
+		return new RestIterator<T>(this);
+
+	public inline function keyValueIterator():RestKeyValueIterator<T>
+		return new RestKeyValueIterator<T>(this);
+
+	public function append(item:T):Rest<T> {
+		var result = new NativeRest(this.Length + 1);
+		CsArray.Copy(this, 0, result, 0, this.Length);
+		result[this.Length] = item;
+		return new Rest(result);
+	}
+
+	public function prepend(item:T):Rest<T> {
+		var result = new NativeRest(this.Length + 1);
+		CsArray.Copy(this, 0, result, 1, this.Length);
+		result[0] = item;
+		return new Rest(result);
+	}
+}

+ 70 - 0
std/haxe/Rest.hx

@@ -0,0 +1,70 @@
+package haxe;
+
+import haxe.iterators.RestIterator;
+import haxe.iterators.RestKeyValueIterator;
+
+private typedef NativeRest<T> = Array<T>;
+
+/**
+	A special type that represents "rest" function argument.
+
+	Should be used as a type for the last argument of a method, representing that
+	arbitrary number of arguments of given type can be passed to that method.
+
+	Allows to use array access by index to get values of rest arguments.
+	If the index exceeds the amount of rest arguments passed, the result is unspecified.
+**/
+@:coreApi
+abstract Rest<T>(NativeRest<T>) {
+	/** Amount of arguments passed as rest arguments */
+	public var length(get,never):Int;
+	inline function get_length():Int
+		return this.length;
+
+	/**
+		Create rest arguments using contents of `array`.
+
+		WARNING:
+		Depending on a target platform modifying `array` after using this method
+		may affect the created `Rest` instance.
+		Use `Rest.of(array.copy())` to avoid that.
+	**/
+	@:from static public inline function of<T>(array:Array<T>):Rest<T>
+		return new Rest(array);
+
+	inline function new(array:Array<T>):Void
+		this = array;
+
+	@:arrayAccess inline function get(index:Int):T
+		return this[index];
+
+	/**
+		Creates an array containing all the values of rest arguments.
+	**/
+	@:to public #if !cppia inline #end function toArray():Array<T>
+		return this.copy();
+
+	public inline function iterator():RestIterator<T>
+		return new RestIterator<T>(this);
+
+	public inline function keyValueIterator():RestKeyValueIterator<T>
+		return new RestKeyValueIterator<T>(this);
+
+	/**
+		Create a new rest arguments collection by appending `item` to this one.
+	**/
+	public function append(item:T):Rest<T> {
+		var result = this.copy();
+		result.push(item);
+		return new Rest(result);
+	}
+
+	/**
+		Create a new rest arguments collection by prepending this one with `item`.
+	**/
+	public function prepend(item:T):Rest<T> {
+		var result = this.copy();
+		result.unshift(item);
+		return new Rest(result);
+	}
+}

+ 4 - 4
std/haxe/extern/Rest.hx

@@ -23,12 +23,12 @@
 package haxe.extern;
 
 /**
-	A special abstract type that represents "rest" function argument.
-
+	DEPRECATED: use haxe.Rest instead.
+	
+	A special type that represents "rest" function argument.
 	Should be used as a type for the last argument of an extern method,
 	representing that arbitrary number of arguments of given type can be
 	passed to that method.
-
 	@see <https://haxe.org/manual/lf-externs.html>
 **/
-abstract Rest<T>(Array<T>) {}
+typedef Rest<T> = haxe.Rest<T>

+ 19 - 0
std/haxe/iterators/RestIterator.hx

@@ -0,0 +1,19 @@
+package haxe.iterators;
+
+class RestIterator<T> {
+	final args:Rest<T>;
+	var current:Int = 0;
+
+	@:allow(haxe.Rest)
+	inline function new(args:Any) {
+		this.args = args;
+	}
+
+	public inline function hasNext():Bool {
+		return current < args.length;
+	}
+
+	public inline function next():T {
+		return args[current++];
+	}
+}

+ 19 - 0
std/haxe/iterators/RestKeyValueIterator.hx

@@ -0,0 +1,19 @@
+package haxe.iterators;
+
+class RestKeyValueIterator<T> {
+	final args:Rest<T>;
+	var current:Int = 0;
+
+	@:allow(haxe.Rest)
+	inline function new(args:Any) {
+		this.args = args;
+	}
+
+	public inline function hasNext():Bool {
+		return current < args.length;
+	}
+
+	public inline function next():{key:Int, value:T} {
+		return {key:current, value:args[current++]};
+	}
+}

+ 5 - 0
std/haxe/macro/Expr.hx

@@ -245,6 +245,11 @@ enum Unop {
 		`~`
 	**/
 	OpNegBits;
+
+	/**
+		`...`
+	**/
+	OpSpread;
 }
 
 /**

+ 1 - 0
std/haxe/macro/Printer.hx

@@ -47,6 +47,7 @@ class Printer {
 			case OpNot: "!";
 			case OpNeg: "-";
 			case OpNegBits: "~";
+			case OpSpread: "...";
 		}
 
 	public function printBinop(op:Binop)

+ 60 - 0
std/java/_std/haxe/Rest.hx

@@ -0,0 +1,60 @@
+package haxe;
+
+import haxe.iterators.RestIterator;
+import haxe.iterators.RestKeyValueIterator;
+import java.NativeArray;
+import java.lang.System;
+import java.lang.Object;
+import java.util.Arrays;
+
+private typedef NativeRest<T> = NativeArray<Object>;
+
+@:coreApi
+abstract Rest<T>(NativeRest<T>) {
+	public var length(get,never):Int;
+	inline function get_length():Int
+		return this.length;
+
+	@:from static public function of<T>(array:Array<T>):Rest<T> {
+		var native = @:privateAccess array.__a;
+		var result:NativeRest<T>;
+		#if jvm
+			result = (cast native:Object).clone();
+		#else
+			result = new NativeRest<T>(native.length);
+			for(i in 0...native.length)
+				result[i] = cast native[i];
+		#end
+		return new Rest(result);
+	}
+
+	inline function new(a:NativeRest<T>):Void
+		this = a;
+
+	@:arrayAccess inline function get(index:Int):T
+		return cast this[index];
+
+	@:to public function toArray():Array<T> {
+		return [for(i in 0...this.length) cast this[i]];
+	}
+
+	public inline function iterator():RestIterator<T>
+		return new RestIterator<T>(this);
+
+	public inline function keyValueIterator():RestKeyValueIterator<T>
+		return new RestKeyValueIterator<T>(this);
+
+	public function append(item:T):Rest<T> {
+		var result = new NativeRest<T>(this.length + 1);
+		System.arraycopy(this, 0, result, 0, this.length);
+		result[this.length] = cast item;
+		return new Rest(result);
+	}
+
+	public function prepend(item:T):Rest<T> {
+		var result = new NativeRest<T>(this.length + 1);
+		System.arraycopy(this, 0, result, 1, this.length);
+		result[0] = cast item;
+		return new Rest(result);
+	}
+}

+ 51 - 0
std/lua/_std/haxe/Rest.hx

@@ -0,0 +1,51 @@
+package haxe;
+
+import lua.Lua.select;
+import lua.Table;
+import lua.PairTools.copy;
+import lua.TableTools.maxn;
+import lua.TableTools.pack;
+import lua.TableTools.unpack;
+import haxe.iterators.RestIterator;
+import haxe.iterators.RestKeyValueIterator;
+
+private typedef NativeRest<T> = Table<Int,T>;
+
+@:coreApi
+abstract Rest<T>(NativeRest<T>) {
+	public var length(get, never):Int;
+	inline function get_length():Int
+		return maxn(this);
+
+	@:from static public function of<T>(array:Array<T>):Rest<T> {
+		return new Rest(Table.fromArray(array));
+	}
+
+	inline function new(table:Table<Int,T>):Void
+		this = table;
+
+	@:arrayAccess inline function get(index:Int):T
+		return this[index + 1];
+
+	@:to public function toArray():Array<T> {
+		return Table.toArray(this);
+	}
+
+	public inline function iterator():RestIterator<T>
+		return new RestIterator<T>(this);
+
+	public inline function keyValueIterator():RestKeyValueIterator<T>
+		return new RestKeyValueIterator<T>(this);
+
+	public inline function append(item:T):Rest<T> {
+		var result = copy(this);
+		Table.insert(result, item);
+		return new Rest(result);
+	}
+
+	public inline function prepend(item:T):Rest<T> {
+		var result = copy(this);
+		Table.insert(result, 1, item);
+		return new Rest(result);
+	}
+}

+ 1 - 1
std/php/Syntax.hx

@@ -22,7 +22,7 @@
 
 package php;
 
-import haxe.extern.Rest;
+import haxe.Rest;
 import haxe.extern.AsVar;
 import haxe.extern.EitherType;
 

+ 46 - 0
std/php/_std/haxe/Rest.hx

@@ -0,0 +1,46 @@
+package haxe;
+
+import php.*;
+import haxe.iterators.RestIterator;
+import haxe.iterators.RestKeyValueIterator;
+
+private typedef NativeRest<T> = NativeIndexedArray<T>;
+
+@:coreApi
+@:semantics(value)
+abstract Rest<T>(NativeRest<T>) {
+	public var length(get,never):Int;
+	inline function get_length():Int
+		return Global.count(this);
+
+	@:from
+	static public inline function of<T>(array:Array<T>):Rest<T>
+		return new Rest(@:privateAccess array.arr);
+
+	inline function new(a:NativeIndexedArray<T>):Void
+		this = a;
+
+	@:arrayAccess inline function get(index:Int):T
+		return this[index];
+
+	@:to public inline function toArray():Array<T>
+		return @:privateAccess Array.wrap(this);
+
+	public inline function iterator():RestIterator<T>
+		return new RestIterator<T>(this);
+
+	public inline function keyValueIterator():RestKeyValueIterator<T>
+		return new RestKeyValueIterator<T>(this);
+
+	public inline function append(item:T):Rest<T> {
+		var result = this;
+		result.push(item);
+		return new Rest(result);
+	}
+
+	public inline function prepend(item:T):Rest<T> {
+		var result = this;
+		Global.array_unshift(result, item);
+		return new Rest(result);
+	}
+}

+ 0 - 4
tests/misc/projects/Issue3238/NonExtern.hx

@@ -1,4 +0,0 @@
-class NonExtern {
-    static function f(a:haxe.extern.Rest<String>):Void {}
-	static function main() {}
-}

+ 0 - 1
tests/misc/projects/Issue3238/non-extern-fail.hxml

@@ -1 +0,0 @@
---run NonExtern

+ 0 - 1
tests/misc/projects/Issue3238/non-extern-fail.hxml.stderr

@@ -1 +0,0 @@
-NonExtern.hx:2: characters 23-24 : Rest argument are only supported for extern methods

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

@@ -74,6 +74,7 @@ function main() {
 		new TestNull(),
 		new TestNumericCasts(),
 		new TestHashMap(),
+		new TestRest(),
 		#if (!no_http && (!github || !(php && Windows)))
 		new TestHttp(),
 		#end

+ 1 - 0
tests/unit/src/unit/TestMatch.hx

@@ -327,6 +327,7 @@ class TestMatch extends Test {
 			case OpIncrement:
 			case OpDecrement:
 			case OpNot:
+			case OpSpread:
 		}));
 		eq("Unmatched patterns: Node(Node, _)", getErrorMessage(switch(Leaf("foo")) {
 			case Node(Leaf("foo"), _):

+ 156 - 0
tests/unit/src/unit/TestRest.hx

@@ -0,0 +1,156 @@
+package unit;
+
+import haxe.Rest;
+
+class TestRest extends Test {
+	function testArrayAccess() {
+		function rest(a:Int, b:Int, ...r:Int) {
+			return r[2];
+		}
+		eq(123, rest(1, 2, 0, 0, 123, 0));
+	}
+
+	function testLength() {
+		function rest(...r:Int) {
+			return r.length;
+		}
+		eq(4, rest(1, 2, 3, 4));
+	}
+
+	function testToArray() {
+		function rest(...r:Int):Array<Int> {
+			var a = r.toArray();
+			a[0] = 999; //make sure array modification doesn't affect rest arguments object
+			return r.toArray();
+		}
+		aeq([1, 2, 3, 4], rest(1, 2, 3, 4));
+	}
+
+	@:depends(testToArray)
+	function testRestReturn() {
+		function rest(...r:Int):Rest<Int> {
+			return r;
+		}
+		aeq([1, 2, 3, 4], rest(1, 2, 3, 4).toArray());
+	}
+
+	function testIterator() {
+		function rest(...r:Int):Array<Int> {
+			return [for(i in r) i];
+		}
+		aeq([3, 2, 1], rest(3, 2, 1));
+	}
+
+	function testKeyValueIterator() {
+		function rest(...r:Int):{keys:Array<Int>, values:Array<Int>} {
+			var keys = [];
+			var values = [];
+			for(k => v in r) {
+				keys.push(k);
+				values.push(v);
+			}
+			return {keys:keys, values:values}
+		}
+		var r = rest(3, 2, 1, 0);
+		aeq([0, 1, 2, 3], r.keys);
+		aeq([3, 2, 1, 0], r.values);
+	}
+
+	@:depends(testToArray)
+	function testAppend() {
+		function rest(...r:Int) {
+			var appended = r.append(9);
+			return {initial:r.toArray(), appended:appended.toArray()}
+		}
+		var result = rest(1, 2);
+		aeq([1, 2], result.initial);
+		aeq([1, 2, 9], result.appended);
+	}
+
+	@:depends(testToArray)
+	function testSpread() {
+		function rest(...r:Int) {
+			return r.toArray();
+		}
+		function spreadRest(...r:Int) {
+			return rest(...r);
+		}
+		aeq([1, 2, 3], rest(...[1, 2, 3]));
+		aeq([3, 2, 1], spreadRest(3, 2, 1));
+		aeq([1, 2, 3], new Parent(...[1, 2, 3]).ctorArgs.toArray());
+	}
+
+	@:depends(testToArray, testRestReturn)
+	function testInheritance() {
+		var p = new Parent(1, 2, 3);
+		var c = new Child(4, 5, 6);
+		aeq([1, 2, 3], p.ctorArgs.toArray());
+		aeq([4, 5, 6, 999], c.ctorArgs.toArray());
+
+		aeq([7, 8, 9], p.methodWithRest(7, 8, 9).toArray());
+		aeq([7, 8, 9, 999], c.methodWithRest(7, 8, 9).toArray());
+	}
+
+	function testInline() {
+		inline function rest(args:Rest<Int>) {
+			return args[2];
+		}
+		eq(3, rest(1, 2, 3, 4));
+		eq(7, rest(...[5, 6, 7, 8]));
+	}
+
+	@:depends(testToArray)
+	function testClosure() {
+		var fn:(...r:Int)->Array<Int>;
+		fn = staticRest;
+		aeq([1, 2, 3], fn(1, 2, 3));
+		aeq([1, 2, 3], fn(...[1, 2, 3]));
+		fn = instanceRest;
+		aeq([3, 2, 1], fn(3, 2, 1));
+		aeq([3, 2, 1], fn(...[3, 2, 1]));
+	}
+	function instanceRest(...r:Int):Array<Int> {
+		return r.toArray();
+	}
+	static function staticRest(...r:Int):Array<Int> {
+		return r.toArray();
+	}
+
+	@:depends(testToArray)
+	function testRestOfArrays() {
+		function rest(...r:Array<Int>) {
+			return r.toArray();
+		}
+		aeq([[1, 2, 3], [6, 5, 4]], rest([1, 2, 3], [6, 5, 4]));
+	}
+
+	@:depends(testToArray)
+	function testRestOfObjects() {
+		function rest(...r:{f:Int}) {
+			return r.toArray();
+		}
+		aeq([{f:2}, {f:1}], rest({f:2}, {f:1}));
+	}
+}
+
+private class Parent {
+	public final ctorArgs:Rest<Int>;
+
+	public function new(rest:Rest<Int>) {
+		ctorArgs = rest;
+	}
+
+	public function methodWithRest(rest:Rest<Int>):Rest<Int> {
+		return rest;
+	}
+}
+
+private class Child extends Parent {
+	public function new(rest:Rest<Int>) {
+		super(...rest.append(999));
+	}
+
+	override public function methodWithRest(rest:Rest<Int>):Rest<Int> {
+		return super.methodWithRest(...rest.append(999));
+	}
+}