[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:
Rolf Bjarne Kvinge 2023-09-06 12:15:45 +02:00 коммит произвёл GitHub
Родитель 33a05569e0
Коммит 68432a1550
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 72 добавлений и 157 удалений

Просмотреть файл

@ -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]