Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for native cs type parameter constraints #8311

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src-json/define.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
65 changes: 61 additions & 4 deletions src/generators/gencs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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") }, [] )
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
55 changes: 55 additions & 0 deletions std/cs/Constraints.hx
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions tests/misc/projects/Issue3526/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/cs
28 changes: 28 additions & 0 deletions tests/misc/projects/Issue3526/IncompatibleCombinations.hx
Original file line number Diff line number Diff line change
@@ -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 changes: 72 additions & 0 deletions tests/misc/projects/Issue3526/Main.hx
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 0 deletions tests/misc/projects/Issue3526/compile-7.3.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-cs cs
-D no-compilation
-D cs_ver=7.3
Main
2 changes: 2 additions & 0 deletions tests/misc/projects/Issue3526/compile.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-cs cs
Main
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-cs cs
-D no-compilation
-D cs_ver=7.3
IncompatibleCombinations
Original file line number Diff line number Diff line change
@@ -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.