Skip to content

Commit

Permalink
[Rgen] Add skeleton code for the field properties in a class. (#21993)
Browse files Browse the repository at this point in the history
Add code that starts generating field properties. This commits loops
over the field properties and emit the following code:

* Auto generated attributes for the properties.
* SupportedOS attributes for properties.
* SupportedOS attributes for accessors.
* Acessors with no implementation.
* Advice attribute for notification fields.

The code also keeps track of the notifications to later allow to
generate the helper clases.

---------

Co-authored-by: GitHub Actions Autoformatter <[email protected]>
Co-authored-by: Rolf Bjarne Kvinge <[email protected]>
  • Loading branch information
3 people authored Jan 18, 2025
1 parent a9225f2 commit 8ce51e8
Show file tree
Hide file tree
Showing 11 changed files with 990 additions and 10 deletions.
10 changes: 10 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ public bool IsNotification
/// </summary>
public ImmutableArray<Accessor> Accessors { get; } = [];

public Accessor? GetAccessor (AccessorKind accessorKind)
{
// careful, do not use FirstOrDefault from LINQ because we are using structs!
foreach (var accessor in Accessors) {
if (accessor.Kind == accessorKind)
return accessor;
}
return null;
}

internal Property (string name, TypeInfo returnType,
SymbolAvailability symbolAvailability,
ImmutableArray<AttributeCodeChange> attributes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Macios.Generator.DataModel;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Microsoft.Macios.Generator.Emitters;
Expand Down Expand Up @@ -84,4 +85,33 @@ static CompilationUnitSyntax ThrowException (string type, string message)

static CompilationUnitSyntax ThrowNotSupportedException (string message)
=> ThrowException (type: "NotSupportedException", message: message);

/// <summary>
/// Generates the syntax to declare the variable used by a field property.
/// </summary>
/// <param name="property">The field property whose backing variable we want to generate.</param>
/// <returns>The variable declaration syntax.</returns>
public static CompilationUnitSyntax FieldPropertyBackingVariable (in Property property)
{
var variableType = property.ReturnType.Name;
if (property.ReturnType.SpecialType is SpecialType.System_IntPtr or SpecialType.System_UIntPtr
&& property.ReturnType.MetadataName is not null) {
variableType = property.ReturnType.MetadataName;
}
var compilationUnit = CompilationUnit ().WithMembers (
SingletonList<MemberDeclarationSyntax> (
FieldDeclaration (
VariableDeclaration (
property.IsReferenceType // nullable only for reference types
? NullableType (IdentifierName (variableType))
: IdentifierName (variableType)
)
.WithVariables (
SingletonSeparatedList (
VariableDeclarator (
Identifier (property.BackingField)))))
.WithModifiers (TokenList (Token (SyntaxKind.StaticKeyword))))) // fields are static variables
.NormalizeWhitespace ();
return compilationUnit;
}
}
80 changes: 78 additions & 2 deletions src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.Context;
using Microsoft.Macios.Generator.DataModel;
using Microsoft.Macios.Generator.Formatters;
using ObjCBindings;
using Property = Microsoft.Macios.Generator.DataModel.Property;
using static Microsoft.Macios.Generator.Emitters.BindingSyntaxFactory;

namespace Microsoft.Macios.Generator.Emitters;

Expand Down Expand Up @@ -58,6 +61,74 @@ void EmitDefaultConstructors (in BindingContext bindingContext, TabbedStringBuil
classBlock.AppendLine ($"protected internal {bindingContext.Changes.Name} (NativeHandle handle) : base (handle) {{}}");
}

/// <summary>
/// Emit the code for all the field properties in the class. The code will add any necessary backing fields and
/// will return all properties that are notifications.
/// </summary>
/// <param name="className">The current class name.</param>
/// <param name="properties">All properties of the class, the method will filter those that are fields.</param>
/// <param name="classBlock">Current class block.</param>
/// <param name="notificationProperties">An immutable array with all the properties that are marked as notifications
/// and that need a helper class to be generated.</param>
void EmitFields (string className, in ImmutableArray<Property> properties, TabbedStringBuilder classBlock,
out ImmutableArray<Property> notificationProperties)
{
var notificationsBuilder = ImmutableArray.CreateBuilder<Property> ();
foreach (var property in properties.OrderBy (p => p.Name)) {
if (!property.IsField)
continue;

classBlock.AppendLine ();
// a field should always have a getter, if it does not, we do not generate the property
var getter = property.GetAccessor (AccessorKind.Getter);
if (getter is null)
continue;

// provide a backing variable for the property if and only if we are dealing with a reference type
if (property.IsReferenceType) {
classBlock.AppendLine (FieldPropertyBackingVariable (property).ToString ());
}

classBlock.AppendLine ();
classBlock.AppendMemberAvailability (property.SymbolAvailability);
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
if (property.IsNotification) {
// add it to the bucket so that we can later generate the necessary partial class for the
// notifications
notificationsBuilder.Add (property);
classBlock.AppendNotificationAdvice (className, property.Name);
}

using (var propertyBlock = classBlock.CreateBlock (property.ToDeclaration ().ToString (), block: true)) {
// generate the accessors, we will always have a get, a set is optional depending on the type
// if the symbol availability of the accessor is different of the one from the property, write it

// be very verbose with the availability, makes the life easier to the dotnet analyzer
propertyBlock.AppendMemberAvailability (getter.Value.SymbolAvailability);
using (var getterBlock = propertyBlock.CreateBlock ("get", block: true)) {
getterBlock.AppendLine ("throw new NotImplementedException ();");
}

var setter = property.GetAccessor (AccessorKind.Setter);
if (setter is null)
// we are done with the current property
continue;

propertyBlock.AppendLine (); // add space between getter and setter since we have the attrs
propertyBlock.AppendMemberAvailability (setter.Value.SymbolAvailability);
using (var setterBlock = propertyBlock.CreateBlock ("set", block: true)) {
setterBlock.AppendLine ("throw new NotImplementedException ();");
}
}
}
notificationProperties = notificationsBuilder.ToImmutable ();
}

void EmitNotifications (in ImmutableArray<Property> properties, TabbedStringBuilder classBlock)
{
// to be implemented, do not throw or tests will fail.
}

public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out ImmutableArray<Diagnostic>? diagnostics)
{
diagnostics = null;
Expand All @@ -77,7 +148,7 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out

// register the class only if we are not dealing with a static class
var bindingData = (BindingTypeData<Class>) bindingContext.Changes.BindingInfo;
// registration depends on the class name. If the binding data contains a name, we use that one, else
// Registration depends on the class name. If the binding data contains a name, we use that one, else
// we use the name of the class
var registrationName = bindingData.Name ?? bindingContext.Changes.Name;

Expand All @@ -98,6 +169,11 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out
disableDefaultCtor: bindingData.Flags.HasFlag (Class.DisableDefaultCtor));
}

EmitFields (bindingContext.Changes.Name, bindingContext.Changes.Properties, classBlock,
out var notificationProperties);

// emit the notification helper classes, leave this for the very bottom of the class
EmitNotifications (notificationProperties, classBlock);
classBlock.AppendLine ("// TODO: add binding code here");
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@
namespace Microsoft.Macios.Generator.Formatters;

static class PropertyFormatter {

/// <summary>
/// Return the declaration represented by the given property.
/// </summary>
/// <param name="property">The property whose declaration we want to generate.</param>
/// <returns>A compilation unit syntax node with the declaration of the property.</returns>
public static CompilationUnitSyntax? ToDeclaration (this in Property? property)
public static CompilationUnitSyntax ToDeclaration (this in Property property)
{
if (property is null)
return null;

var compilationUnit = CompilationUnit ().WithMembers (
SingletonList<MemberDeclarationSyntax> (
PropertyDeclaration (
type: property.Value.ReturnType.GetIdentifierSyntax (),
identifier: Identifier (property.Value.Name))
.WithModifiers (TokenList (property.Value.Modifiers)))).NormalizeWhitespace ();
type: property.ReturnType.GetIdentifierSyntax (),
identifier: Identifier (property.Name))
.WithModifiers (TokenList (property.Modifiers)))).NormalizeWhitespace ();
return compilationUnit;
}

/// <summary>
/// Return the declaration represented by the given property.
/// </summary>
/// <param name="property">The property whose declaration we want to generate.</param>
/// <returns>A compilation unit syntax node with the declaration of the property.</returns>
public static CompilationUnitSyntax? ToDeclaration (this in Property? property)
=> property?.ToDeclaration ();
}
21 changes: 21 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.Availability;
using Microsoft.Macios.Generator.DataModel;
using ObjCRuntime;
using Xamarin.Utils;

namespace Microsoft.Macios.Generator;
Expand Down Expand Up @@ -227,6 +229,25 @@ public TabbedStringBuilder AppendMemberAvailability (in SymbolAvailability allPl
return this;
}

public TabbedStringBuilder AppendExportData<T> (in ExportData<T> exportData) where T : Enum
{
// Try to write the smaller amount of data. We are using the old ExportAttribute until we make the final move
if (exportData.ArgumentSemantic != ArgumentSemantic.None) {
AppendLine ($"[Export (\"{exportData.Selector}\", ArgumentSemantic.{exportData.ArgumentSemantic})]");
} else {
AppendLine ($"[Export (\"{exportData.Selector}\")]");
}
return this;
}

public TabbedStringBuilder AppendNotificationAdvice (in string className, in string notification)
{
string attr =
$"[Advice (\"Use '{className}.Notifications.{notification}' helper method instead.\")]";
AppendLine (attr);
return this;
}

/// <summary>
/// Append a EditorBrowsable attribute. Added for convenience.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class TestDataGenerator : BaseTestDataGenerator, IEnumerable<object []> {
(ApplePlatform.MacOSX, "AVAudioPcmBuffer", "AVAudioPcmBufferDefaultCtr.cs", "ExpectedAVAudioPcmBufferDefaultCtr.cs", null),
(ApplePlatform.iOS, "AVAudioPcmBuffer", "AVAudioPcmBufferNoNativeName.cs", "ExpectedAVAudioPcmBufferNoNativeName.cs", null),
(ApplePlatform.MacOSX, "AVAudioPcmBuffer", "AVAudioPcmBufferNoNativeName.cs", "ExpectedAVAudioPcmBufferNoNativeName.cs", null),
(ApplePlatform.iOS, "CIImage", "CIImage.cs", "ExpectedCIImage.cs", null),
(ApplePlatform.TVOS, "CIImage", "CIImage.cs", "ExpectedCIImage.cs", null),
(ApplePlatform.MacCatalyst, "CIImage", "CIImage.cs", "ExpectedCIImage.cs", null),

};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Runtime.Versioning;
using Foundation;
using ObjCBindings;

namespace TestNamespace;

[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("ios11.0")]
[SupportedOSPlatform ("tvos11.0")]

[BindingType<Class> ()]
public partial class CIImage {

[SupportedOSPlatform ("maccatalyst13.1")]
[Field<Property> ("FormatRGBA16Int")]
public static partial int FormatRGBA16Int { get; }

[SupportedOSPlatform ("maccatalyst13.1")]
[Field<Property> ("kCIFormatABGR8")]
public static partial int FormatABGR8 { get; }

[SupportedOSPlatform ("maccatalyst13.1")]
[Field<Property> ("kCIFormatLA8")]
public static partial int FormatLA8 {
get;

[SupportedOSPlatform ("ios17.0")]
[SupportedOSPlatform ("tvos17.0")]
[SupportedOSPlatform ("macos14.0")]
[SupportedOSPlatform ("maccatalyst17.0")]
set;
}

[SupportedOSPlatform ("maccatalyst13.1")]
[Field<Property> ("kCIFormatLA8", Flags = Property.Notification)]
public static partial NSString DidProcessEditingNotification { get; }

}
Loading

0 comments on commit 8ce51e8

Please sign in to comment.