Skip to content

Commit

Permalink
Functional interface support (#11019)
Browse files Browse the repository at this point in the history
* [jvm] deal with functional interfaces

see #9576

* [typer] handle functional interface assignments

* Added tests for Java functional interfaces.

* fix test

---------

Co-authored-by: EliteMasterEric <ericmyllyoja@gmail.com>
  • Loading branch information
Simn and EliteMasterEric authored Mar 24, 2023
1 parent 9717436 commit f132e61
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 83 deletions.
49 changes: 25 additions & 24 deletions src/codegen/javaModern.ml
Original file line number Diff line number Diff line change
Expand Up @@ -196,33 +196,34 @@ module JReaderHoldovers = struct

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
let len = String.length s in
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
parse_params 0 []
| _ -> [], 0
Expand Down
4 changes: 4 additions & 0 deletions src/context/abstractCast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ and do_check_cast ctx uctx tleft eright p =
in
loop2 a.a_to
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
end
Expand Down
1 change: 1 addition & 0 deletions src/context/typecore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type typer_globals = {
mutable complete : bool;
mutable type_hints : (module_def_display * pos * t) list;
mutable load_only_cached_modules : bool;
functional_interface_lut : (path,tclass_field) lookup;
(* api *)
do_inherit : typer -> Type.tclass -> pos -> (bool * placed_type_path) -> bool;
do_create : Common.context -> typer;
Expand Down
1 change: 1 addition & 0 deletions src/core/tType.ml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ type flag_tclass =
| CFinal
| CInterface
| CAbstract
| CFunctionalInterface

type flag_tclass_field =
| CfPublic
Expand Down
36 changes: 32 additions & 4 deletions src/generators/genjvm.ml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ let rec jsignature_of_type gctx stack t =
TObject((["haxe";"root"],"Array"),[TType(WNone,t)])
| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
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_path = ["_Class"],"Class_Impl_"},_) -> java_class_sig
| TInst({cl_path = ["_Enum"],"Enum_Impl_"},_) -> java_class_sig
Expand Down Expand Up @@ -422,7 +422,7 @@ let create_field_closure gctx jc path_this jm name jsig =
| _ ->
die "" __LOC__
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) ->
jm_invoke#add_local name jsig VarArgument
) args in
Expand Down Expand Up @@ -571,6 +571,10 @@ class texpr_to_jvm
let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncLocal name) jc jm context in
let jc_closure = wf#get_class in
ignore(wf#generate_constructor (env <> []));
let filter = match ret with
| RValue (Some (TObject(path,_)),_) -> [path]
| _ -> []
in
let args,ret =
let args = List.map (fun (v,eo) ->
(* TODO: Can we do this differently? *)
Expand All @@ -579,7 +583,7 @@ class texpr_to_jvm
) tf.tf_args in
args,(return_of_type gctx tf.tf_type)
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
handler#set_env env;
let args = List.map (fun (v,eo) ->
Expand Down Expand Up @@ -656,7 +660,7 @@ class texpr_to_jvm
let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncStatic(path,name)) jc jm [] in
let jc_closure = wf#get_class in
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) ->
jm_invoke#add_local name jsig VarArgument
) args in
Expand Down Expand Up @@ -2918,6 +2922,29 @@ module Preprocessor = struct
end else if fst mt.mt_path = [] then
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 rec has_runtime_meta = function
| (Meta.Custom s,_,_) :: _ when String.length s > 0 && s.[0] <> ':' ->
Expand Down Expand Up @@ -2947,6 +2974,7 @@ module Preprocessor = struct
match mt with
| TClassDecl c ->
if not (has_class_flag c CInterface) then gctx.preprocessor#preprocess_class c
else check_single_method_interface gctx c;
| _ -> ()
) gctx.com.types;
(* find typedef-interface implementations *)
Expand Down
82 changes: 35 additions & 47 deletions src/generators/jvm/jvmFunctions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -294,39 +294,17 @@ module JavaFunctionalInterfaces = struct
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 rec loop params want have = match want,have with
Expand Down Expand Up @@ -357,15 +335,22 @@ module JavaFunctionalInterfaces = struct
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
unify jfi args ret
) java_functional_interfaces
acc
) [] java_functional_interfaces
end

open JavaFunctionalInterfaces
Expand Down Expand Up @@ -411,7 +396,7 @@ class typed_function
jm_ctor#return;
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 meth = functions#register_signature arg_sigs ret in
let jsig_invoke = method_sig arg_sigs ret in
Expand All @@ -424,14 +409,17 @@ class typed_function
end
in
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
let check_functional_interfaces meth =
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) ->
add_interface jfi.jpath params;
spawn_forward_function {meth with name=jfi.jname} meth false;
Expand Down
42 changes: 34 additions & 8 deletions src/typing/typeloadFields.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,30 @@ let finalize_class ctx cctx =
| Some r -> delay ctx PTypeField (fun() -> ignore(lazy_type r)))
) 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 cctx = create_class_context c context_init p in
let ctx = create_typer_context_for_class ctx cctx p in
Expand Down Expand Up @@ -1828,14 +1852,16 @@ let init_class ctx c p context_init herits fields =
with Error (Custom str,p2,depth) when p = p2 ->
display_error ~depth ctx.com str p
) 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_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
Expand Down
1 change: 1 addition & 0 deletions src/typing/typer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,7 @@ let rec create com =
complete = false;
type_hints = [];
load_only_cached_modules = false;
functional_interface_lut = new pmap_lookup;
do_inherit = MagicTypes.on_inherit;
do_create = create;
do_macro = MacroContext.type_macro;
Expand Down
34 changes: 34 additions & 0 deletions tests/misc/java/projects/Issue11014/Main.hx
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions tests/misc/java/projects/Issue11014/compile.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--main Main
--jvm bin/run.jar
--cmd java -jar bin/run.jar
4 changes: 4 additions & 0 deletions tests/misc/java/projects/Issue11014/compile.hxml.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Main.hx:17: Add: 12
Main.hx:20: Subtract: 4
Main.hx:23: Multiply: 32
Main.hx:28: Divide: 2

0 comments on commit f132e61

Please sign in to comment.