Browse Source

[cs] Added cs.Flags to easily manipulate C# Enums with the Flags attribute

Cauê Waneck 10 years ago
parent
commit
fe7e9a83da
5 changed files with 201 additions and 5 deletions
  1. 32 4
      gencs.ml
  2. 1 1
      libs
  3. 100 0
      std/cs/Flags.hx
  4. 15 0
      tests/unit/native_cs/src/haxe/test/TEnumWithValue.cs
  5. 53 0
      tests/unit/src/unit/TestCSharp.hx

+ 32 - 4
gencs.ml

@@ -844,6 +844,8 @@ let configure gen =
 		let ret = match t with
 			| TAbstract (a, pl) when not (Meta.has Meta.CoreType a.a_meta) ->
 				real_type (Abstract.get_underlying_type a pl)
+			| TAbstract ({ a_path = (["cs";"_Flags"], "EnumUnderlying") }, [t]) ->
+				real_type t
 			| TInst( { cl_path = (["haxe"], "Int32") }, [] ) -> gen.gcon.basic.tint
 			| TInst( { cl_path = (["haxe"], "Int64") }, [] ) -> ti64
 			| TAbstract( { a_path = [],"Class" }, _ )
@@ -3272,13 +3274,38 @@ let ilpath_s = function
 let get_cls = function
 	| _,_,c -> c
 
-let convert_ilenum ctx p ilcls =
+(* TODO: When possible on Haxe, use this to detect flag enums, and make an abstract with @:op() *)
+(* that behaves like an enum, and with an enum as its underlying type *)
+let enum_is_flag ilcls =
+	let check_flag name ns = name = "FlagsAttribute" && ns = ["System"] in
+	List.exists (fun a ->
+		match a.ca_type with
+			| TypeRef r ->
+				check_flag r.tr_name r.tr_namespace
+			| TypeDef d ->
+				check_flag d.td_name d.td_namespace
+			| Method m ->
+				(match m.m_declaring with
+					| Some d ->
+						check_flag d.td_name d.td_namespace
+					| _ -> false)
+			| MemberRef r ->
+				(match r.memr_class with
+					| TypeRef r ->
+						check_flag r.tr_name r.tr_namespace
+					| TypeDef d ->
+						check_flag d.td_name d.td_namespace
+					| _ -> false)
+			| _ ->
+				false
+	) ilcls.cattrs
+
+let convert_ilenum ctx p ?(is_flag=false) ilcls =
 	let meta = ref [
 		Meta.Native, [EConst (String (ilpath_s ilcls.cpath) ), p], p;
-		Meta.Enum, [], p;
 		Meta.CsNative, [], p;
-		Meta.Extern, [], p; (* abstracts can't be externs *)
 	] in
+
 	let data = ref [] in
 	List.iter (fun f -> match f.fname with
 		| "value__" -> ()
@@ -3302,8 +3329,9 @@ let convert_ilenum ctx p ilcls =
 	let data = List.stable_sort (fun (_,i1) (_,i2) -> Int64.compare i1 i2) (List.rev !data) in
 
 	let _, c = netpath_to_hx ctx.nstd ilcls.cpath in
+	let name = netname_to_hx c in
 	EEnum {
-		d_name = netname_to_hx c;
+		d_name = if is_flag then name ^ "_FlagsEnum" else name;
 		d_doc = None;
 		d_params = []; (* enums never have type parameters *)
 		d_meta = !meta;

+ 1 - 1
libs

@@ -1 +1 @@
-Subproject commit 9b294cde4dbfc2166687d1f9f0a86f45de325248
+Subproject commit 7a767563347f00ad32da8f9fa6a35462874ec12a

+ 100 - 0
std/cs/Flags.hx

@@ -0,0 +1,100 @@
+package cs;
+
+/**
+	Use this type to have access to the bitwise operators of C# enums that have a `cs.system.FlagsAttribute` attribute.
+
+	Usage example:
+	```haxe
+		import cs.system.reflection.BindingFlags;
+
+		var binding = new Flags(BindingFlags.Public) | BindingFlags.Static | BindingFlags.NonPublic;
+	```
+ **/
+abstract Flags<T : EnumValue>(T) from T to T
+{
+
+	/**
+		Creates a new `Flags` type with an optional initial value. If no initial value was specified,
+		the default enum value for an empty flags attribute is specified
+	 **/
+	@:extern inline public function new(?initial:T)
+		this = initial;
+
+	/**
+		Accessible through the bitwise OR operator (`|`). Returns a new `Flags` type with the flags
+		passed at `flags` added to it.
+	 **/
+	@:op(A|B) @:extern inline public function add(flags:Flags<T>):Flags<T>
+	{
+		return new Flags(underlying() | flags.underlying());
+	}
+
+	/**
+		Accessible through the bitwise AND operator (`&`). Returns a new `Flags` type with
+		the flags that are set on both `this` and `flags`
+	 **/
+	@:op(A&B) @:extern inline public function bitAnd(flags:Flags<T>):Flags<T>
+	{
+		return new Flags(underlying() & flags.underlying());
+	}
+
+	/**
+		Accessible through the bitwise XOR operator (`^`).
+	 **/
+	@:op(A^B) @:extern inline public function bitXor(flags:Flags<T>):Flags<T>
+	{
+		return new Flags(underlying() & flags.underlying());
+	}
+
+	/**
+		Accesible through the bitwise negation operator (`~`). Returns a new `Flags` type
+		with all unset flags as set - but the ones that are set already.
+	 **/
+	@:op(~A) @:extern inline public function bitNeg():Flags<T>
+	{
+		return new Flags(~underlying());
+	}
+
+	/**
+		Returns a new `Flags` type with all flags set by `flags` unset
+	 **/
+	@:extern inline public function remove(flags:Flags<T>):Flags<T>
+	{
+		return new Flags(underlying() & ~flags.underlying());
+	}
+
+	/**
+		Returns whether `flag` is present on `this` type
+	 **/
+	@:extern inline public function has(flag:T):Bool
+	{
+		return underlying() & new Flags(flag).underlying() != null;
+	}
+
+	/**
+		Returns whether `this` type has any flag set by `flags` also set
+	 **/
+	@:extern inline public function hasAny(flags:Flags<T>):Bool
+	{
+		return underlying() & flags.underlying() != null;
+	}
+
+	/**
+		Returns whether `this` type has all flags set by `flags` also set
+	 **/
+	@:extern inline public function hasAll(flags:Flags<T>):Bool
+	{
+		return underlying() & flags.underlying() == flags.underlying();
+	}
+
+	@:extern inline private function underlying():EnumUnderlying<T>
+		return this;
+}
+
+@:coreType private abstract EnumUnderlying<T> from T to T
+{
+	@:op(A|B) public static function or<T>(lhs:EnumUnderlying<T>, rhs:EnumUnderlying<T>):T;
+	@:op(A^B) public static function xor<T>(lhs:EnumUnderlying<T>, rhs:EnumUnderlying<T>):T;
+	@:op(A&B) public static function and<T>(lhs:EnumUnderlying<T>, rhs:EnumUnderlying<T>):T;
+	@:op(~A) public static function bneg<T>(t:EnumUnderlying<T>):T;
+}

+ 15 - 0
tests/unit/native_cs/src/haxe/test/TEnumWithValue.cs

@@ -14,5 +14,20 @@ public enum TEnumWithBigValue : ulong
 	TBC = 0x3000000000000L, 
 }
 
+[System.Flags]
+public enum TEnumWithFlag
+{
+	TFA = 0x100,TFB = 0x1,TFD = 0x10, TFC = 0x20
+}
+
+[System.Flags]
+public enum TEnumWithBigFlag : ulong
+{
+	TFBA = 0x1000000000L, 
+	TFBD = 0x200000000000L, 
+	TFBB = 0x100000000L, 
+	TFBC = 0x3000000000000L, 
+}
+
 }
 

+ 53 - 0
tests/unit/src/unit/TestCSharp.hx

@@ -5,9 +5,13 @@ import haxe.test.Base.Base_InnerClass;
 import haxe.test.TEnum;
 import haxe.test.TEnumWithValue;
 import haxe.test.TEnumWithBigValue;
+import haxe.test.TEnumWithFlag;
+import haxe.test.TEnumWithBigFlag;
 import haxe.test.IEditableTextBuffer;
 import haxe.test.LowerCaseClass;
 
+import cs.Flags;
+
 import NoPackage;
 #if unsafe
 import cs.Pointer;
@@ -291,6 +295,55 @@ class TestCSharp extends Test
 		eq(21,c.SomeProp2);
 	}
 
+	function testEnumFlags()
+	{
+		var flags = new Flags(TFA) | TFC;
+		t(flags.has(TFA));
+		t(flags.has(TFC));
+		f(flags.has(TFB));
+		f(flags.has(TFD));
+		flags = new Flags();
+		f(flags.has(TFA));
+		f(flags.has(TFB));
+		f(flags.has(TFC));
+		f(flags.has(TFD));
+
+		flags |= TFB;
+		t(flags.has(TFB));
+		eq(flags & TFB,flags);
+		flags |= TFA;
+
+		f(flags.has(TFD));
+		t(flags.hasAny(new Flags(TFD) | TFB));
+		f(flags.hasAny(new Flags(TFD) | TFC));
+		f(flags.hasAll(new Flags(TFD) | TFB));
+		t(flags.hasAll(new Flags(TFB)));
+		t(flags.hasAll(new Flags(TFA) | TFB));
+
+		var flags = new Flags(TFBA) | TFBC;
+		t(flags.has(TFBA));
+		t(flags.has(TFBC));
+		f(flags.has(TFBB));
+		f(flags.has(TFBD));
+		flags = new Flags();
+		f(flags.has(TFBA));
+		f(flags.has(TFBB));
+		f(flags.has(TFBC));
+		f(flags.has(TFBD));
+
+		flags |= TFBB;
+		t(flags.has(TFBB));
+		eq(flags & TFBB,flags);
+		flags |= TFBA;
+
+		f(flags.has(TFBD));
+		t(flags.hasAny(new Flags(TFBD) | TFBB));
+		f(flags.hasAny(new Flags(TFBD) | TFBC));
+		f(flags.hasAll(new Flags(TFBD) | TFBB));
+		t(flags.hasAll(new Flags(TFBB)));
+		t(flags.hasAll(new Flags(TFBA) | TFBB));
+	}
+
 	function testEnum()
 	{
 		var e = TEnum.TA;