Преглед изворни кода

Add support for native cs type parameter constraints (#8311)

* Add support for native cs type parameter constraints

* Unify native C# Constraints names

* Cs new() constraint: only for Constructible<Void->Void>

* Cs constraints: class and struct are incompatible too

* Cs constraints: unmanaged is only available with cs >= 7.3

* Cs native constraints: add tests

* Add cs_ver to define.ml

* Fix copy/paste fail and add corresponding test

* Use @:coreType in cs.Constraints
Rudy Ges пре 6 година
родитељ
комит
3e90e133a1

+ 6 - 0
src-json/define.json

@@ -42,6 +42,12 @@
 		"define": "cppia",
 		"doc": "Generate cpp instruction assembly"
 	},
+	{
+		"name": "CsVer",
+		"define": "cs_ver",
+		"doc": "The C# version to target",
+		"platforms": ["cs"]
+	},
 	{
 		"name": "NoCppiaAst",
 		"define": "nocppiaast",

+ 61 - 4
src/generators/gencs.ml

@@ -30,6 +30,20 @@ open Printf
 open Option
 open ExtString
 
+type cs_native_constraint =
+	| CsStruct
+	| CsClass
+	| CsUnmanaged
+	| CsConstructible
+	| CsConstraint of string
+
+let get_constraint = function
+	| CsStruct -> "struct"
+	| CsClass -> "class"
+	| CsUnmanaged -> "unmanaged"
+	| CsConstructible -> "new()"
+	| CsConstraint s -> s
+
 let rec is_cs_basic_type t =
 	match follow t with
 		| TInst( { cl_path = (["haxe"], "Int32") }, [] )
@@ -1831,6 +1845,9 @@ let generate con =
 			match cl_params with
 				| (_ :: _) when not (erase_generics && is_hxgeneric (TClassDecl cl)) ->
 					let get_param_name t = match follow t with TInst(cl, _) -> snd cl.cl_path | _ -> assert false in
+					let combination_error c1 c2 =
+						gen.gcon.error ("The " ^ (get_constraint c1) ^ " constraint cannot be combined with the " ^ (get_constraint c2) ^ " constraint.") cl.cl_pos in
+
 					let params = sprintf "<%s>" (String.concat ", " (List.map (fun (_, tcl) -> get_param_name tcl) cl_params)) in
 					let params_extends =
 						if hxgen || not (Meta.has (Meta.NativeGen) cl.cl_meta) then
@@ -1849,21 +1866,61 @@ let generate con =
 
 												(* non-sealed class *)
 												| TInst ({ cl_interface = false; cl_final = false},_) ->
-													base_class_constraints := (t_s t) :: !base_class_constraints;
+													base_class_constraints := (CsConstraint (t_s t)) :: !base_class_constraints;
 													acc;
 
 												(* interface *)
 												| TInst ({ cl_interface = true}, _) ->
-													(t_s t) :: acc
+													(CsConstraint (t_s t)) :: acc
+
+												(* cs constraints *)
+												(* See https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters *)
+												| TAbstract({ a_path = (_, c); a_module = { m_path = ([pack],"Constraints") } }, params) ->
+													(match pack, c with
+														| "haxe", "Constructible" ->
+															(match params with
+																(* Only for parameterless constructors *)
+																| [TFun ([],TAbstract({a_path=[],"Void"},_))] ->
+																	if (List.memq CsStruct acc) then combination_error CsConstructible CsStruct;
+																	if (List.memq CsUnmanaged acc) then combination_error CsUnmanaged CsConstructible;
+																	CsConstructible :: acc;
+																| _ -> acc;
+															)
+														| "cs", "CsStruct" ->
+															if (List.memq CsClass acc) then combination_error CsClass CsStruct;
+															if (List.memq CsConstructible acc) then combination_error CsConstructible CsStruct;
+															if (List.memq CsUnmanaged acc) then combination_error CsUnmanaged CsStruct;
+															CsStruct :: acc;
+														| "cs", "CsUnmanaged" ->
+															if (List.memq CsStruct acc) then combination_error CsUnmanaged CsStruct;
+															if (List.memq CsConstructible acc) then combination_error CsUnmanaged CsConstructible;
+															CsUnmanaged :: acc;
+														| "cs", "CsClass" ->
+															if (List.memq CsStruct acc) then combination_error CsClass CsStruct;
+															CsClass :: acc;
+														| _, _ -> acc;
+													)
 
 												(* skip anything other *)
 												| _ ->
 													acc
 										) [] constraints in
 
-										let s_constraints = (!base_class_constraints @ other_constraints) in
+										let s_constraints = (List.sort
+											(* C# expects some ordering for built-in constraints: *)
+											(fun c1 c2 -> match c1, c2 with
+												| a, b when a == b -> 0
+												(* - "new()" type constraint should be last *)
+												| CsConstructible, _ -> 1
+												| _, CsConstructible -> -1
+												(* - "class", "struct" and "unmanaged" should be first *)
+												| CsClass, _ | CsStruct, _ | CsUnmanaged, _ -> -1
+												| _, CsClass | _, CsStruct | _, CsUnmanaged -> 1
+												| _, _ -> 0
+										) (!base_class_constraints @ other_constraints)) in
+
 										if s_constraints <> [] then
-											(sprintf " where %s : %s" (get_param_name t) (String.concat ", " s_constraints) :: acc)
+											(sprintf " where %s : %s" (get_param_name t) (String.concat ", " (List.map get_constraint s_constraints)) :: acc)
 										else
 											acc;
 									| _ -> acc

+ 55 - 0
std/cs/Constraints.hx

@@ -0,0 +1,55 @@
+/*
+ * 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 cs;
+
+/**
+	The type argument must be a value type. Any value type except Nullable_1<T>
+	can be specified.
+
+	It is intended to be used as a native cs type parameter constraint, when
+	using `@:nativeGen`. This constraint won't have any effect on Haxe code.
+	If used as a real type, the underlying type will be `Dynamic`.
+**/
+@:coreType abstract CsStruct from Dynamic {}
+
+/**
+	The type argument must be a reference type. This constraint applies also to
+	any class, interface, delegate, or array type.
+
+	It is intended to be used as a native cs type parameter constraint, when
+	using `@:nativeGen`. This constraint won't have any effect on Haxe code.
+	If used as a real type, the underlying type will be `Dynamic`.
+**/
+@:coreType abstract CsClass from Dynamic {}
+
+#if (cs_ver >= "7.3")
+/**
+	The type argument must not be a reference type and must not contain any
+	reference type members at any level of nesting.
+
+	It is intended to be used as a native cs type parameter constraint, when
+	using `@:nativeGen`. This constraint won't have any effect on Haxe code.
+	If used as a real type, the underlying type will be `Dynamic`.
+**/
+@:coreType abstract CsUnmanaged from Dynamic {}
+#end

+ 1 - 0
tests/misc/projects/Issue3526/.gitignore

@@ -0,0 +1 @@
+/cs

+ 28 - 0
tests/misc/projects/Issue3526/IncompatibleCombinations.hx

@@ -0,0 +1,28 @@
+import cs.Constraints;
+import haxe.Constraints.Constructible;
+
+@:nativeGen
+class StructAndConstructible<T:CsStruct & Constructible<Void->Void>> {}
+
+@:nativeGen
+class ConstructibleAndStruct<T:Constructible<Void->Void> & CsStruct> {}
+
+@:nativeGen
+class StructAndClass<T:CsStruct & CsClass> {}
+
+@:nativeGen
+class ClassAndStruct<T:CsClass & CsStruct> {}
+
+#if (cs_ver >= "7.3")
+@:nativeGen
+class UnmanagedAndStruct<T:CsUnmanaged & CsStruct> {}
+
+@:nativeGen
+class StructAndUnmanaged<T:CsStruct & CsUnmanaged> {}
+
+@:nativeGen
+class UnmanagedAndConstructible<T:CsUnmanaged & Constructible<Void->Void>> {}
+
+@:nativeGen
+class ConstructibleAndUnmanaged<T:Constructible<Void->Void> & CsUnmanaged> {}
+#end

+ 72 - 0
tests/misc/projects/Issue3526/Main.hx

@@ -0,0 +1,72 @@
+import cs.Constraints;
+import haxe.Constraints.Constructible;
+
+@:classCode("
+	public static void testClass<T>(T t) where T : class {}
+	public static void testStruct<T>(T t) where T : struct {}
+	public static void testConstructible<T>(T t) where T : new() {}
+	public static void testConstructibleClass<T>(T t) where T : class, new() {}
+")
+class TestCs {
+    extern public static function testClass<T:CsClass>(t:T):Void;
+    extern public static function testStruct<T:CsStruct>(t:T):Void;
+    extern public static function testConstructible<T:Constructible<Void->Void>>(t:T):Void;
+    extern public static function testConstructibleClass<T:Constructible<Void->Void> & CsClass>(t:T):Void;
+}
+
+@:nativeGen
+class Main {
+    public static function main() {
+        testClass(new Array<String>());
+        TestCs.testClass(new Class_(new Array<String>()).value);
+
+        testStruct(42);
+        TestCs.testStruct(new Struct(42).value);
+
+        testConstructible(new haxe.Serializer());
+        TestCs.testConstructible(new Constructible_(new haxe.Serializer()).value);
+
+        testConstructibleClass(new haxe.Serializer());
+        TestCs.testConstructibleClass(new ConstructibleClass(new haxe.Serializer()).value);
+    }
+
+    static function testClass<T:CsClass>(value:T) TestCs.testClass(value);
+    static function testStruct<T:CsStruct>(value:T) TestCs.testStruct(value);
+    static function testConstructible<T:Constructible<Void->Void>>(value:T) TestCs.testConstructible(value);
+    static function testConstructibleClass<T:Constructible<Void->Void> & CsClass>(value:T) TestCs.testConstructibleClass(value);
+}
+
+@:nativeGen
+class Class_<T:CsClass> {
+    public var value:T;
+    public function new(value:T) this.value = value;
+}
+
+@:nativeGen
+class Struct<T:CsStruct> {
+    public var value:T;
+    public function new(value:T) this.value = value;
+}
+
+@:nativeGen
+class Constructible_<T:Constructible<Void->Void>> {
+    public var value:T;
+    public function new(value:T) this.value = value;
+}
+
+@:nativeGen
+class ConstructibleClass<T:Constructible<Void->Void> & CsClass> {
+    public var value:T;
+    public function new(value:T) this.value = value;
+}
+
+@:nativeGen
+class StructT<T, T1:T & CsStruct> {}
+
+#if (cs_ver >= "7.3")
+@:nativeGen
+class Unmanaged<T:CsUnmanaged> {}
+
+@:nativeGen
+class UnmanagedClass<T:CsUnmanaged & CsClass> {}
+#end

+ 4 - 0
tests/misc/projects/Issue3526/compile-7.3.hxml

@@ -0,0 +1,4 @@
+-cs cs
+-D no-compilation
+-D cs_ver=7.3
+Main

+ 2 - 0
tests/misc/projects/Issue3526/compile.hxml

@@ -0,0 +1,2 @@
+-cs cs
+Main

+ 4 - 0
tests/misc/projects/Issue3526/incompatible-combinations-fail.hxml

@@ -0,0 +1,4 @@
+-cs cs
+-D no-compilation
+-D cs_ver=7.3
+IncompatibleCombinations

+ 8 - 0
tests/misc/projects/Issue3526/incompatible-combinations-fail.hxml.stderr

@@ -0,0 +1,8 @@
+IncompatibleCombinations.hx:5: characters 1-72 : The new() constraint cannot be combined with the struct constraint.
+IncompatibleCombinations.hx:8: characters 1-72 : The new() constraint cannot be combined with the struct constraint.
+IncompatibleCombinations.hx:11: characters 1-46 : The class constraint cannot be combined with the struct constraint.
+IncompatibleCombinations.hx:14: characters 1-46 : The class constraint cannot be combined with the struct constraint.
+IncompatibleCombinations.hx:18: characters 1-54 : The unmanaged constraint cannot be combined with the struct constraint.
+IncompatibleCombinations.hx:21: characters 1-54 : The unmanaged constraint cannot be combined with the struct constraint.
+IncompatibleCombinations.hx:24: characters 1-78 : The unmanaged constraint cannot be combined with the new() constraint.
+IncompatibleCombinations.hx:27: characters 1-78 : The unmanaged constraint cannot be combined with the new() constraint.