Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rgen] Add skeleton code for the field properties in a class. #21993

Merged
merged 6 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ware 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
Loading