Bläddra i källkod

[java][jvm] Parse varargs as haxe.extern.Rest (#9846)

* pars varargs as haxe.extern.Rest

* parse varargs as haxe.Rest; added Rest.ofNative for java/jvm

* cleanup
Aleksandr Kuzmenko 4 år sedan
förälder
incheckning
48b514e4bd

+ 3 - 5
libs/javalib/jReader.ml

@@ -352,13 +352,11 @@ let rec expand_constant consts i =
 let parse_access_flags ch all_flags =
   let fl = read_ui16 ch in
   let flags = ref [] in
-  let fbit = ref 0 in
-  List.iter (fun f ->
-    if fl land (1 lsl !fbit) <> 0 then begin
+  List.iteri (fun fbit f ->
+    if fl land (1 lsl fbit) <> 0 then begin
       flags := f :: !flags;
       if f = JUnusable then error ("Unusable flag: " ^ string_of_int fl)
-    end;
-    incr fbit
+    end
   ) all_flags;
   (*if fl land (0x4000 - (1 lsl !fbit)) <> 0 then error ("Invalid access flags " ^ string_of_int fl);*)
   !flags

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

@@ -1017,7 +1017,8 @@ let get_boxed gen t =
 		get (["java";"lang"], "Character")
 	| TAbstract({ a_path = ([],"Single") }, []) ->
 		get (["java";"lang"], "Float")
-	| TAbstract({ a_path = (["java"],"Int64") }, []) ->
+	| TAbstract({ a_path = (["java"],"Int64") }, [])
+	| TAbstract({ a_path = (["haxe"],"Int64") }, []) ->
 		get (["java";"lang"], "Long")
 	| _ -> t
 

+ 22 - 2
src/codegen/java.ml

@@ -215,6 +215,17 @@ let show_in_completion ctx jc =
 		| ("java" | "javax" | "org") :: _ -> true
 		| _ -> false
 
+(**
+	`haxe.Rest<T>` auto-boxes primitive types.
+	That means we can't use it as varargs for extern methods.
+	E.g externs with `int` varargs are represented as `int[]` at run time
+	while `haxe.Rest<Int>` is actually `java.lang.Integer[]`.
+*)
+let is_eligible_for_haxe_rest_args arg_type =
+	match arg_type with
+	| TByte | TChar | TDouble | TFloat | TInt | TLong | TShort | TBool -> false
+	| _ -> true
+
 let convert_java_enum ctx p pe =
 	let meta = ref (get_canonical ctx p (fst pe.cpath) (snd pe.cpath) :: [Meta.Native, [EConst (String (real_java_path ctx pe.cpath,SDoubleQuotes) ), p], p ]) in
 	let data = ref [] in
@@ -254,6 +265,7 @@ let convert_java_enum ctx p pe =
 		in
 		let jf_constant = ref field.jf_constant in
 		let readonly = ref false in
+		let is_varargs = ref false in
 
 		List.iter (function
 			| JPublic -> cff_access := (APublic,null_pos) :: !cff_access
@@ -274,7 +286,7 @@ let convert_java_enum ctx p pe =
 			(* | JSynchronized -> cff_meta := (Meta.Synchronized, [], p) :: !cff_meta *)
 			| JVolatile -> cff_meta := (Meta.Volatile, [], p) :: !cff_meta
 			| JTransient -> cff_meta := (Meta.Transient, [], p) :: !cff_meta
-			(* | JVarArgs -> cff_meta := (Meta.VarArgs, [], p) :: !cff_meta *)
+			| JVarArgs -> is_varargs := true
 			| JAbstract when not is_interface ->
 				cff_access := (AAbstract, p) :: !cff_access
 			| _ -> ()
@@ -341,9 +353,17 @@ let convert_java_enum ctx p pe =
 					| c :: others -> ctx.jtparams <- (c @ field.jf_types) :: others
 					| [] -> ctx.jtparams <- field.jf_types :: []);
 					let i = ref 0 in
+					let args_count = List.length args in
 					let args = List.map (fun s ->
 						incr i;
-						(local_names !i,null_pos), false, [], Some(convert_signature ctx p s,null_pos), None
+						let hx_sig =
+							match s with
+							| TArray (s1,_) when !is_varargs && !i = args_count && is_eligible_for_haxe_rest_args s1 ->
+								mk_type_path ctx (["haxe"], "Rest") [TPType (convert_signature ctx p s1,null_pos)]
+							| _ ->
+								convert_signature ctx null_pos s
+						in
+						(local_names !i,null_pos), false, [], Some(hx_sig,null_pos), None
 					) args in
 					let t = Option.map_default (convert_signature ctx p) (mk_type_path ctx ([], "Void") []) ret in
 					cff_access := (AOverload,p) :: !cff_access;

+ 21 - 1
src/codegen/javaModern.ml

@@ -758,6 +758,17 @@ module Converter = struct
 			PMap.add s (ct_type_param s) acc
 		) acc params
 
+	(**
+		`haxe.Rest<T>` auto-boxes primitive types.
+		That means we can't use it as varargs for extern methods.
+		E.g externs with `int` varargs are represented as `int[]` at run time
+		while `haxe.Rest<Int>` is actually `java.lang.Integer[]`.
+	*)
+	let is_eligible_for_haxe_rest_args arg_type =
+		match arg_type with
+		| TByte | TChar | TDouble | TFloat | TInt | TLong | TShort | TBool -> false
+		| _ -> true
+
 	let convert_field ctx is_method (jc : jclass) (is_interface : bool) (jf : jfield) p =
 		let ctx = {
 			type_params = type_param_lut ctx.type_params jf.jf_types;
@@ -845,9 +856,18 @@ module Converter = struct
 				begin match jf.jf_descriptor with
 				| TMethod(args,ret) ->
 					let local_names = extract_local_names() in
+					let args_count = List.length args
+					and is_varargs = AccessFlags.has_flag jf.jf_flags MVarargs in
 					let convert_arg i jsig =
 						let name = local_names (i + 1) in
-						((name,p),false,[],Some (convert_signature ctx p jsig,p),None)
+						let hx_sig =
+							match jsig with
+							| TArray (jsig1,_) when is_varargs && i + 1 = args_count && is_eligible_for_haxe_rest_args jsig1 ->
+								mk_type_path (["haxe"], "Rest") [TPType (convert_signature ctx p jsig1,p)]
+							| _ ->
+								convert_signature ctx p jsig
+						in
+						((name,p),false,[],Some (hx_sig,p),None)
 					in
 					let f = {
 						f_params = List.map (fun tp -> convert_type_parameter ctx tp p) jf.jf_types;

+ 2 - 2
std/java/_std/Type.hx

@@ -179,8 +179,8 @@ enum ValueType {
 	}
 
 	// cache empty constructor arguments so we don't allocate it on each createEmptyInstance call
-	@:protected @:readOnly static var __createEmptyInstance_EMPTY_TYPES = java.NativeArray.make(java.Lib.toNativeEnum(java.internal.Runtime.EmptyObject));
-	@:protected @:readOnly static var __createEmptyInstance_EMPTY_ARGS = java.NativeArray.make(java.internal.Runtime.EmptyObject.EMPTY);
+	@:protected @:readOnly static var __createEmptyInstance_EMPTY_TYPES = java.Lib.toNativeEnum(java.internal.Runtime.EmptyObject);
+	@:protected @:readOnly static var __createEmptyInstance_EMPTY_ARGS = java.internal.Runtime.EmptyObject.EMPTY;
 
 	public static function createEmptyInstance<T>(cl:Class<T>):T {
 		var t = java.Lib.toNativeType(cl);

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

@@ -6,6 +6,7 @@ import java.NativeArray;
 import java.lang.System;
 import java.lang.Object;
 import java.util.Arrays;
+import java.StdTypes;
 
 private typedef NativeRest<T> = NativeArray<T>;
 
@@ -29,6 +30,41 @@ abstract Rest<T>(NativeRest<T>) {
 	}
 	#end
 
+	@:generic
+	static function ofNativePrimitive<TBoxed,TRest>(result:NativeRest<TBoxed>, collection:NativeArray<TRest>):Rest<TRest> {
+		for(i in 0...collection.length)
+			result[i] = cast collection[i];
+		return new Rest(cast result);
+	}
+
+	@:from static function ofNativeInt(collection:NativeArray<Int>):Rest<Int>
+		return ofNativePrimitive(new NativeRest<java.lang.Integer>(collection.length), collection);
+
+	@:from static function ofNativeFloat(collection:NativeArray<Float>):Rest<Float>
+		return ofNativePrimitive(new NativeRest<java.lang.Double>(collection.length), collection);
+
+	@:from static function ofNativeBool(collection:NativeArray<Bool>):Rest<Bool>
+		return ofNativePrimitive(new NativeRest<java.lang.Boolean>(collection.length), collection);
+
+	@:from static function ofNativeInt8(collection:NativeArray<Int8>):Rest<Int8>
+		return ofNativePrimitive(new NativeRest<java.lang.Byte>(collection.length), collection);
+
+	@:from static function ofNativeInt16(collection:NativeArray<Int16>):Rest<Int16>
+		return ofNativePrimitive(new NativeRest<java.lang.Short>(collection.length), collection);
+
+	@:from static function ofNativeChar16(collection:NativeArray<Char16>):Rest<Char16>
+		return ofNativePrimitive(new NativeRest<java.lang.Character>(collection.length), collection);
+
+	@:from static function ofNativeHaxeInt64(collection:NativeArray<haxe.Int64>):Rest<haxe.Int64>
+		return ofNativePrimitive(new NativeRest<java.lang.Long>(collection.length), collection);
+
+	@:from static function ofNativeInt64(collection:NativeArray<Int64>):Rest<Int64>
+		return ofNativePrimitive(new NativeRest<java.lang.Long>(collection.length), collection);
+
+	@:from static function ofNative<T>(collection:NativeArray<T>):Rest<T> {
+		return new Rest(collection);
+	}
+
 	inline function new(a:NativeRest<T>):Void
 		this = a;
 

+ 1 - 1
std/java/_std/sys/io/Process.hx

@@ -75,7 +75,7 @@ class Process {
 			}
 		}
 
-		return new java.lang.ProcessBuilder(pargs);
+		return new java.lang.ProcessBuilder(...pargs);
 	}
 
 	public function new(cmd:String, ?args:Array<String>, ?detached:Bool):Void {

+ 1 - 1
std/jvm/Closure.hx

@@ -47,7 +47,7 @@ class Closure extends ClosureDispatch {
 				args;
 		};
 		try {
-			return method.invoke(context, args);
+			return method.invoke(context, ...args);
 		} catch (e:java.lang.reflect.InvocationTargetException) {
 			throw e.getCause();
 		}

+ 1 - 1
std/jvm/Jvm.hx

@@ -291,7 +291,7 @@ class Jvm {
 
 	static public function readFieldClosure(obj:Dynamic, name:String, parameterTypes:NativeArray<java.lang.Class<Dynamic>>):Dynamic {
 		var cl = (obj : java.lang.Object).getClass();
-		var method = cl.getMethod(name, parameterTypes);
+		var method = cl.getMethod(name, ...parameterTypes);
 		if (method.isBridge()) {
 			/* This is probably not what we want... go through all methods and see if we find one that
 				isn't a bridge. This is pretty awkward, but I can't figure out how to use the Java reflection

+ 6 - 13
std/jvm/_std/Type.hx

@@ -123,17 +123,9 @@ class Type {
 		}
 	}
 
-	static final emptyArg = {
-		var a = new java.NativeArray(1);
-		a[0] = (null : jvm.EmptyConstructor);
-		a;
-	}
+	static final emptyArg = (null : jvm.EmptyConstructor);
 
-	static final emptyClass = {
-		var a = new java.NativeArray(1);
-		a[0] = jvm.EmptyConstructor.native();
-		a;
-	}
+	static final emptyClass = jvm.EmptyConstructor.native();
 
 	public static function createInstance<T>(cl:Class<T>, args:Array<Dynamic>):T {
 		var args = @:privateAccess args.getNative();
@@ -150,7 +142,7 @@ class Type {
 			switch (Jvm.unifyCallArguments(args, params, true)) {
 				case Some(args):
 					ctor.setAccessible(true);
-					return ctor.newInstance(args);
+					return ctor.newInstance(...args);
 				case None:
 			}
 		}
@@ -166,7 +158,7 @@ class Type {
 					case Some(args):
 						var obj = emptyCtor.newInstance(emptyArg);
 						method.setAccessible(true);
-						method.invoke(obj, args);
+						method.invoke(obj, ...args);
 						return obj;
 					case None:
 				}
@@ -178,7 +170,8 @@ class Type {
 	public static function createEmptyInstance<T>(cl:Class<T>):T {
 		var annotation = (cl.native().getAnnotation((cast ClassReflectionInformation : java.lang.Class<ClassReflectionInformation>)));
 		if (annotation != null) {
-			return cl.native().getConstructor(emptyClass).newInstance(emptyArg);
+			return cl.native().getConstructor(emptyClass)
+				.newInstance(emptyArg);
 		} else {
 			return cl.native().newInstance();
 		}

+ 1 - 1
tests/unit/src/unit/TestJava.hx

@@ -89,7 +89,7 @@ class TestJava extends Test
 		eq(a.someEnum(), TB);
 		eq(a.currentRevision(), 1);
 		t(cl.getAnnotation(java.Lib.toNativeType(MyClass_ParameterLessAnnotation)) != null);
-		var m = cl.getMethod("testAnnotations", new java.NativeArray(0));
+		var m = cl.getMethod("testAnnotations");
 		a = m.getAnnotation(java.Lib.toNativeType(MyClass_MyAnnotation));
 		t(a != null);
 		eq(a.author(), "author");

+ 1 - 1
tests/unit/src/unit/issues/Issue9034.hx

@@ -5,7 +5,7 @@ import unit.Test;
 class Issue9034 extends Test{
 	#if java
 	function test() {
-		bar(java.nio.file.Paths.get('build.hxml', new java.NativeArray(0)));
+		bar(java.nio.file.Paths.get('build.hxml'));
 		utest.Assert.pass();
 	}