Skip to content

Commit

Permalink
[RGen] Use the correct attribute for field properties.
Browse files Browse the repository at this point in the history
We cannot use Export because we need to be ablet to pass the library
name to generate the property field correctly. This change moves from
using Export<Field> to Field<Property> which allows to mark a property
as a field AND will allow use to add the library name.

This change + the one in PR
#21945 improves the way
the Library.g.cs file is generated.
  • Loading branch information
mandel-macaque committed Jan 13, 2025
1 parent 3f2cae0 commit ab71bee
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 83 deletions.
17 changes: 0 additions & 17 deletions src/ObjCBindings/FieldTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,6 @@

namespace ObjCBindings {

/// <summary>
/// The exported constant/field is a class/interface property field.
/// </summary>
[Flags]
[Experimental ("APL0003")]
public enum Field {
/// <summary>
/// Use the default values.
/// </summary>
Default = 0,

/// <summary>
/// Field represents a notification in ObjC.
/// </summary>
Notification = 1 << 2,
}

/// <summary>
/// Field flag that states that the field is used as a Enum value.
/// </summary>
Expand Down
28 changes: 15 additions & 13 deletions src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ internal FieldData (string symbolName, string? libraryName, T? flags)
LibraryName = libraryName;
Flags = flags;
}

internal FieldData (string symbolName, T? flags) : this(symbolName, null, flags) { }

internal FieldData (string symbolName) : this(symbolName, null, default) { }

public static bool TryParse (AttributeData attributeData,
[NotNullWhen (true)] out FieldData<T>? data)
Expand All @@ -52,21 +56,19 @@ public static bool TryParse (AttributeData attributeData,
if (!attributeData.ConstructorArguments [0].TryGetIdentifier (out symbolName)) {
return false;
}
switch (attributeData.ConstructorArguments [1].Value) {
// there are two possible cases here:
// 1. The second argument is a string
// 2. The second argument is an enum
case T enumValue:
flags = enumValue;
break;
case string lib:
libraryName = lib;
break;
default:
// unexpected value :/
error = new (ParsingError.UnknownConstructor, attributeData.ConstructorArguments.Length);

if (attributeData.ConstructorArguments [1].Value is string) {
libraryName = (string?) attributeData.ConstructorArguments [1].Value!;
} else {
flags = (T) attributeData.ConstructorArguments [1].Value!;
}
break;
case 3:
if (!attributeData.ConstructorArguments [0].TryGetIdentifier (out symbolName)) {
return false;
}
libraryName = (string?) attributeData.ConstructorArguments [1].Value!;
flags = (T) attributeData.ConstructorArguments [2].Value!;
break;
default:
// 0 should not be an option.
Expand Down
16 changes: 13 additions & 3 deletions src/rgen/Microsoft.Macios.Generator/AttributesNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ static class AttributesNames {
public const string BindingStrongDictionaryAttribute = "ObjCBindings.BindingTypeAttribute<ObjCBindings.StrongDictionary>";
public const string FieldAttribute = "ObjCBindings.FieldAttribute";
public const string EnumFieldAttribute = "ObjCBindings.FieldAttribute<ObjCBindings.EnumValue>";
public const string ExportFieldAttribute = "ObjCBindings.ExportAttribute<ObjCBindings.Field>";
public const string FieldPropertyAttribute = "ObjCBindings.FieldAttribute<ObjCBindings.Property>";
public const string ExportPropertyAttribute = "ObjCBindings.ExportAttribute<ObjCBindings.Property>";
public const string ExportMethodAttribute = "ObjCBindings.ExportAttribute<ObjCBindings.Method>";
public const string SupportedOSPlatformAttribute = "System.Runtime.Versioning.SupportedOSPlatformAttribute";
Expand Down Expand Up @@ -55,9 +55,19 @@ static class AttributesNames {
{
// we cannot use a switch statement because typeof is not a constant value
var type = typeof (T);
if (type == typeof (ObjCBindings.Field)) {
return ExportFieldAttribute;
if (type == typeof (ObjCBindings.Property)) {
return FieldPropertyAttribute;
}
if (type == typeof(ObjCBindings.EnumValue)) {
return EnumFieldAttribute;
}
return null;
}

public static string? GetExportAttributeName<T> () where T : Enum
{
// we cannot use a switch statement because typeof is not a constant value
var type = typeof (T);
if (type == typeof (ObjCBindings.Property)) {
return ExportPropertyAttribute;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ internal static bool Skip (PropertyDeclarationSyntax propertyDeclarationSyntax,
// 2. Exported properties
if (propertyDeclarationSyntax.Modifiers.Any (SyntaxKind.PartialKeyword)) {
return !propertyDeclarationSyntax.HasAtLeastOneAttribute (semanticModel,
AttributesNames.ExportFieldAttribute, AttributesNames.ExportPropertyAttribute);
AttributesNames.FieldPropertyAttribute, AttributesNames.ExportPropertyAttribute);
}

return true;
Expand Down
6 changes: 3 additions & 3 deletions src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ namespace Microsoft.Macios.Generator.DataModel;
/// <summary>
/// The data of the field attribute used to mark the value as a field binding.
/// </summary>
public ExportData<Field>? ExportFieldData { get; init; }
public FieldData<ObjCBindings.Property>? ExportFieldData { get; init; }

/// <summary>
/// True if the property represents a Objc field.
/// </summary>
[MemberNotNullWhen (true, nameof (ExportFieldData))]
public bool IsField => ExportFieldData is not null;

public bool IsNotification => IsField && ExportFieldData.Value.Flags.HasFlag (Field.Notification);
public bool IsNotification => IsField && ExportFieldData.Value.Flags.HasFlag (ObjCBindings.Property.Notification);

/// <summary>
/// The data of the field attribute used to mark the value as a property binding.
Expand Down Expand Up @@ -211,7 +211,7 @@ public static bool TryCreate (PropertyDeclarationSyntax declaration, SemanticMod
attributes: attributes,
modifiers: [.. declaration.Modifiers],
accessors: accessorCodeChanges) {
ExportFieldData = propertySymbol.GetExportData<Field> (),
ExportFieldData = propertySymbol.GetFieldData<ObjCBindings.Property> (),
ExportPropertyData = propertySymbol.GetExportData<ObjCBindings.Property> (),
};
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -193,22 +194,17 @@ public static BindingTypeData<T> GetBindingData<T> (this ISymbol symbol) where T
return default;
}

/// <summary>
/// Retrieve the data of an export attribute on a symbol.
/// </summary>
/// <param name="symbol">The tagged symbol.</param>
/// <typeparam name="T">Enum type used in the attribute.</typeparam>
/// <returns>The data of the export attribute if present or null if it was not found.</returns>
/// <remarks>If the passed enum is unknown or not supproted as an enum for the export attribute, null will be
/// returned.</remarks>
public static ExportData<T>? GetExportData<T> (this ISymbol symbol) where T : Enum
delegate string? GetAttributeNames ();
delegate bool TryParse<T> (AttributeData data, [NotNullWhen(true)]out T? value) where T : struct;

static T? GetAttribute<T> (this ISymbol symbol, GetAttributeNames getAttributeNames, TryParse<T> tryParse) where T : struct
{
var attributes = symbol.GetAttributeData ();
if (attributes.Count == 0)
return null;

// retrieve the name of the attribute based on the flag
var attrName = AttributesNames.GetFieldAttributeName<T> ();
var attrName = getAttributeNames ();
if (attrName is null)
return null;
if (!attributes.TryGetValue (attrName, out var exportAttrDataList) ||
Expand All @@ -220,11 +216,33 @@ public static BindingTypeData<T> GetBindingData<T> (this ISymbol symbol) where T
if (fieldSyntax is null)
return null;

if (ExportData<T>.TryParse (exportAttrData, out var exportData))
if (tryParse (exportAttrData, out var exportData))
return exportData.Value;
return null;
}

/// <summary>
/// Retrieve the data of an export attribute on a symbol.
/// </summary>
/// <param name="symbol">The tagged symbol.</param>
/// <typeparam name="T">Enum type used in the attribute.</typeparam>
/// <returns>The data of the export attribute if present or null if it was not found.</returns>
/// <remarks>If the passed enum is unknown or not supported as an enum for the export attribute, null will be
/// returned.</remarks>
public static ExportData<T>? GetExportData<T> (this ISymbol symbol) where T : Enum
=> GetAttribute<ExportData<T>> (symbol, AttributesNames.GetExportAttributeName<T>, ExportData<T>.TryParse);

/// <summary>
/// Retrieve the data of a field attribute on a symbol.
/// </summary>
/// <param name="symbol">The tagged symbol.</param>
/// <typeparam name="T">Enum type used in the attribute.</typeparam>
/// <returns>The data of the export attribute if present or null if it was not found.</returns>
/// <remarks>If the passed enum is unknown or not supported as an enum for the field attribute, null will be
/// returned.</remarks>
public static FieldData<T>? GetFieldData<T> (this ISymbol symbol) where T : Enum
=> GetAttribute<FieldData<T>> (symbol, AttributesNames.GetFieldAttributeName<T>, FieldData<T>.TryParse);

/// <summary>
/// Returns if a type is blittable or not.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public class ExportDataTests {
[Fact]
public void TestExportDataEqualsDiffSelector ()
{
var x = new ExportData<Field> ("field1");
var y = new ExportData<Field> ("field2");
var x = new ExportData<Method> ("field1");
var y = new ExportData<Method> ("field2");
Assert.False (x.Equals (y));
Assert.False (y.Equals (x));
Assert.False (x == y);
Expand All @@ -27,8 +27,8 @@ public void TestExportDataEqualsDiffSelector ()
[Fact]
public void TestExportDataEqualsDiffArgumentSemantic ()
{
var x = new ExportData<Field> ("property", ArgumentSemantic.None);
var y = new ExportData<Field> ("property", ArgumentSemantic.Retain);
var x = new ExportData<Method> ("property", ArgumentSemantic.None);
var y = new ExportData<Method> ("property", ArgumentSemantic.Retain);
Assert.False (x.Equals (y));
Assert.False (y.Equals (x));
Assert.False (x == y);
Expand All @@ -52,14 +52,14 @@ class TestDataToString : IEnumerable<object []> {
public IEnumerator<object []> GetEnumerator ()
{
yield return [
Field.Default,
new ExportData<Field> ("symbol", ArgumentSemantic.None, Field.Default),
"{ Type: 'ObjCBindings.Field', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }"
Method.Default,
new ExportData<Method> ("symbol", ArgumentSemantic.None, Method.Default),
"{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }"
];
yield return [
Field.Default,
new ExportData<Field> ("symbol"),
"{ Type: 'ObjCBindings.Field', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }"
Method.Default,
new ExportData<Method> ("symbol"),
"{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }"
];
yield return [
Property.Default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@ public class AttributesNamesTests {
[Theory]
[InlineData (StringComparison.Ordinal, null)]
[InlineData (EnumValue.Default, null)]
[InlineData (Field.Default, AttributesNames.ExportFieldAttribute)]
[InlineData (Property.Default, AttributesNames.ExportPropertyAttribute)]
[InlineData (Method.Default, AttributesNames.ExportMethodAttribute)]
public void GetExportAttributeName<T> (T @enum, string? expectedName) where T : Enum
{
Assert.NotNull (@enum);
Assert.Equal (expectedName, AttributesNames.GetExportAttributeName<T> ());
}

[Theory]
[InlineData (StringComparison.Ordinal, null)]
[InlineData (Method.Default, null)]
[InlineData (Property.Default, AttributesNames.FieldPropertyAttribute)]
[InlineData (EnumValue.Default, AttributesNames.EnumFieldAttribute)]
public void GetFieldAttributeName<T> (T @enum, string? expectedName) where T : Enum
{
Assert.NotNull (@enum);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public IEnumerator<object []> GetEnumerator ()
builder.Add (new SupportedOSPlatformData ("ios17.0"));
builder.Add (new SupportedOSPlatformData ("tvos17.0"));
builder.Add (new UnsupportedOSPlatformData ("macos"));

const string emptyClass = @"
using Foundation;
using ObjCRuntime;
Expand Down Expand Up @@ -535,7 +535,7 @@ namespace NS;
[BindingType]
public partial class MyClass {
[Export<Property> (""name"", Property.Notification)]
[Field<Property> (""name"", Property.Notification)]
public partial string Name { get; set; } = string.Empty;
}
";
Expand Down Expand Up @@ -565,7 +565,7 @@ public partial class MyClass {
returnType: new ("string", isReferenceType: true),
symbolAvailability: new (),
attributes: [
new ("ObjCBindings.ExportAttribute<ObjCBindings.Property>", ["name", "ObjCBindings.Property.Notification"])
new ("ObjCBindings.FieldAttribute<ObjCBindings.Property>", ["name", "ObjCBindings.Property.Notification"])
],
modifiers: [
SyntaxFactory.Token (SyntaxKind.PublicKeyword),
Expand All @@ -589,7 +589,7 @@ public partial class MyClass {
]
) {

ExportPropertyData = new ("name", ArgumentSemantic.None, Property.Notification)
ExportFieldData = new (symbolName: "name", flags: Property.Notification)
}
]
}
Expand All @@ -602,7 +602,7 @@ namespace NS;
[BindingType]
public partial class MyClass {
[Export<Field> (""CONSTANT"")]
[Field<Property> (""CONSTANT"")]
public static partial string Name { get; set; } = string.Empty;
}
";
Expand Down Expand Up @@ -632,7 +632,7 @@ public partial class MyClass {
returnType: new ("string", isReferenceType: true),
symbolAvailability: new (),
attributes: [
new ("ObjCBindings.ExportAttribute<ObjCBindings.Field>", ["CONSTANT"])
new ("ObjCBindings.FieldAttribute<ObjCBindings.Property>", ["CONSTANT"])
],
modifiers: [
SyntaxFactory.Token (SyntaxKind.PublicKeyword),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public class TestClass {
";
yield return [wrongAttributeInProperty, true];

const string fieldAttributeInProperty = @"
const string exportFieldAttributeInProperty = @"
using System;
using Foundation;
using ObjCRuntime;
Expand All @@ -134,6 +134,20 @@ public class TestClass {
[Export<Field> (""name"")]
public partial string Name { get;set; }
}
";
yield return [exportFieldAttributeInProperty, true];

const string fieldAttributeInProperty = @"
using System;
using Foundation;
using ObjCRuntime;
using ObjCBindings;
[BindingType]
public class TestClass {
[Field<Property> (""name"")]
public partial string Name { get;set; }
}
";
yield return [fieldAttributeInProperty, false];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.DataModel;
using ObjCBindings;
using ObjCRuntime;
using Xamarin.Tests;
using Xamarin.Utils;
using Xunit;
Expand Down Expand Up @@ -295,7 +294,7 @@ namespace NS;
[BindingType]
public partial interface IProtocol {
[Export<Property> (""name"", Property.Notification)]
[Field<Property> (""name"", Property.Notification)]
public partial string Name { get; set; } = string.Empty;
}
";
Expand Down Expand Up @@ -323,7 +322,7 @@ public partial interface IProtocol {
returnType: new ("string", isReferenceType: true),
symbolAvailability: new (),
attributes: [
new ("ObjCBindings.ExportAttribute<ObjCBindings.Property>", ["name", "ObjCBindings.Property.Notification"])
new ("ObjCBindings.FieldAttribute<ObjCBindings.Property>", ["name", "ObjCBindings.Property.Notification"])
],
modifiers: [
SyntaxFactory.Token (SyntaxKind.PublicKeyword),
Expand All @@ -346,7 +345,7 @@ public partial interface IProtocol {
),
]
) {
ExportPropertyData = new ("name", ArgumentSemantic.None, Property.Notification)
ExportFieldData= new ("name", Property.Notification)
}
]
}
Expand Down
Loading

0 comments on commit ab71bee

Please sign in to comment.