diff --git a/src/ObjCBindings/ExportTag.cs b/src/ObjCBindings/ExportTag.cs index 36267814ba7..83b909c6d33 100644 --- a/src/ObjCBindings/ExportTag.cs +++ b/src/ObjCBindings/ExportTag.cs @@ -127,6 +127,13 @@ public enum Property : Int64 { /// IsThreadSafe = 1 << 7, + /// + /// 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 + /// + Transient = 1 << 8, + } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs index 74f39100914..a18c630d26d 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs @@ -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; @@ -50,6 +53,50 @@ public bool IsNotification public bool MarshalNativeExceptions => IsProperty && ExportPropertyData.Value.Flags.HasFlag (ObjCBindings.Property.MarshalNativeExceptions); + /// + /// True if the property should be generated without a backing field. + /// + public bool IsTransient => IsProperty && ExportPropertyData.Value.Flags.HasFlag (ObjCBindings.Property.Transient); + + readonly bool? needsBackingField = null; + /// + /// States if the property, when generated, needs a backing field. + /// + 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; + /// + /// States if the property, when generated, should have a dirty check. + /// + 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? GetFieldInfo (RootBindingContext context, IPropertySymbol propertySymbol) { // grab the last port of the namespace @@ -122,4 +169,34 @@ public static bool TryCreate (PropertyDeclarationSyntax declaration, RootBinding }; return true; } + + /// + 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; + } + + /// + 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 (); + } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs index 1b8f31ec340..e9799dd974d 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs @@ -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; @@ -96,8 +94,7 @@ internal Property (string name, TypeInfo returnType, Accessors = accessors; } - /// - public bool Equals (Property other) + bool CoreEquals (Property other) { // this could be a large && but ifs are more readable if (Name != other.Name) @@ -151,17 +148,4 @@ public override int GetHashCode () return !left.Equals (right); } - /// - 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 (); - } } diff --git a/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs index 682ca922622..a876c858c67 100644 --- a/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs @@ -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; @@ -36,4 +38,21 @@ readonly partial struct Property { /// True if the method was exported with the MarshalNativeExceptions flag allowing it to support native exceptions. /// public bool MarshalNativeExceptions => throw new NotImplementedException (); + + /// + public bool Equals (Property other) => Comparer.Equals (this, other); + + /// + 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 (); + } } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/PropertyTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/PropertyTests.cs index 41666d793dc..c8200bd726b 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/PropertyTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/PropertyTests.cs @@ -1248,6 +1248,88 @@ public static partial string Name { ), ]) ]; + + const string nsObjectProperty = @" +using System; +using Foundation; +using ObjCBindings; + +namespace Test; + +public class TestClass { + + [Export(""name"")] + public NSObject Name { get; } +} +"; + yield return [ + nsObjectProperty, + new Property ( + name: "Name", + returnType: ReturnTypeForNSObject (), + symbolAvailability: new (), + attributes: [ + new (name: "ObjCBindings.ExportAttribute", 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(""name"")] + public NSObject[] Name { get; } +} +"; + yield return [ + nsObjectArrayProperty, + new Property ( + name: "Name", + returnType: ReturnTypeForArray ("Foundation.NSObject"), + symbolAvailability: new (), + attributes: [ + new (name: "ObjCBindings.ExportAttribute", 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 () diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs index 965d6981530..8dc85bd8e67 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs @@ -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)