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 properties in the Property data model to decide if a field is needed. #22078

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
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