Browse Source

Generate separate modules for generic methods specializations (#9051)

* generate separate modules for generic methods specializations

* test multiple calls with the same type params

* @:genericPerClass

* PR fixes
Aleksandr Kuzmenko 5 years ago
parent
commit
c408d14f3d

+ 6 - 0
src-json/meta.json

@@ -419,6 +419,12 @@
 		"targets": ["TClassField"],
 		"internal": true
 	},
+	{
+		"name": "GenericClassPerMethod",
+		"metadata": ":genericClassPerMethod",
+		"doc": "Makes compiler generate separate class per generic static method specialization",
+		"targets": ["TClass"]
+	},
 	{
 		"name": "Getter",
 		"metadata": ":getter",

+ 57 - 32
src/typing/calls.ml

@@ -390,7 +390,7 @@ let type_generic_function ctx (e,fa) el ?(using_param=None) with_type p =
 			display_error ctx "Conflicting field was defined here" pcf;
 			raise err
 		in
-		let cf2 = try
+		let c, cf2 = try
 			let cf2 = if stat then
 				let cf2 = PMap.find name c.cl_statics in
 				unify_existing_field cf2.cf_type cf2.cf_pos;
@@ -400,7 +400,7 @@ let type_generic_function ctx (e,fa) el ?(using_param=None) with_type p =
 				unify_existing_field cf2.cf_type cf2.cf_pos;
 				cf2
 			in
-			cf2
+			c, cf2
 			(*
 				java.Lib.array() relies on the ability to shadow @:generic function for certain types
 				see https://github.com/HaxeFoundation/haxe/issues/8393#issuecomment-508685760
@@ -410,43 +410,68 @@ let type_generic_function ctx (e,fa) el ?(using_param=None) with_type p =
 			else
 				error ("Cannot specialize @:generic because the generated function name is already used: " ^ name) p *)
 		with Not_found ->
-			let cf2 = mk_field name (map_monos cf.cf_type) cf.cf_pos cf.cf_name_pos in
+			let finalize_field c cf2 =
+				ignore(follow cf.cf_type);
+				let rec check e = match e.eexpr with
+					| TNew({cl_kind = KTypeParameter _} as c,_,_) when not (TypeloadCheck.is_generic_parameter ctx c) ->
+						display_error ctx "Only generic type parameters can be constructed" e.epos;
+						display_error ctx "While specializing this call" p;
+					| _ ->
+						Type.iter check e
+				in
+				cf2.cf_expr <- (match cf.cf_expr with
+					| None ->
+						display_error ctx "Recursive @:generic function" p; None;
+					| Some e ->
+						let e = Generic.generic_substitute_expr gctx e in
+						check e;
+						Some e
+				);
+				cf2.cf_kind <- cf.cf_kind;
+				if not (has_class_field_flag cf CfPublic) then remove_class_field_flag cf2 CfPublic;
+				let metadata = List.filter (fun (m,_,_) -> match m with
+					| Meta.Generic -> false
+					| _ -> true
+				) cf.cf_meta in
+				cf2.cf_meta <- (Meta.NoCompletion,[],p) :: (Meta.NoUsing,[],p) :: (Meta.GenericInstance,[],p) :: metadata
+			in
+			let mk_cf2 name =
+				mk_field name (map_monos cf.cf_type) cf.cf_pos cf.cf_name_pos
+			in
 			if stat then begin
-				c.cl_statics <- PMap.add name cf2 c.cl_statics;
-				c.cl_ordered_statics <- cf2 :: c.cl_ordered_statics
+				if Meta.has Meta.GenericClassPerMethod c.cl_meta then begin
+					let c = Generic.static_method_container gctx c cf p in
+					try
+						let cf2 = PMap.find cf.cf_name c.cl_statics in
+						unify_existing_field cf2.cf_type cf2.cf_pos;
+						c, cf2
+					with Not_found ->
+						let cf2 = mk_cf2 cf.cf_name in
+						c.cl_statics <- PMap.add cf2.cf_name cf2 c.cl_statics;
+						c.cl_ordered_statics <- cf2 :: c.cl_ordered_statics;
+						finalize_field c cf2;
+						c, cf2
+				end else begin
+					let cf2 = mk_cf2 name in
+					c.cl_statics <- PMap.add cf2.cf_name cf2 c.cl_statics;
+					c.cl_ordered_statics <- cf2 :: c.cl_ordered_statics;
+					finalize_field c cf2;
+					c, cf2
+				end
 			end else begin
+				let cf2 = mk_cf2 name in
 				if List.memq cf c.cl_overrides then c.cl_overrides <- cf2 :: c.cl_overrides;
-				c.cl_fields <- PMap.add name cf2 c.cl_fields;
-				c.cl_ordered_fields <- cf2 :: c.cl_ordered_fields
-			end;
-			ignore(follow cf.cf_type);
-			let rec check e = match e.eexpr with
-				| TNew({cl_kind = KTypeParameter _} as c,_,_) when not (TypeloadCheck.is_generic_parameter ctx c) ->
-					display_error ctx "Only generic type parameters can be constructed" e.epos;
-					display_error ctx "While specializing this call" p;
-				| _ ->
-					Type.iter check e
-			in
-			cf2.cf_expr <- (match cf.cf_expr with
-				| None ->
-					display_error ctx "Recursive @:generic function" p; None;
-				| Some e ->
-					let e = Generic.generic_substitute_expr gctx e in
-					check e;
-					Some e
-			);
-			cf2.cf_kind <- cf.cf_kind;
-			if not (has_class_field_flag cf CfPublic) then remove_class_field_flag cf2 CfPublic;
-			let metadata = List.filter (fun (m,_,_) -> match m with
-				| Meta.Generic -> false
-				| _ -> true
-			) cf.cf_meta in
-			cf2.cf_meta <- (Meta.NoCompletion,[],p) :: (Meta.NoUsing,[],p) :: (Meta.GenericInstance,[],p) :: metadata;
-			cf2
+				c.cl_fields <- PMap.add cf2.cf_name cf2 c.cl_fields;
+				c.cl_ordered_fields <- cf2 :: c.cl_ordered_fields;
+				finalize_field c cf2;
+				c, cf2
+			end
 		in
 		let e = match c.cl_kind with
 			| KAbstractImpl(a) ->
 				type_type ctx a.a_path p
+			| _ when stat ->
+				Builder.make_typeexpr (TClassDecl c) e.epos
 			| _ -> e
 		in
 		let fa = if stat then FStatic (c,cf2) else FInstance (c,tl,cf2) in

+ 25 - 0
src/typing/generic.ml

@@ -145,6 +145,31 @@ let get_short_name =
 		Printf.sprintf "Hx___short___hx_type_%i" !i
 	)
 
+let static_method_container gctx c cf p =
+	let ctx = gctx.ctx in
+	let pack = fst c.cl_path in
+	let name = (snd c.cl_path) ^ "_" ^ cf.cf_name ^ "_" ^ gctx.name in
+	try
+		let t = Typeload.load_instance ctx ({ tpackage = pack; tname = name; tparams = []; tsub = None },p) true in
+		match t with
+		| TInst(cg,_) -> cg
+		| _ -> error ("Cannot specialize @:generic static method because the generated type name is already used: " ^ name) p
+	with Error(Module_not_found path,_) when path = (pack,name) ->
+		let m = (try Hashtbl.find ctx.g.modules (Hashtbl.find ctx.g.types_module c.cl_path) with Not_found -> assert false) in
+		let mg = {
+			m_id = alloc_mid();
+			m_path = (pack,name);
+			m_types = [];
+			m_extra = module_extra (s_type_path (pack,name)) m.m_extra.m_sign 0. MFake m.m_extra.m_check_policy;
+		} in
+		gctx.mg <- Some mg;
+		let cg = mk_class mg (pack,name) c.cl_pos null_pos in
+		mg.m_types <- [TClassDecl cg];
+		Hashtbl.add ctx.g.modules mg.m_path mg;
+		add_dependency mg m;
+		add_dependency ctx.m.curmod mg;
+		cg
+
 let rec build_generic ctx c p tl =
 	let pack = fst c.cl_path in
 	let recurse = ref false in

+ 4 - 4
tests/unit/src/unit/Test.hx

@@ -60,18 +60,18 @@ class Test implements utest.ITest {
 	}
 
 	function hf(c:Class<Dynamic>, n:String, ?pos:haxe.PosInfos) {
-		t(Lambda.has(Type.getInstanceFields(c), n));
+		t(Lambda.has(Type.getInstanceFields(c), n), pos);
 	}
 
 	function nhf(c:Class<Dynamic>, n:String, ?pos:haxe.PosInfos) {
-		f(Lambda.has(Type.getInstanceFields(c), n));
+		f(Lambda.has(Type.getInstanceFields(c), n), pos);
 	}
 
 	function hsf(c:Class<Dynamic> , n:String, ?pos:haxe.PosInfos) {
-		t(Lambda.has(Type.getClassFields(c), n));
+		t(Lambda.has(Type.getClassFields(c), n), pos);
 	}
 
 	function nhsf(c:Class<Dynamic> , n:String, ?pos:haxe.PosInfos) {
-		f(Lambda.has(Type.getClassFields(c), n));
+		f(Lambda.has(Type.getClassFields(c), n), pos);
 	}
 }

+ 25 - 0
tests/unit/src/unit/issues/Issue9046.hx

@@ -0,0 +1,25 @@
+package unit.issues;
+
+class Issue9046 extends unit.Test {
+	function test() {
+		var a = Utils9046.flatten('hello');
+		aeq(['hello'], a);
+
+		//check multiple calls with the same type params
+		var a = Utils9046.flatten('hello');
+		aeq(['hello'], a);
+
+		//Check it gets a separate module.
+		//This test should not rely on a generated module name,
+		//but I don't know how to check it without the name.
+		t(null != Type.resolveClass('unit.issues.Utils9046_flatten_String'));
+	}
+}
+
+@:genericClassPerMethod
+class Utils9046 {
+	@:pure(false)
+	@:generic public static function flatten<T>(i:T):Array<T> {
+		return [i];
+	}
+}