From f6c6aba859f08b44ed5556abffb0e2c34ec45ae1 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Sun, 2 Mar 2025 13:12:20 -0500 Subject: [PATCH] [RGen] Add missing configuration options for notifications. (#22260) Add the missing notification configuration options for the FieldAttribute. Do remember that we will use the analyzer to make sure that those options are only used when the user passes the notification flag. --------- Co-authored-by: GitHub Actions Autoformatter --- src/ObjCBindings/FieldAttribute.cs | 10 ++ .../Attributes/FieldData.cs | 39 ++++- .../Attributes/FieldDataTests.cs | 4 +- .../DataModel/ClassBindingTests.cs | 144 ++++++++++++++++++ .../DataModel/EnumMemberCodeChangesTests.cs | 8 +- 5 files changed, 196 insertions(+), 9 deletions(-) diff --git a/src/ObjCBindings/FieldAttribute.cs b/src/ObjCBindings/FieldAttribute.cs index 3f7758dd7e66..9ad08c1aeaac 100644 --- a/src/ObjCBindings/FieldAttribute.cs +++ b/src/ObjCBindings/FieldAttribute.cs @@ -27,6 +27,16 @@ public class FieldAttribute : Attribute where T : Enum { /// public string? LibraryName { get; set; } = default; + /// + /// Get/Set the notification type. + /// + public Type? Type { get; set; } = null; + + /// + /// Get/Set the notification center. + /// + public string? NotificationCenter { get; set; } = null; + /// /// Create a new FieldAttribute for the given symbol and using the namespace as its containing library. /// The name of the symbol. diff --git a/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs b/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs index 7b5b10fe116c..38c32234e91e 100644 --- a/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs +++ b/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.Macios.Generator.Extensions; @@ -18,6 +19,16 @@ public enum ParsingError { public string SymbolName { get; } public string? LibraryName { get; } + /// + /// Gets and set the type to be used. This is a property that can be used on notifications. + /// + public string? Type { get; init; } + + /// + /// The notification center to be used, if null, the default one will be used. + /// + public string? NotificationCenter { get; init; } + public T? Flags { get; } = default; internal FieldData (string symbolName, string? libraryName, T? flags) @@ -45,6 +56,11 @@ public static bool TryParse (AttributeData attributeData, string? symbolName; string? libraryName = null; T? flags = default; + + // notifications customizations + string? notificationType = null; + string? notificationCenter = null; + switch (count) { case 1: if (!attributeData.ConstructorArguments [0].TryGetIdentifier (out symbolName)) { @@ -89,13 +105,23 @@ public static bool TryParse (AttributeData attributeData, case "Flags": flags = (T) value.Value!; break; + case "Type": + notificationType = ((INamedTypeSymbol) value.Value!).ToDisplayString (); + break; + case "NotificationCenter": + notificationCenter = (string?) value.Value!; + break; default: data = null; error = new (ParsingError.UnknownNamedArgument, name); return false; } } - data = new (symbolName, libraryName, flags); + + data = new (symbolName, libraryName, flags) { + Type = notificationType, + NotificationCenter = notificationCenter, + }; return true; } @@ -106,6 +132,10 @@ public bool Equals (FieldData other) return false; if (LibraryName != other.LibraryName) return false; + if (Type != other.Type) + return false; + if (NotificationCenter != other.NotificationCenter) + return false; if (Flags is not null && other.Flags is not null) { return Flags.Equals (other.Flags); } @@ -137,6 +167,11 @@ public override int GetHashCode () /// public override string ToString () { - return $"{{ SymbolName: '{SymbolName}', LibraryName: '{LibraryName ?? "null"}', Flags: '{Flags}' }}"; + var sb = new StringBuilder ($"{{ SymbolName: '{SymbolName}', "); + sb.Append ($"LibraryName: '{LibraryName ?? "null"}', "); + sb.Append ($"Type: '{Type ?? "null"}', "); + sb.Append ($"NotificationCenter: '{NotificationCenter ?? "null"}', "); + sb.Append ($"Flags: '{Flags}' }}"); + return sb.ToString (); } } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/FieldDataTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/FieldDataTests.cs index bc5900a2de98..2ca7a4112892 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/FieldDataTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/FieldDataTests.cs @@ -63,11 +63,11 @@ public IEnumerator GetEnumerator () { yield return [ new FieldData ("symbol", null, EnumValue.Default), - "{ SymbolName: 'symbol', LibraryName: 'null', Flags: 'Default' }" + "{ SymbolName: 'symbol', LibraryName: 'null', Type: 'null', NotificationCenter: 'null', Flags: 'Default' }" ]; yield return [ new FieldData ("symbol", "lib", EnumValue.Default), - "{ SymbolName: 'symbol', LibraryName: 'lib', Flags: 'Default' }" + "{ SymbolName: 'symbol', LibraryName: 'lib', Type: 'null', NotificationCenter: 'null', Flags: 'Default' }" ]; } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassBindingTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassBindingTests.cs index 07eaacfd8174..447b28802877 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassBindingTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassBindingTests.cs @@ -641,6 +641,150 @@ public partial class MyClass { } ]; + const string notificationCenterPropertyClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Field (""name"", Flags = Property.Notification, NotificationCenter=""SharedWorkspace.NotificationCenter"")] + public partial string Name { get; set; } = string.Empty; +} +"; + + yield return [ + notificationCenterPropertyClass, + new Binding ( + bindingInfo: new (new BindingTypeData ()), + name: "MyClass", + @namespace: ["NS"], + fullyQualifiedSymbol: "NS.MyClass", + symbolAvailability: new () + ) { + Base = "object", + Interfaces = ImmutableArray.Empty, + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + UsingDirectives = new HashSet { "ObjCBindings" }, + Modifiers = [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword) + ], + Properties = [ + new ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: new (), + attributes: [ + new ("ObjCBindings.FieldAttribute", ["name", "ObjCBindings.Property.Notification", "SharedWorkspace.NotificationCenter"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new ( + accessorKind: AccessorKind.Getter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + new ( + accessorKind: AccessorKind.Setter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + ] + ) { + + ExportFieldData = new ( + fieldData: new (symbolName: "name", flags: Property.Notification) { + NotificationCenter = "SharedWorkspace.NotificationCenter", + }, + libraryName: "NS"), + } + ] + } + ]; + + const string notificationTypePropertyClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Field (""name"", Flags = Property.Notification, Type=typeof (UIApplicationLaunchEventArgs))] + public partial string Name { get; set; } = string.Empty; +} + +public class UIApplicationLaunchEventArgs {} +"; + + yield return [ + notificationTypePropertyClass, + new Binding ( + bindingInfo: new (new BindingTypeData ()), + name: "MyClass", + @namespace: ["NS"], + fullyQualifiedSymbol: "NS.MyClass", + symbolAvailability: new () + ) { + Base = "object", + Interfaces = ImmutableArray.Empty, + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + UsingDirectives = new HashSet { "ObjCBindings" }, + Modifiers = [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword) + ], + Properties = [ + new ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: new (), + attributes: [ + new ("ObjCBindings.FieldAttribute", ["name", "ObjCBindings.Property.Notification", "NS.UIApplicationLaunchEventArgs"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new ( + accessorKind: AccessorKind.Getter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + new ( + accessorKind: AccessorKind.Setter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + ] + ) { + + ExportFieldData = new ( + fieldData: new (symbolName: "name", flags: Property.Notification) { + Type = "NS.UIApplicationLaunchEventArgs", + }, + libraryName: "NS"), + } + ] + } + ]; + const string fieldPropertyClass = @" using ObjCBindings; diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumMemberCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumMemberCodeChangesTests.cs index b7c5b6f8a195..c4b6d36256ae 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumMemberCodeChangesTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumMemberCodeChangesTests.cs @@ -262,7 +262,6 @@ public void NotEqualsDifferentFieldData () class TestDataToString : IEnumerable { public IEnumerator GetEnumerator () { - var simpleEnum = new EnumMember ( name: "EnumValue", libraryName: "Test", @@ -271,7 +270,6 @@ public IEnumerator GetEnumerator () symbolAvailability: new (), attributes: []); yield return [simpleEnum, "{ Name: 'EnumValue' SymbolAvailability: [] FieldInfo: Attributes: [] }"]; - var fieldDataEnum = new EnumMember ( name: "EnumValue", libraryName: "Test", @@ -281,7 +279,7 @@ public IEnumerator GetEnumerator () attributes: []); yield return [ fieldDataEnum, - "{ Name: 'EnumValue' SymbolAvailability: [] FieldInfo: FieldData = { SymbolName: 'x', LibraryName: 'libName', Flags: 'Default' }, LibraryName = Test, LibraryPath = /path/to/library Attributes: [] }" + "{ Name: 'EnumValue' SymbolAvailability: [] FieldInfo: FieldData = { SymbolName: 'x', LibraryName: 'libName', Type: 'null', NotificationCenter: 'null', Flags: 'Default' }, LibraryName = Test, LibraryPath = /path/to/library Attributes: [] }", ]; var builder = SymbolAvailability.CreateBuilder (); @@ -296,7 +294,7 @@ public IEnumerator GetEnumerator () attributes: []); yield return [ availabilityEnum, - "{ Name: 'EnumValue' SymbolAvailability: [{ Platform: 'iOS', Supported: '0.0', Unsupported: [], Obsoleted: [] }] FieldInfo: FieldData = { SymbolName: 'x', LibraryName: 'libName', Flags: 'Default' }, LibraryName = Test, LibraryPath = /path/to/library Attributes: [] }" + "{ Name: 'EnumValue' SymbolAvailability: [{ Platform: 'iOS', Supported: '0.0', Unsupported: [], Obsoleted: [] }] FieldInfo: FieldData = { SymbolName: 'x', LibraryName: 'libName', Type: 'null', NotificationCenter: 'null', Flags: 'Default' }, LibraryName = Test, LibraryPath = /path/to/library Attributes: [] }", ]; var attrsEnum = new EnumMember ( @@ -311,7 +309,7 @@ public IEnumerator GetEnumerator () ]); yield return [ attrsEnum, - "{ Name: 'EnumValue' SymbolAvailability: [{ Platform: 'iOS', Supported: '0.0', Unsupported: [], Obsoleted: [] }] FieldInfo: FieldData = { SymbolName: 'x', LibraryName: 'libName', Flags: 'Default' }, LibraryName = Test, LibraryPath = /path/to/library Attributes: [{ Name: Attribute1, Arguments: [] }, { Name: Attribute2, Arguments: [] }] }" + "{ Name: 'EnumValue' SymbolAvailability: [{ Platform: 'iOS', Supported: '0.0', Unsupported: [], Obsoleted: [] }] FieldInfo: FieldData = { SymbolName: 'x', LibraryName: 'libName', Type: 'null', NotificationCenter: 'null', Flags: 'Default' }, LibraryName = Test, LibraryPath = /path/to/library Attributes: [{ Name: Attribute1, Arguments: [] }, { Name: Attribute2, Arguments: [] }] }", ]; }