Skip to content

Commit

Permalink
[Rgen] Add properties in the Property data model to decide if a field…
Browse files Browse the repository at this point in the history
… is needed. (#22078)

Add some new properties, to be only used by the generator, in the
property data model to decide the inclusion of a backing field and if
this field needs to be checked.

We have moved the ToString method to the .Generator and .Transformer
files so that we can reflect the two new properties. The same has
happened for the Equals method.
  • Loading branch information
mandel-macaque authored Jan 30, 2025
1 parent 5bb740e commit f501981
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 22 deletions.
7 changes: 7 additions & 0 deletions src/ObjCBindings/ExportTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ public enum Property : Int64 {
/// </summary>
IsThreadSafe = 1 << 7,

/// <summary>
/// If this falgs is applied to a property, we do not generate a
/// backing field. See bugzilla #3359 and Assistly 7032 for some
/// background information
/// </summary>
Transient = 1 << 8,

}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.Context;
using Microsoft.Macios.Generator.Extensions;
using ObjCRuntime;

namespace Microsoft.Macios.Generator.DataModel;

Expand Down Expand Up @@ -50,6 +53,50 @@ public bool IsNotification
public bool MarshalNativeExceptions
=> IsProperty && ExportPropertyData.Value.Flags.HasFlag (ObjCBindings.Property.MarshalNativeExceptions);

/// <summary>
/// True if the property should be generated without a backing field.
/// </summary>
public bool IsTransient => IsProperty && ExportPropertyData.Value.Flags.HasFlag (ObjCBindings.Property.Transient);

readonly bool? needsBackingField = null;
/// <summary>
/// States if the property, when generated, needs a backing field.
/// </summary>
public bool NeedsBackingField {
get {
if (needsBackingField is not null)
return needsBackingField.Value;
var isWrapped = ReturnType.IsWrapped ||
ReturnType is { IsArray: true, ArrayElementTypeIsWrapped: true };
return isWrapped && !IsTransient;
}
// Added to allow testing. This way we can set the correct expectation in the test factory
init => needsBackingField = value;
}

readonly bool? requiresDirtyCheck = null;
/// <summary>
/// States if the property, when generated, should have a dirty check.
/// </summary>
public bool RequiresDirtyCheck {
get {
if (requiresDirtyCheck is not null)
return requiresDirtyCheck.Value;
if (!IsProperty)
return false;
switch (ExportPropertyData.Value.ArgumentSemantic) {
case ArgumentSemantic.Copy:
case ArgumentSemantic.Retain:
case ArgumentSemantic.None:
return NeedsBackingField;
default:
return false;
}
}
// Added to allow testing. This way we can set the correct expectation in the test factory
init => requiresDirtyCheck = value;
}

static FieldInfo<ObjCBindings.Property>? GetFieldInfo (RootBindingContext context, IPropertySymbol propertySymbol)
{
// grab the last port of the namespace
Expand Down Expand Up @@ -122,4 +169,34 @@ public static bool TryCreate (PropertyDeclarationSyntax declaration, RootBinding
};
return true;
}

/// <inheritdoc />
public bool Equals (Property other)
{
if (!CoreEquals (other))
return false;
if (IsTransient != other.IsTransient)
return false;
if (NeedsBackingField != other.NeedsBackingField)
return false;
return RequiresDirtyCheck == other.RequiresDirtyCheck;
}

/// <inheritdoc />
public override string ToString ()
{
var sb = new StringBuilder (
$"Name: '{Name}', Type: {ReturnType}, Supported Platforms: {SymbolAvailability}, ExportFieldData: '{ExportFieldData?.ToString () ?? "null"}', ExportPropertyData: '{ExportPropertyData?.ToString () ?? "null"}', ");
sb.Append ($"IsTransient: '{IsTransient}', ");
sb.Append ($"NeedsBackingField: '{NeedsBackingField}', ");
sb.Append ($"RequiresDirtyCheck: '{RequiresDirtyCheck}', ");
sb.Append ("Attributes: [");
sb.AppendJoin (",", Attributes);
sb.Append ("], Modifiers: [");
sb.AppendJoin (",", Modifiers.Select (x => x.Text));
sb.Append ("], Accessors: [");
sb.AppendJoin (",", Accessors);
sb.Append (']');
return sb.ToString ();
}
}
18 changes: 1 addition & 17 deletions src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// Licensed under the MIT License.
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.Macios.Generator.Availability;

Expand Down Expand Up @@ -96,8 +94,7 @@ internal Property (string name, TypeInfo returnType,
Accessors = accessors;
}

/// <inheritdoc />
public bool Equals (Property other)
bool CoreEquals (Property other)
{
// this could be a large && but ifs are more readable
if (Name != other.Name)
Expand Down Expand Up @@ -151,17 +148,4 @@ public override int GetHashCode ()
return !left.Equals (right);
}

/// <inheritdoc />
public override string ToString ()
{
var sb = new StringBuilder (
$"Name: '{Name}', Type: {ReturnType}, Supported Platforms: {SymbolAvailability}, ExportFieldData: '{ExportFieldData?.ToString () ?? "null"}', ExportPropertyData: '{ExportPropertyData?.ToString () ?? "null"}' Attributes: [");
sb.AppendJoin (",", Attributes);
sb.Append ("], Modifiers: [");
sb.AppendJoin (",", Modifiers.Select (x => x.Text));
sb.Append ("], Accessors: [");
sb.AppendJoin (",", Accessors);
sb.Append (']');
return sb.ToString ();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.Macios.Transformer.Attributes;

namespace Microsoft.Macios.Generator.DataModel;
Expand Down Expand Up @@ -36,4 +38,21 @@ readonly partial struct Property {
/// True if the method was exported with the MarshalNativeExceptions flag allowing it to support native exceptions.
/// </summary>
public bool MarshalNativeExceptions => throw new NotImplementedException ();

/// <inheritdoc />
public bool Equals (Property other) => Comparer.Equals (this, other);

/// <inheritdoc />
public override string ToString ()
{
var sb = new StringBuilder (
$"Name: '{Name}', Type: {ReturnType}, Supported Platforms: {SymbolAvailability}, ExportFieldData: '{ExportFieldData?.ToString () ?? "null"}', ExportPropertyData: '{ExportPropertyData?.ToString () ?? "null"}' Attributes: [");
sb.AppendJoin (",", Attributes);
sb.Append ("], Modifiers: [");
sb.AppendJoin (",", Modifiers.Select (x => x.Text));
sb.Append ("], Accessors: [");
sb.AppendJoin (",", Accessors);
sb.Append (']');
return sb.ToString ();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,88 @@ public static partial string Name {
),
])
];

const string nsObjectProperty = @"
using System;
using Foundation;
using ObjCBindings;
namespace Test;
public class TestClass {
[Export<Property>(""name"")]
public NSObject Name { get; }
}
";
yield return [
nsObjectProperty,
new Property (
name: "Name",
returnType: ReturnTypeForNSObject (),
symbolAvailability: new (),
attributes: [
new (name: "ObjCBindings.ExportAttribute<ObjCBindings.Property>", arguments: ["name"]),
],
modifiers: [
SyntaxFactory.Token (kind: SyntaxKind.PublicKeyword),
],
accessors: [
new (
accessorKind: AccessorKind.Getter,
symbolAvailability: new (),
exportPropertyData: null,
attributes: [],
modifiers: []
)
]
) {
NeedsBackingField = true,
RequiresDirtyCheck = true,
ExportPropertyData = new (selector: "name"),
}
];

const string nsObjectArrayProperty = @"
using System;
using Foundation;
using ObjCBindings;
namespace Test;
public class TestClass {
[Export<Property>(""name"")]
public NSObject[] Name { get; }
}
";
yield return [
nsObjectArrayProperty,
new Property (
name: "Name",
returnType: ReturnTypeForArray ("Foundation.NSObject"),
symbolAvailability: new (),
attributes: [
new (name: "ObjCBindings.ExportAttribute<ObjCBindings.Property>", arguments: ["name"]),
],
modifiers: [
SyntaxFactory.Token (kind: SyntaxKind.PublicKeyword),
],
accessors: [
new (
accessorKind: AccessorKind.Getter,
symbolAvailability: new (),
exportPropertyData: null,
attributes: [],
modifiers: []
)
]
) {
NeedsBackingField = true,
RequiresDirtyCheck = true,
ExportPropertyData = new (selector: "name"),
}
];
}

IEnumerator IEnumerable.GetEnumerator ()
Expand Down
17 changes: 12 additions & 5 deletions tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,16 +300,23 @@ public static TypeInfo ReturnTypeForDelegate (string delegateName)
]
};

public static TypeInfo ReturnTypeForNSObject (string nsObjectName, bool isNullable = false)
public static TypeInfo ReturnTypeForNSObject (string? nsObjectName = null, bool isNullable = false)
=> new (
name: nsObjectName,
name: nsObjectName ?? "Foundation.NSObject",
isNullable: isNullable,
isArray: false
isArray: false,
isReferenceType: true
) {
IsNSObject = true,
IsINativeObject = true,
Parents = ["Foundation.NSObject", "object"],
Interfaces = ["ObjCRuntime.INativeObject"]
Parents = nsObjectName is null ? ["object"] : ["Foundation.NSObject", "object"],
Interfaces = [
"ObjCRuntime.INativeObject",
$"System.IEquatable<{nsObjectName ?? "Foundation.NSObject"}>",
"System.IDisposable",
"Foundation.INSObjectFactory",
"Foundation.INSObjectProtocol"
]
};

public static TypeInfo ReturnTypeForINativeObject (string nativeObjectName, bool isNullable = false)
Expand Down

0 comments on commit f501981

Please sign in to comment.