[bgen] Add ToArray and ToFlags extension methods for strongly typed [Flags] enums. (#18925)
We have a few different implementations of how to compute a set or array of NSString constants from an enum value, and also computing an enum value from a set or array of NSString constants. So add support to the generator for generating a ToArray and a ToFlags method for these conversions, and remove all the manual code. This has a few advantages: * The generated code is automatically updated with new enum fields (not all previous implementations were). * Reduces code duplication. * Reduces manual code. Additionally, when computing between a group of constants and the corresponding flags, we have to handle any missing constants or flags gracefully, otherwise the code won’t be forward compatible. Example (from bug #18833): * App developer writes code to fetch the AVCaptureMetadataOutput.AvailableMetadataObjectTypes property, which is an enum value of flags. * App developer ships their app in the app store. * Apple releases a new iOS update, adding new values and constants to the enum. * The native [AVCaptureMetadataOutput availableMetadataObjectTypes] method returns these new constants. * This means that if the AVCaptureMetadataOutput.AvailableMetadataObjectTypes property throws an exception if encountering unknown constants, our API wouldn’t be forward compatible. * The correct solution here is that AVCaptureMetadataOutput.AvailableMetadataObjectTypes must ignore any constants it doesn’t know about. Fixes https://github.com/xamarin/xamarin-macios/issues/18833.
This commit is contained in:
Родитель
33a05569e0
Коммит
68432a1550
|
@ -38,12 +38,12 @@ namespace AVFoundation {
|
|||
public partial class AVCaptureMetadataOutput {
|
||||
|
||||
public AVMetadataObjectType AvailableMetadataObjectTypes {
|
||||
get { return AVMetadataObject.ArrayToEnum (WeakAvailableMetadataObjectTypes); }
|
||||
get { return AVMetadataObjectTypeExtensions.ToFlags (WeakAvailableMetadataObjectTypes); }
|
||||
}
|
||||
|
||||
public AVMetadataObjectType MetadataObjectTypes {
|
||||
get { return AVMetadataObject.ArrayToEnum (WeakMetadataObjectTypes); }
|
||||
set { WeakMetadataObjectTypes = AVMetadataObject.EnumToArray (value); }
|
||||
get { return AVMetadataObjectTypeExtensions.ToFlags (WeakMetadataObjectTypes); }
|
||||
set { WeakMetadataObjectTypes = value.ToArray (); }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,42 +41,6 @@ namespace AVFoundation {
|
|||
return AVMetadataObjectTypeExtensions.GetValue (WeakType);
|
||||
}
|
||||
}
|
||||
|
||||
internal static AVMetadataObjectType ArrayToEnum (NSString[]? arr)
|
||||
{
|
||||
AVMetadataObjectType rv = AVMetadataObjectType.None;
|
||||
|
||||
if (arr is null || arr.Length == 0)
|
||||
return rv;
|
||||
|
||||
foreach (var str in arr) {
|
||||
rv |= AVMetadataObjectTypeExtensions.GetValue (str);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
internal static NSString[]? EnumToArray (AVMetadataObjectType value)
|
||||
{
|
||||
if (value == AVMetadataObjectType.None)
|
||||
return null;
|
||||
|
||||
var rv = new List<NSString> ();
|
||||
var val = (ulong) value;
|
||||
var shifts = 0;
|
||||
|
||||
while (val != 0) {
|
||||
if ((val & 0x1) == 0x1) {
|
||||
var constant = ((AVMetadataObjectType) (0x1UL << shifts)).GetConstant ();
|
||||
if (constant is not null)
|
||||
rv.Add (constant);
|
||||
}
|
||||
val >>= 1;
|
||||
shifts++;
|
||||
}
|
||||
|
||||
return rv.ToArray ();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -11,82 +11,7 @@ namespace HomeKit {
|
|||
public partial class HMHome {
|
||||
public HMService []? GetServices (HMServiceType serviceTypes)
|
||||
{
|
||||
var arr = new ServiceTypeList<NSString> ();
|
||||
|
||||
if ((serviceTypes & HMServiceType.LightBulb) == HMServiceType.LightBulb)
|
||||
arr.Add (HMServiceType.LightBulb.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Switch) == HMServiceType.Switch)
|
||||
arr.Add (HMServiceType.Switch.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Thermostat) == HMServiceType.Thermostat)
|
||||
arr.Add (HMServiceType.Thermostat.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.GarageDoorOpener) == HMServiceType.GarageDoorOpener)
|
||||
arr.Add (HMServiceType.GarageDoorOpener.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.AccessoryInformation) == HMServiceType.AccessoryInformation)
|
||||
arr.Add (HMServiceType.AccessoryInformation.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Fan) == HMServiceType.Fan)
|
||||
arr.Add (HMServiceType.Fan.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Outlet) == HMServiceType.Outlet)
|
||||
arr.Add (HMServiceType.Outlet.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.LockMechanism) == HMServiceType.LockMechanism)
|
||||
arr.Add (HMServiceType.LockMechanism.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.LockManagement) == HMServiceType.LockManagement)
|
||||
arr.Add (HMServiceType.LockManagement.GetConstant ());
|
||||
// iOS 9
|
||||
if ((serviceTypes & HMServiceType.AirQualitySensor) == HMServiceType.AirQualitySensor)
|
||||
arr.Add (HMServiceType.AirQualitySensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.SecuritySystem) == HMServiceType.SecuritySystem)
|
||||
arr.Add (HMServiceType.SecuritySystem.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.CarbonMonoxideSensor) == HMServiceType.CarbonMonoxideSensor)
|
||||
arr.Add (HMServiceType.CarbonMonoxideSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.ContactSensor) == HMServiceType.ContactSensor)
|
||||
arr.Add (HMServiceType.ContactSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Door) == HMServiceType.Door)
|
||||
arr.Add (HMServiceType.Door.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.HumiditySensor) == HMServiceType.HumiditySensor)
|
||||
arr.Add (HMServiceType.HumiditySensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.LeakSensor) == HMServiceType.LeakSensor)
|
||||
arr.Add (HMServiceType.LeakSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.LightSensor) == HMServiceType.LightSensor)
|
||||
arr.Add (HMServiceType.LightSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.MotionSensor) == HMServiceType.MotionSensor)
|
||||
arr.Add (HMServiceType.MotionSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.OccupancySensor) == HMServiceType.OccupancySensor)
|
||||
arr.Add (HMServiceType.OccupancySensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.StatefulProgrammableSwitch) == HMServiceType.StatefulProgrammableSwitch)
|
||||
arr.Add (HMServiceType.StatefulProgrammableSwitch.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.StatelessProgrammableSwitch) == HMServiceType.StatelessProgrammableSwitch)
|
||||
arr.Add (HMServiceType.StatelessProgrammableSwitch.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.SmokeSensor) == HMServiceType.SmokeSensor)
|
||||
arr.Add (HMServiceType.SmokeSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.TemperatureSensor) == HMServiceType.TemperatureSensor)
|
||||
arr.Add (HMServiceType.TemperatureSensor.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Window) == HMServiceType.Window)
|
||||
arr.Add (HMServiceType.Window.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.WindowCovering) == HMServiceType.WindowCovering)
|
||||
arr.Add (HMServiceType.WindowCovering.GetConstant ());
|
||||
// iOS 10.2
|
||||
if ((serviceTypes & HMServiceType.AirPurifier) == HMServiceType.AirPurifier)
|
||||
arr.Add (HMServiceType.AirPurifier.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.VentilationFan) == HMServiceType.VentilationFan)
|
||||
arr.Add (HMServiceType.VentilationFan.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.FilterMaintenance) == HMServiceType.FilterMaintenance)
|
||||
arr.Add (HMServiceType.FilterMaintenance.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.HeaterCooler) == HMServiceType.HeaterCooler)
|
||||
arr.Add (HMServiceType.HeaterCooler.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.HumidifierDehumidifier) == HMServiceType.HumidifierDehumidifier)
|
||||
arr.Add (HMServiceType.HumidifierDehumidifier.GetConstant ());
|
||||
if ((serviceTypes & HMServiceType.Slats) == HMServiceType.Slats)
|
||||
arr.Add (HMServiceType.Slats.GetConstant ());
|
||||
|
||||
return GetServices (arr.ToArray ());
|
||||
}
|
||||
|
||||
class ServiceTypeList<T> : List<T> {
|
||||
public new void Add (T? item)
|
||||
{
|
||||
if (item is not null)
|
||||
base.Add (item);
|
||||
}
|
||||
return GetServices (serviceTypes.ToArray ());
|
||||
}
|
||||
|
||||
#if !NET
|
||||
|
|
|
@ -10,38 +10,14 @@ namespace PassKit {
|
|||
|
||||
static public PKContactFields GetValue (NSSet set)
|
||||
{
|
||||
var fields = PKContactFields.None;
|
||||
if (set is null)
|
||||
return fields;
|
||||
|
||||
foreach (PKContactFields value in Enum.GetValues (typeof (PKContactFields))) {
|
||||
var constant = value.GetConstant ();
|
||||
// None does not have an associated native value and Contains would throw an ANE
|
||||
if ((constant is not null) && set.Contains (constant))
|
||||
fields |= value;
|
||||
}
|
||||
return fields;
|
||||
return PKContactFields.None;
|
||||
return PKContactFieldsExtensions.ToFlags (set.ToArray<NSString> ());
|
||||
}
|
||||
|
||||
static public NSSet GetSet (PKContactFields values)
|
||||
{
|
||||
var set = new NSMutableSet ();
|
||||
if (values == PKContactFields.None)
|
||||
return set;
|
||||
|
||||
#if NET
|
||||
foreach (var value in Enum.GetValues<PKContactFields> ()) {
|
||||
#else
|
||||
foreach (PKContactFields value in Enum.GetValues (typeof (PKContactFields))) {
|
||||
#endif
|
||||
if (values.HasFlag (value)) {
|
||||
var constant = value.GetConstant ();
|
||||
// None does not have an associated native value and Contains would throw an ANE
|
||||
if (constant is not null)
|
||||
set.Add (constant);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
return new NSMutableSet (values.ToArray ());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ public partial class Generator {
|
|||
// - call/emit PrintPlatformAttributes on the type
|
||||
void GenerateEnum (Type type)
|
||||
{
|
||||
if (AttributeManager.HasAttribute<FlagsAttribute> (type))
|
||||
var isFlagsEnum = AttributeManager.HasAttribute<FlagsAttribute> (type);
|
||||
if (isFlagsEnum)
|
||||
print ("[Flags]");
|
||||
|
||||
var native = AttributeManager.GetCustomAttribute<NativeAttribute> (type);
|
||||
|
@ -238,6 +239,57 @@ public partial class Generator {
|
|||
print ("return {0}.{1};", type.Name, default_symbol.Item1.Name);
|
||||
indent--;
|
||||
print ("}");
|
||||
|
||||
if (isFlagsEnum) {
|
||||
print ($"public static NSString[] ToArray (this {type.Name} value)");
|
||||
print ("{");
|
||||
indent++;
|
||||
print ("var rv = new global::System.Collections.Generic.List<NSString> ();");
|
||||
foreach (var kvp in fields) {
|
||||
print ($"if (value.HasFlag ({type.Name}.{kvp.Key.Name}) && {kvp.Value.SymbolName} != IntPtr.Zero)");
|
||||
indent++;
|
||||
print ($"rv.Add ((NSString) Runtime.GetNSObject ({kvp.Value.SymbolName})!);");
|
||||
indent--;
|
||||
}
|
||||
print ($"// In order to be forward-compatible, any unknown values are ignored.");
|
||||
print ("return rv.ToArray ();");
|
||||
indent--;
|
||||
print ("}");
|
||||
print ("");
|
||||
|
||||
print ($"public static {type.Name} ToFlags (global::System.Collections.Generic.IEnumerable<NSString{(nullable ? "?" : "")}>{(nullable ? "?" : "")} constants)");
|
||||
print ("{");
|
||||
indent++;
|
||||
print ($"var rv = default ({type.Name});");
|
||||
print ($"if (constants is null)");
|
||||
indent++;
|
||||
print ("return rv;");
|
||||
indent--;
|
||||
print ($"foreach (var constant in constants) {{");
|
||||
indent++;
|
||||
var first = true;
|
||||
if (nullable) {
|
||||
print ("if (constant is null)");
|
||||
indent++;
|
||||
print ("continue;");
|
||||
indent--;
|
||||
first = false;
|
||||
}
|
||||
foreach (var kvp in fields) {
|
||||
print ($"{(first ? "if" : "else if")} (constant.IsEqualTo ({kvp.Value.SymbolName}))");
|
||||
first = false;
|
||||
indent++;
|
||||
print ($"rv |= {type.Name}.{kvp.Key.Name};");
|
||||
indent--;
|
||||
}
|
||||
print ($"// In order to be forward-compatible, any unknown values are ignored.");
|
||||
indent--;
|
||||
print ("}");
|
||||
print ("return rv;");
|
||||
indent--;
|
||||
print ("}");
|
||||
print ("");
|
||||
}
|
||||
}
|
||||
|
||||
if ((fields.Count > 0) || (error is not null) || (null_field is not null)) {
|
||||
|
|
|
@ -51,21 +51,12 @@ namespace MonoTouchFixtures.AVFoundation {
|
|||
[Test]
|
||||
public void Flags ()
|
||||
{
|
||||
// we can only only work with [Weak]AvailableMetadataObjectTypes on an instance of AVCaptureMetadataOutput
|
||||
// so we use reflection to test the internal of the flags/enum/constants conversions
|
||||
// the previous tests ensure what we need is not removed by the linker
|
||||
var t = typeof (AVMetadataObject);
|
||||
var array_to_enum = t.GetMethod ("ArrayToEnum", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Assert.NotNull (array_to_enum, "ArrayToEnum");
|
||||
|
||||
var enum_to_array = t.GetMethod ("EnumToArray", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Assert.NotNull (enum_to_array, "EnumToArray");
|
||||
|
||||
// single
|
||||
var flags = AVMetadataObjectType.Face;
|
||||
var result = (AVMetadataObjectType) array_to_enum.Invoke (null, new [] { new NSString [] { flags.GetConstant () } });
|
||||
var result = AVMetadataObjectTypeExtensions.ToFlags (new NSString [] { flags.GetConstant () });
|
||||
Assert.AreEqual (flags, result, "a2e 1");
|
||||
var back = (NSString []) enum_to_array.Invoke (null, new object [] { result });
|
||||
|
||||
var back = result.ToArray ();
|
||||
Assert.That (back.Length, Is.EqualTo (1), "l 1");
|
||||
Assert.That (back [0], Is.EqualTo (flags.GetConstant ()), "e2a 1");
|
||||
|
||||
|
@ -78,14 +69,21 @@ namespace MonoTouchFixtures.AVFoundation {
|
|||
AVMetadataObjectType.DogBody.GetConstant (),
|
||||
AVMetadataObjectType.HumanBody.GetConstant ()
|
||||
};
|
||||
result = (AVMetadataObjectType) array_to_enum.Invoke (null, new [] { array });
|
||||
result = AVMetadataObjectTypeExtensions.ToFlags (array);
|
||||
Assert.AreEqual (flags, result, "a2e 3");
|
||||
back = (NSString []) enum_to_array.Invoke (null, new object [] { result });
|
||||
back = result.ToArray ();
|
||||
Assert.That (back.Length, Is.EqualTo (3), "l 3");
|
||||
Assert.That (back [0], Is.EqualTo (array [0]), "e2a 3a");
|
||||
Assert.That (back [1], Is.EqualTo (array [1]), "e2a 3b");
|
||||
Assert.That (back [2], Is.EqualTo (array [2]), "e2a 3c");
|
||||
}
|
||||
|
||||
var all = (AVMetadataObjectType) ulong.MaxValue;
|
||||
var someArray = all.ToArray (); // converting all flags to an array will only return strings for flags that exist in the current OS.
|
||||
Assert.That (someArray.Length, Is.GreaterThan (1), "some back");
|
||||
var someFlags = AVMetadataObjectTypeExtensions.ToFlags (someArray);
|
||||
Assert.That (someFlags, Is.Not.EqualTo (AVMetadataObjectType.None), "Some, but not None");
|
||||
Assert.That (someFlags, Is.Not.EqualTo (all), "Some, but not all");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
Загрузка…
Ссылка в новой задаче