2
0
Эх сурвалжийг харах

Functional interface support (#11019)

* [jvm] deal with functional interfaces

see #9576

* [typer] handle functional interface assignments

* Added tests for Java functional interfaces.

* fix test

---------

Co-authored-by: EliteMasterEric <[email protected]>
Simon Krajewski 2 жил өмнө
parent
commit
f132e61f36

+ 25 - 24
src/codegen/javaModern.ml

@@ -196,33 +196,34 @@ module JReaderHoldovers = struct
 
 
 	let parse_formal_type_params s = match s.[0] with
 	let parse_formal_type_params s = match s.[0] with
 		| '<' ->
 		| '<' ->
-			let rec read_id i =
-			match s.[i] with
-			| ':' | '>' -> i
-			| _ -> read_id (i + 1)
+			let rec read_id i = match s.[i] with
+				| ':' | '>' -> i
+				| _ -> read_id (i + 1)
 			in
 			in
 			let len = String.length s in
 			let len = String.length s in
 			let rec parse_params idx acc =
 			let rec parse_params idx acc =
-			let idi = read_id (idx + 1) in
-			let id = String.sub s (idx + 1) (idi - idx - 1) in
-			(* next must be a : *)
-			(match s.[idi] with | ':' -> () | _ -> failwith ("Invalid formal type signature character: " ^ Char.escaped s.[idi] ^ " ; from " ^ s));
-			let ext, l = match s.[idi + 1] with
-				| ':' | '>' -> None, idi + 1
-				| _ ->
-				let sgn, l = parse_signature_part (String.sub s (idi + 1) (len - idi - 1)) in
-				Some sgn, l + idi + 1
-			in
-			let rec loop idx acc =
-				match s.[idx] with
-				| ':' ->
-				let ifacesig, ifacei = parse_signature_part (String.sub s (idx + 1) (len - idx - 1)) in
-				loop (idx + ifacei + 1) (ifacesig :: acc)
-				| _ -> acc, idx
-			in
-			let ifaces, idx = loop l [] in
-			let acc = (id, ext, ifaces) :: acc in
-			if s.[idx] = '>' then List.rev acc, idx + 1 else parse_params (idx - 1) acc
+				let idi = read_id (idx + 1) in
+				let id = String.sub s (idx + 1) (idi - idx - 1) in
+				(* next must be a : *)
+				(match s.[idi] with | ':' -> () | _ -> failwith ("Invalid formal type signature character: " ^ Char.escaped s.[idi] ^ " ; from " ^ s));
+				let ext, l = match s.[idi + 1] with
+					| ':' | '>' ->
+						None, idi + 1
+					| _ ->
+						let sgn, l = parse_signature_part (String.sub s (idi + 1) (len - idi - 1)) in
+						Some sgn, l + idi + 1
+				in
+				let rec loop idx acc =
+					match s.[idx] with
+					| ':' ->
+						let ifacesig, ifacei = parse_signature_part (String.sub s (idx + 1) (len - idx - 1)) in
+						loop (idx + ifacei + 1) (ifacesig :: acc)
+					| _ ->
+						acc, idx
+				in
+				let ifaces, idx = loop l [] in
+				let acc = (id, ext, ifaces) :: acc in
+				if s.[idx] = '>' then List.rev acc, idx + 1 else parse_params (idx - 1) acc
 			in
 			in
 			parse_params 0 []
 			parse_params 0 []
 		| _ -> [], 0
 		| _ -> [], 0

+ 4 - 0
src/context/abstractCast.ml

@@ -87,6 +87,10 @@ and do_check_cast ctx uctx tleft eright p =
 						in
 						in
 						loop2 a.a_to
 						loop2 a.a_to
 					end
 					end
+				| TInst(c,tl), TFun _ when has_class_flag c CFunctionalInterface ->
+					let cf = ctx.g.functional_interface_lut#find c.cl_path in
+					unify_raise_custom uctx eright.etype (apply_params c.cl_params tl cf.cf_type) p;
+					eright
 				| _ ->
 				| _ ->
 					raise Not_found
 					raise Not_found
 			end
 			end

+ 1 - 0
src/context/typecore.ml

@@ -82,6 +82,7 @@ type typer_globals = {
 	mutable complete : bool;
 	mutable complete : bool;
 	mutable type_hints : (module_def_display * pos * t) list;
 	mutable type_hints : (module_def_display * pos * t) list;
 	mutable load_only_cached_modules : bool;
 	mutable load_only_cached_modules : bool;
+	functional_interface_lut : (path,tclass_field) lookup;
 	(* api *)
 	(* api *)
 	do_inherit : typer -> Type.tclass -> pos -> (bool * placed_type_path) -> bool;
 	do_inherit : typer -> Type.tclass -> pos -> (bool * placed_type_path) -> bool;
 	do_create : Common.context -> typer;
 	do_create : Common.context -> typer;

+ 1 - 0
src/core/tType.ml

@@ -416,6 +416,7 @@ type flag_tclass =
 	| CFinal
 	| CFinal
 	| CInterface
 	| CInterface
 	| CAbstract
 	| CAbstract
+	| CFunctionalInterface
 
 
 type flag_tclass_field =
 type flag_tclass_field =
 	| CfPublic
 	| CfPublic

+ 32 - 4
src/generators/genjvm.ml

@@ -201,7 +201,7 @@ let rec jsignature_of_type gctx stack t =
 		TObject((["haxe";"root"],"Array"),[TType(WNone,t)])
 		TObject((["haxe";"root"],"Array"),[TType(WNone,t)])
 	| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
 	| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
 		TArray(jsignature_of_type t,None)
 		TArray(jsignature_of_type t,None)
-	| TInst({cl_kind = KTypeParameter [t]},_) -> jsignature_of_type t
+	| TInst({cl_kind = KTypeParameter [t]},_) when t != t_dynamic -> jsignature_of_type t
 	| TInst({cl_kind = KTypeParameter _; cl_path = (_,name)},_) -> TTypeParameter name
 	| TInst({cl_kind = KTypeParameter _; cl_path = (_,name)},_) -> TTypeParameter name
 	| TInst({cl_path = ["_Class"],"Class_Impl_"},_) -> java_class_sig
 	| TInst({cl_path = ["_Class"],"Class_Impl_"},_) -> java_class_sig
 	| TInst({cl_path = ["_Enum"],"Enum_Impl_"},_) -> java_class_sig
 	| TInst({cl_path = ["_Enum"],"Enum_Impl_"},_) -> java_class_sig
@@ -422,7 +422,7 @@ let create_field_closure gctx jc path_this jm name jsig =
 		| _ ->
 		| _ ->
 			die "" __LOC__
 			die "" __LOC__
 	in
 	in
-	let jm_invoke = wf#generate_invoke args ret in
+	let jm_invoke = wf#generate_invoke args ret [] in
 	let vars = List.map (fun (name,jsig) ->
 	let vars = List.map (fun (name,jsig) ->
 		jm_invoke#add_local name jsig VarArgument
 		jm_invoke#add_local name jsig VarArgument
 	) args in
 	) args in
@@ -571,6 +571,10 @@ class texpr_to_jvm
 		let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncLocal name) jc jm context in
 		let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncLocal name) jc jm context in
 		let jc_closure = wf#get_class in
 		let jc_closure = wf#get_class in
 		ignore(wf#generate_constructor (env <> []));
 		ignore(wf#generate_constructor (env <> []));
+		let filter = match ret with
+			| RValue (Some (TObject(path,_)),_) -> [path]
+			| _ -> []
+		in
 		let args,ret =
 		let args,ret =
 			let args = List.map (fun (v,eo) ->
 			let args = List.map (fun (v,eo) ->
 				(* TODO: Can we do this differently? *)
 				(* TODO: Can we do this differently? *)
@@ -579,7 +583,7 @@ class texpr_to_jvm
 			) tf.tf_args in
 			) tf.tf_args in
 			args,(return_of_type gctx tf.tf_type)
 			args,(return_of_type gctx tf.tf_type)
 		in
 		in
-		let jm_invoke = wf#generate_invoke args ret in
+		let jm_invoke = wf#generate_invoke args ret filter in
 		let handler = new texpr_to_jvm gctx field_info jc_closure jm_invoke ret in
 		let handler = new texpr_to_jvm gctx field_info jc_closure jm_invoke ret in
 		handler#set_env env;
 		handler#set_env env;
 		let args = List.map (fun (v,eo) ->
 		let args = List.map (fun (v,eo) ->
@@ -656,7 +660,7 @@ class texpr_to_jvm
 			let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncStatic(path,name)) jc jm [] in
 			let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncStatic(path,name)) jc jm [] in
 			let jc_closure = wf#get_class in
 			let jc_closure = wf#get_class in
 			ignore(wf#generate_constructor false);
 			ignore(wf#generate_constructor false);
-			let jm_invoke = wf#generate_invoke args ret in
+			let jm_invoke = wf#generate_invoke args ret [] in
 			let vars = List.map (fun (name,jsig) ->
 			let vars = List.map (fun (name,jsig) ->
 				jm_invoke#add_local name jsig VarArgument
 				jm_invoke#add_local name jsig VarArgument
 			) args in
 			) args in
@@ -2918,6 +2922,29 @@ module Preprocessor = struct
 		end else if fst mt.mt_path = [] then
 		end else if fst mt.mt_path = [] then
 			mt.mt_path <- make_root mt.mt_path
 			mt.mt_path <- make_root mt.mt_path
 
 
+	let check_single_method_interface gctx c =
+		let rec loop m l = match l with
+			| [] ->
+				m
+			| cf :: l ->
+				if not (has_class_field_flag cf CfDefault) then begin match m with
+					| None ->
+						loop (Some cf) l
+					| Some _ ->
+						None
+				end else
+					loop m l
+		in
+		match loop None c.cl_ordered_fields with
+		| None ->
+			()
+		| Some cf ->
+			match jsignature_of_type gctx cf.cf_type with
+			| TMethod(args,ret) ->
+				JvmFunctions.JavaFunctionalInterfaces.add args ret c.cl_path cf.cf_name (List.map extract_param_name (c.cl_params @ cf.cf_params));
+			| _ ->
+				()
+
 	let preprocess gctx =
 	let preprocess gctx =
 		let rec has_runtime_meta = function
 		let rec has_runtime_meta = function
 			| (Meta.Custom s,_,_) :: _ when String.length s > 0 && s.[0] <> ':' ->
 			| (Meta.Custom s,_,_) :: _ when String.length s > 0 && s.[0] <> ':' ->
@@ -2947,6 +2974,7 @@ module Preprocessor = struct
 			match mt with
 			match mt with
 			| TClassDecl c ->
 			| TClassDecl c ->
 				if not (has_class_flag c CInterface) then gctx.preprocessor#preprocess_class c
 				if not (has_class_flag c CInterface) then gctx.preprocessor#preprocess_class c
+				else check_single_method_interface gctx c;
 			| _ -> ()
 			| _ -> ()
 		) gctx.com.types;
 		) gctx.com.types;
 		(* find typedef-interface implementations *)
 		(* find typedef-interface implementations *)

+ 35 - 47
src/generators/jvm/jvmFunctions.ml

@@ -294,39 +294,17 @@ module JavaFunctionalInterfaces = struct
 		jparams : string list;
 		jparams : string list;
 	}
 	}
 
 
-	let java_functional_interfaces =
-		let juf = ["java";"util";"function"] in
-		let tp name = TTypeParameter name in
-		[
-			{
-				jargs = [];
-				jret = None;
-				jpath = ["java";"lang"],"Runnable";
-				jname = "run";
-				jparams = []
-			};
-			{
-				jargs = [tp "T"];
-				jret = None;
-				jpath = juf,"Consumer";
-				jname = "accept";
-				jparams = ["T"]
-			};
-			{
-				jargs = [tp "T";tp "U"];
-				jret = None;
-				jpath = juf,"BiConsumer";
-				jname = "accept";
-				jparams = ["T";"U"]
-			};
-			{
-				jargs = [tp "T"];
-				jret = Some (tp "R");
-				jpath = juf,"Function";
-				jname = "apply";
-				jparams = ["T";"R"]
-			};
-		]
+	let java_functional_interfaces = DynArray.create ()
+
+	let add args ret path name params =
+		let jfi = {
+			jargs = args;
+			jret = ret;
+			jpath = path;
+			jname = name;
+			jparams = params;
+		} in
+		DynArray.add java_functional_interfaces jfi
 
 
 	let unify jfi args ret =
 	let unify jfi args ret =
 		let rec loop params want have = match want,have with
 		let rec loop params want have = match want,have with
@@ -357,15 +335,22 @@ module JavaFunctionalInterfaces = struct
 			None
 			None
 
 
 
 
-	let find_compatible args ret =
-		ExtList.List.filter_map (fun jfi ->
-			if jfi.jparams = [] then begin
-				if jfi.jargs = args && jfi.jret = ret then
-					Some (jfi,[])
-				else None
+	let find_compatible args ret filter =
+		DynArray.fold_left (fun acc jfi ->
+			if filter = [] || List.mem jfi.jpath filter then begin
+				if jfi.jparams = [] then begin
+					if jfi.jargs = args && jfi.jret = ret then
+						(jfi,[]) :: acc
+					else
+						acc
+				end else match unify jfi args ret with
+					| Some x ->
+						x :: acc
+					| None ->
+						acc
 			end else
 			end else
-				unify jfi args ret
-		) java_functional_interfaces
+				acc
+		) [] java_functional_interfaces
 end
 end
 
 
 open JavaFunctionalInterfaces
 open JavaFunctionalInterfaces
@@ -411,7 +396,7 @@ class typed_function
 		jm_ctor#return;
 		jm_ctor#return;
 		jm_ctor
 		jm_ctor
 
 
-	method generate_invoke (args : (string * jsignature) list) (ret : jsignature option)=
+	method generate_invoke (args : (string * jsignature) list) (ret : jsignature option) (functional_interface_filter : jpath list) =
 		let arg_sigs = List.map snd args in
 		let arg_sigs = List.map snd args in
 		let meth = functions#register_signature arg_sigs ret in
 		let meth = functions#register_signature arg_sigs ret in
 		let jsig_invoke = method_sig arg_sigs ret in
 		let jsig_invoke = method_sig arg_sigs ret in
@@ -424,14 +409,17 @@ class typed_function
 			end
 			end
 		in
 		in
 		let spawn_forward_function meth_from meth_to is_bridge =
 		let spawn_forward_function meth_from meth_to is_bridge =
-			let flags = [MPublic] in
-			let flags = if is_bridge then MBridge :: MSynthetic :: flags else flags in
-			let jm_invoke_next = jc_closure#spawn_method meth_from.name (method_sig meth_from.dargs meth_from.dret) flags in
-			functions#make_forward_method jc_closure jm_invoke_next meth_from meth_to;
+			let msig = method_sig meth_from.dargs meth_from.dret in
+			if not (jc_closure#has_method meth_from.name msig) then begin
+				let flags = [MPublic] in
+				let flags = if is_bridge then MBridge :: MSynthetic :: flags else flags in
+				let jm_invoke_next = jc_closure#spawn_method meth_from.name msig flags in
+				functions#make_forward_method jc_closure jm_invoke_next meth_from meth_to;
+			end
 		in
 		in
 		let check_functional_interfaces meth =
 		let check_functional_interfaces meth =
 			try
 			try
-				let l = JavaFunctionalInterfaces.find_compatible meth.dargs meth.dret in
+				let l = JavaFunctionalInterfaces.find_compatible meth.dargs meth.dret functional_interface_filter in
 				List.iter (fun (jfi,params) ->
 				List.iter (fun (jfi,params) ->
 					add_interface jfi.jpath params;
 					add_interface jfi.jpath params;
 					spawn_forward_function {meth with name=jfi.jname} meth false;
 					spawn_forward_function {meth with name=jfi.jname} meth false;

+ 34 - 8
src/typing/typeloadFields.ml

@@ -1702,6 +1702,30 @@ let finalize_class ctx cctx =
 		| Some r -> delay ctx PTypeField (fun() -> ignore(lazy_type r)))
 		| Some r -> delay ctx PTypeField (fun() -> ignore(lazy_type r)))
 	) cctx.delayed_expr
 	) cctx.delayed_expr
 
 
+let check_functional_interface ctx c =
+	let is_normal_field cf =
+		(* TODO: more? *)
+		not (has_class_field_flag cf CfDefault)
+	in
+	let rec loop o l = match l with
+		| cf :: l ->
+			if is_normal_field cf then begin
+				if o = None then
+					loop (Some cf) l
+				else
+					None
+			end else
+				loop o l
+		| [] ->
+			o
+	in
+	match loop None c.cl_ordered_fields with
+	| None ->
+		()
+	| Some cf ->
+		add_class_flag c CFunctionalInterface;
+		ctx.g.functional_interface_lut#add c.cl_path cf
+
 let init_class ctx c p context_init herits fields =
 let init_class ctx c p context_init herits fields =
 	let cctx = create_class_context c context_init p in
 	let cctx = create_class_context c context_init p in
 	let ctx = create_typer_context_for_class ctx cctx p in
 	let ctx = create_typer_context_for_class ctx cctx p in
@@ -1828,14 +1852,16 @@ let init_class ctx c p context_init herits fields =
 		with Error (Custom str,p2,depth) when p = p2 ->
 		with Error (Custom str,p2,depth) when p = p2 ->
 			display_error ~depth ctx.com str p
 			display_error ~depth ctx.com str p
 	) fields;
 	) fields;
-	(match cctx.abstract with
-	| Some a ->
-		a.a_to_field <- List.rev a.a_to_field;
-		a.a_from_field <- List.rev a.a_from_field;
-		a.a_ops <- List.rev a.a_ops;
-		a.a_unops <- List.rev a.a_unops;
-		a.a_array <- List.rev a.a_array;
-	| None -> ());
+		begin match cctx.abstract with
+		| Some a ->
+			a.a_to_field <- List.rev a.a_to_field;
+			a.a_from_field <- List.rev a.a_from_field;
+			a.a_ops <- List.rev a.a_ops;
+			a.a_unops <- List.rev a.a_unops;
+			a.a_array <- List.rev a.a_array;
+		| None ->
+			if (has_class_flag c CInterface) && ctx.com.platform = Java then check_functional_interface ctx c;
+	end;
 	c.cl_ordered_statics <- List.rev c.cl_ordered_statics;
 	c.cl_ordered_statics <- List.rev c.cl_ordered_statics;
 	c.cl_ordered_fields <- List.rev c.cl_ordered_fields;
 	c.cl_ordered_fields <- List.rev c.cl_ordered_fields;
 	(* if ctx.is_display_file && not cctx.has_display_field && Display.is_display_position c.cl_pos && ctx.com.display.dms_kind = DMToplevel then begin
 	(* if ctx.is_display_file && not cctx.has_display_field && Display.is_display_position c.cl_pos && ctx.com.display.dms_kind = DMToplevel then begin

+ 1 - 0
src/typing/typer.ml

@@ -2061,6 +2061,7 @@ let rec create com =
 			complete = false;
 			complete = false;
 			type_hints = [];
 			type_hints = [];
 			load_only_cached_modules = false;
 			load_only_cached_modules = false;
+			functional_interface_lut = new pmap_lookup;
 			do_inherit = MagicTypes.on_inherit;
 			do_inherit = MagicTypes.on_inherit;
 			do_create = create;
 			do_create = create;
 			do_macro = MacroContext.type_macro;
 			do_macro = MacroContext.type_macro;

+ 34 - 0
tests/misc/java/projects/Issue11014/Main.hx

@@ -0,0 +1,34 @@
+interface MathOperation {
+	function perform(a:Int, b:Int):Int;
+}
+
+class Ops {
+	static public final add:MathOperation = (a, b) -> a + b;
+	static public final subtract:MathOperation = (a, b) -> a - b;
+
+	static public function performMathOperation(operation:MathOperation) {
+		return operation.perform(8, 4);
+	}
+}
+
+class Main {
+	static function main() {
+		var result = Ops.performMathOperation(Ops.add);
+		trace('Add: ${result}');
+
+		result = Ops.performMathOperation(Ops.subtract);
+		trace('Subtract: ${result}');
+
+		result = Ops.performMathOperation(multiply);
+		trace('Multiply: ${result}');
+
+		result = Ops.performMathOperation(function(a, b):Int {
+			return Std.int(a / b);
+		});
+		trace('Divide: ${result}');
+	}
+
+	static function multiply(a, b):Int {
+		return a * b;
+	}
+}

+ 3 - 0
tests/misc/java/projects/Issue11014/compile.hxml

@@ -0,0 +1,3 @@
+--main Main
+--jvm bin/run.jar
+--cmd java -jar bin/run.jar

+ 4 - 0
tests/misc/java/projects/Issue11014/compile.hxml.stdout

@@ -0,0 +1,4 @@
+Main.hx:17: Add: 12
+Main.hx:20: Subtract: 4
+Main.hx:23: Multiply: 32
+Main.hx:28: Divide: 2