diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.Generator.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.Generator.cs index 9462f7a4fe8..4e3002f50ae 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.Generator.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.Generator.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System.Collections.Immutable; +using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.Macios.Generator.Attributes; using Microsoft.Macios.Generator.Availability; @@ -15,6 +17,9 @@ readonly partial struct Accessor { /// public ExportData? ExportPropertyData { get; init; } + /// + /// State if we should marshal native exceptions when generating the accessor. + /// public bool MarshalNativeExceptions => ExportPropertyData is not null && ExportPropertyData.Value.Flags.HasFlag (ObjCBindings.Property.MarshalNativeExceptions); @@ -72,4 +77,16 @@ public Accessor (AccessorKind accessorKind, public bool ShouldMarshalNativeExceptions (in Property property) => MarshalNativeExceptions || property.MarshalNativeExceptions; + /// + public override string ToString () + { + var sb = new StringBuilder ($"{{ Kind: {Kind}, "); + sb.Append ($"Supported Platforms: {SymbolAvailability}, "); + sb.Append ($"ExportData: {ExportPropertyData?.ToString () ?? "null"} Modifiers: ["); + sb.AppendJoin (",", Modifiers.Select (x => x.Text)); + sb.Append ("], Attributes: ["); + sb.AppendJoin (", ", Attributes); + sb.Append ("] }"); + return sb.ToString (); + } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.cs index bc4c0731bef..2fb8e0154a2 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Accessor.cs @@ -25,7 +25,7 @@ namespace Microsoft.Macios.Generator.DataModel; /// /// List of attribute code changes of the accessor. /// - public ImmutableArray Attributes { get; } + public ImmutableArray Attributes { get; } = []; /// /// List of modifiers of the accessor. @@ -70,15 +70,4 @@ public override int GetHashCode () { return !left.Equals (right); } - - /// - public override string ToString () - { - var sb = new StringBuilder ($"{{ Kind: {Kind}, Supported Platforms: {SymbolAvailability}, ExportData: {ExportPropertyData?.ToString () ?? "null"} Modifiers: ["); - sb.AppendJoin (",", Modifiers.Select (x => x.Text)); - sb.Append ("], Attributes: ["); - sb.AppendJoin (", ", Attributes); - sb.Append ("] }"); - return sb.ToString (); - } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs index abdbad852aa..3667343c2d8 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Macios.Generator.Attributes; +using Microsoft.Macios.Generator.Availability; using Microsoft.Macios.Generator.Context; using Microsoft.Macios.Generator.Extensions; using ObjCRuntime; @@ -27,6 +28,9 @@ readonly partial struct Property { [MemberNotNullWhen (true, nameof (ExportFieldData))] public bool IsField => ExportFieldData is not null; + /// + /// Returns if the field was marked as a notification. + /// public bool IsNotification => IsField && ExportFieldData.Value.FieldData.Flags.HasFlag (ObjCBindings.Property.Notification); @@ -116,6 +120,21 @@ public bool RequiresDirtyCheck { return fieldInfo; } + internal Property (string name, TypeInfo returnType, + SymbolAvailability symbolAvailability, + ImmutableArray attributes, + ImmutableArray modifiers, + ImmutableArray accessors) + { + Name = name; + BackingField = $"_{Name}"; + ReturnType = returnType; + SymbolAvailability = symbolAvailability; + Attributes = attributes; + Modifiers = modifiers; + Accessors = accessors; + } + public static bool TryCreate (PropertyDeclarationSyntax declaration, RootBindingContext context, [NotNullWhen (true)] out Property? change) { diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs index 0893e053db7..bf8811f51c6 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs @@ -61,7 +61,7 @@ private init { /// /// Get the modifiers of the property. /// - public ImmutableArray Modifiers { get; } = []; + public ImmutableArray Modifiers { get; init; } = []; /// /// Get the list of accessor changes of the property. @@ -80,20 +80,6 @@ private init { return null; } - internal Property (string name, TypeInfo returnType, - SymbolAvailability symbolAvailability, - ImmutableArray attributes, - ImmutableArray modifiers, ImmutableArray accessors) - { - Name = name; - BackingField = $"_{Name}"; - ReturnType = returnType; - SymbolAvailability = symbolAvailability; - Attributes = attributes; - Modifiers = modifiers; - Accessors = accessors; - } - bool CoreEquals (Property other) { // this could be a large && but ifs are more readable diff --git a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs index 0472fa7dec1..19113fa531b 100644 --- a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs @@ -302,7 +302,7 @@ static class AttributesNames { /// When this attribute is applied to a class it will just generate a static class, one that does not derive /// from NSObject. /// - [BindingFlag (AttributeTargets.Class | AttributeTargets.Method)] + [BindingFlag (AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)] public const string StaticAttribute = "StaticAttribute"; /// diff --git a/src/rgen/Microsoft.Macios.Transformer/DataModel/Accessor.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/DataModel/Accessor.Transformer.cs index 37246704577..2d1ce21acc9 100644 --- a/src/rgen/Microsoft.Macios.Transformer/DataModel/Accessor.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/DataModel/Accessor.Transformer.cs @@ -1,13 +1,50 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Availability; using Microsoft.Macios.Transformer.Attributes; namespace Microsoft.Macios.Generator.DataModel; readonly partial struct Accessor { - public ExportData? ExportPropertyData { get; init; } + readonly ExportData? overrideExportData; - public Accessor () { } + /// + /// The data of the field attribute used to mark the value as a property binding. + /// + public ExportData? ExportPropertyData { + get => overrideExportData ?? ExportAttribute; + init => overrideExportData = value; + } + + /// + /// State if we should marshal native exceptions when generating the accessor. + /// + public bool MarshalNativeExceptions => HasMarshalNativeExceptionsFlag; + + public Accessor (AccessorKind accessorKind, + SymbolAvailability symbolAvailability, + Dictionary> attributes) + { + Kind = accessorKind; + SymbolAvailability = symbolAvailability; + AttributesDictionary = attributes; + + // we trust the modifiers of the property itself + Modifiers = []; + } + + /// + public override string ToString () + { + var sb = new StringBuilder ($"{{ Kind: {Kind}, "); + sb.Append ($"Supported Platforms: {SymbolAvailability}, "); + sb.Append ($"ExportData: {ExportPropertyData?.ToString () ?? "null"} Modifiers: ["); + sb.AppendJoin (",", Modifiers.Select (x => x.Text)); + sb.Append ("] }"); + return sb.ToString (); + } } diff --git a/src/rgen/Microsoft.Macios.Transformer/DataModel/Method.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/DataModel/Method.Transformer.cs index 007bb3fbcda..437032a88b4 100644 --- a/src/rgen/Microsoft.Macios.Transformer/DataModel/Method.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/DataModel/Method.Transformer.cs @@ -21,9 +21,7 @@ readonly partial struct Method { /// The data of the export attribute used to mark the value as a property binding. /// public ExportData? ExportMethodData { - get { - return overrideExportData ?? ExportAttribute; - } + get => overrideExportData ?? ExportAttribute; init => overrideExportData = value; } @@ -50,69 +48,10 @@ public Method (string type, ReturnType = returnType; SymbolAvailability = symbolAvailability; Parameters = parameters; - -#pragma warning disable format - // Modifiers are special because we might be dealing with several flags that the user has set in the method. - // We have to add the partial keyword so that we can have the partial implementation of the method later generated - // by the roslyn code generator - Modifiers = this switch { - // internal static partial - { HasNewFlag: false, HasStaticFlag: true, HasInternalFlag: true } - => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], - - // public static partial - { HasNewFlag: false, HasStaticFlag: true, HasInternalFlag: false } - => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], - - // internal new static partial - { HasNewFlag: true, HasStaticFlag: true, HasInternalFlag: true} - => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], - - // public new static partial - { HasNewFlag: true, HasStaticFlag: true, HasInternalFlag: false } - => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], - - // public new virtual partial - { HasNewFlag: true, HasStaticFlag: false, HasAbstractFlag: false, HasInternalFlag: false } - => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)], - - // internal new virtual partial - { HasNewFlag: true, HasStaticFlag: false, HasAbstractFlag: false, HasInternalFlag: true } - => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)], - - // public new abstract - { HasNewFlag: true, HasAbstractFlag: true, HasInternalFlag: false} - => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.AbstractKeyword)], - - // internal new abstract - { HasNewFlag: true, HasAbstractFlag: true, HasInternalFlag: true} - => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.AbstractKeyword)], - - // public override partial - { HasNewFlag: false, HasOverrideFlag: true, HasInternalFlag: false} - => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.OverrideKeyword), Token (SyntaxKind.PartialKeyword)], - - // internal override partial - { HasNewFlag: false, HasOverrideFlag: true, HasInternalFlag: true} - => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.OverrideKeyword), Token (SyntaxKind.PartialKeyword)], - - // public abstract - { HasAbstractFlag: true, HasInternalFlag: false} - => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.AbstractKeyword)], - - // internal abstract - { HasAbstractFlag: true, HasInternalFlag: true} - => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.AbstractKeyword)], - - // general case, but internal - { HasInternalFlag: true} => - [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.VirtualKeyword)], - - // general case - _ => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)] - }; -#pragma warning restore format + // create a helper struct to retrieve the modifiers + var flags = new ModifiersFlags (HasAbstractFlag, HasInternalFlag, HasNewFlag, HasOverrideFlag, HasStaticFlag); + Modifiers = flags.ToModifiersArray (); } public static bool TryCreate (MethodDeclarationSyntax declaration, SemanticModel semanticModel, diff --git a/src/rgen/Microsoft.Macios.Transformer/DataModel/ModifiersFlags.cs b/src/rgen/Microsoft.Macios.Transformer/DataModel/ModifiersFlags.cs new file mode 100644 index 00000000000..0d09de4b2e6 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/DataModel/ModifiersFlags.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Microsoft.Macios.Generator.DataModel; + +/// +/// Helper class to store the modifiers flags. It also provides a method that will +/// return the correct modifiers according to the flags; +/// +readonly record struct ModifiersFlags { + + public bool HasAbstractFlag { get; } + public bool HasInternalFlag { get; } + public bool HasNewFlag { get; } + public bool HasOverrideFlag { get; } + public bool HasStaticFlag { get; } + + /// + /// Create a new structure with the provided flags. + /// + /// The state of the abstract flag. + /// The state of the internal flag. + /// The state of the new flag. + /// The state of the override flag. + /// The state of the static flag. + public ModifiersFlags (bool hasAbstractFlag, bool hasInternalFlag, bool hasNewFlag, bool hasOverrideFlag, bool hasStaticFlag) + { + HasAbstractFlag = hasAbstractFlag; + HasInternalFlag = hasInternalFlag; + HasNewFlag = hasNewFlag; + HasOverrideFlag = hasOverrideFlag; + HasStaticFlag = hasStaticFlag; + } + + /// + /// Returns the list of modifiers to be used with the provided set of flags. + /// + /// The list of modifiers to use to write the transformed method/property. + public ImmutableArray ToModifiersArray () + { +#pragma warning disable format + // Modifiers are special because we might be dealing with several flags that the user has set in the method. + // We have to add the partial keyword so that we can have the partial implementation of the method later generated + // by the roslyn code generator + return this switch { + // internal static partial + { HasNewFlag: false, HasStaticFlag: true, HasInternalFlag: true } + => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + + // public static partial + { HasNewFlag: false, HasStaticFlag: true, HasInternalFlag: false } + => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + + // internal new static partial + { HasNewFlag: true, HasStaticFlag: true, HasInternalFlag: true} + => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + + // public new static partial + { HasNewFlag: true, HasStaticFlag: true, HasInternalFlag: false } + => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + + // public new virtual partial + { HasNewFlag: true, HasStaticFlag: false, HasAbstractFlag: false, HasInternalFlag: false } + => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)], + + // internal new virtual partial + { HasNewFlag: true, HasStaticFlag: false, HasAbstractFlag: false, HasInternalFlag: true } + => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)], + + // public new abstract + { HasNewFlag: true, HasAbstractFlag: true, HasInternalFlag: false} + => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.AbstractKeyword)], + + // internal new abstract + { HasNewFlag: true, HasAbstractFlag: true, HasInternalFlag: true} + => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.NewKeyword), Token (SyntaxKind.AbstractKeyword)], + + // public override partial + { HasNewFlag: false, HasOverrideFlag: true, HasInternalFlag: false} + => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.OverrideKeyword), Token (SyntaxKind.PartialKeyword)], + + // internal override partial + { HasNewFlag: false, HasOverrideFlag: true, HasInternalFlag: true} + => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.OverrideKeyword), Token (SyntaxKind.PartialKeyword)], + + // public abstract + { HasAbstractFlag: true, HasInternalFlag: false} + => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.AbstractKeyword)], + + // internal abstract + { HasAbstractFlag: true, HasInternalFlag: true} + => [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.AbstractKeyword)], + + // general case, but internal + { HasInternalFlag: true} => + [Token (SyntaxKind.InternalKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.VirtualKeyword)], + + // general case + _ => [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)] + }; +#pragma warning restore format + } + +} diff --git a/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs index 3ae8ec06bc7..380cffbb1fb 100644 --- a/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/DataModel/Property.Transformer.cs @@ -1,19 +1,30 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Availability; +using Microsoft.Macios.Generator.Extensions; using Microsoft.Macios.Transformer.Attributes; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.Macios.Generator.DataModel; readonly partial struct Property { + readonly FieldData? overrideFieldData; + /// /// The data of the field attribute used to mark the value as a field binding. /// - public FieldData? ExportFieldData { get; init; } + public FieldData? ExportFieldData { + get => overrideFieldData ?? FieldAttribute; + init => overrideFieldData = value; + } /// /// True if the property represents a Objc field. @@ -21,12 +32,21 @@ readonly partial struct Property { [MemberNotNullWhen (true, nameof (ExportFieldData))] public bool IsField => ExportFieldData is not null; - public bool IsNotification => throw new NotImplementedException (); + /// + /// Returns if the field was marked as a notification. + /// + public bool IsNotification => HasNotificationAttribute; + + + readonly ExportData? overrideExportData; /// /// The data of the field attribute used to mark the value as a property binding. /// - public ExportData? ExportPropertyData { get; init; } + public ExportData? ExportPropertyData { + get => overrideExportData ?? ExportAttribute; + init => overrideExportData = value; + } /// /// True if the property represents a Objc property. @@ -37,7 +57,12 @@ 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 MarshalNativeExceptions => HasMarshalNativeExceptionsFlag; + + /// + /// True if the property should be generated without a backing field. + /// + public bool IsTransient => IsProperty && HasTransientFlag; /// /// Returns the bind from data if present in the binding. @@ -45,7 +70,73 @@ readonly partial struct Property { public BindAsData? BindAs => BindAsAttribute; /// - public bool Equals (Property other) => Comparer.Equals (this, other); + public bool Equals (Property other) => CoreEquals (other); + + internal Property (string name, + TypeInfo returnType, + SymbolAvailability symbolAvailability, + Dictionary> attributes, + ImmutableArray accessors) + { + Name = name; + BackingField = $"_{Name}"; + ReturnType = returnType; + SymbolAvailability = symbolAvailability; + AttributesDictionary = attributes; + Accessors = accessors; + + // the modifiers depend in we are talking about a field or a property. If we are talking + // about a field, we always have the same modifiers, public static. With a property we need to + // respect the flags that are set in the attributes. + if (IsField) { + // public static partial + Modifiers = [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)]; + } else { + // create a helper struct to retrieve the modifiers + var flags = new ModifiersFlags (HasAbstractFlag, HasInternalFlag, HasNewFlag, HasOverrideFlag, HasStaticFlag); + Modifiers = flags.ToModifiersArray (); + } + } + + public static bool TryCreate (PropertyDeclarationSyntax declaration, SemanticModel semanticModel, + [NotNullWhen (true)] out Property? property) + { + var memberName = declaration.Identifier.ToFullString ().Trim (); + // get the symbol from the property declaration + if (semanticModel.GetDeclaredSymbol (declaration) is not IPropertySymbol propertySymbol) { + property = null; + return false; + } + + var propertySupportedPlatforms = propertySymbol.GetAvailabilityForSymbol (); + ImmutableArray accessors = []; + if (declaration.AccessorList is not null && declaration.AccessorList.Accessors.Count > 0) { + // calculate any possible changes in the accessors of the property + var accessorsBucket = ImmutableArray.CreateBuilder (); + foreach (var accessorDeclaration in declaration.AccessorList.Accessors) { + if (semanticModel.GetDeclaredSymbol (accessorDeclaration) is not ISymbol accessorSymbol) + continue; + var kind = accessorDeclaration.Kind ().ToAccessorKind (); + accessorsBucket.Add (new ( + accessorKind: kind, + // just for the current symbol, not the parents etc.. + symbolAvailability: accessorSymbol.GetAvailabilityForSymbol (), + attributes: accessorSymbol.GetAttributeData () + )); + } + + accessors = accessorsBucket.ToImmutable (); + } + + var propertyAttributes = propertySymbol.GetAttributeData (); + property = new ( + name: memberName, + returnType: new (propertySymbol.Type, propertyAttributes), + symbolAvailability: propertySupportedPlatforms, + attributes: propertyAttributes, + accessors: accessors); + return true; + } /// public override string ToString () diff --git a/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs index 8b2d351d830..258a8bd817b 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs @@ -32,10 +32,6 @@ internal static SymbolAvailability GetAvailabilityForSymbol (this ISymbol symbol // add the different platforms to the result hashsets var builder = SymbolAvailability.CreateBuilder (); var boundAttributes = symbol.GetAttributes (); - if (boundAttributes.Length == 0) { - // no attrs in the symbol, therefore the symbol is supported in all platforms - return builder.ToImmutable (); - } foreach (var attributeData in boundAttributes) { var attrName = attributeData.AttributeClass?.ToDisplayString (); diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs index 95302c45928..3da3a642440 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TestDataFactory.cs @@ -337,4 +337,23 @@ public static TypeInfo ReturnTypeForINativeObject (string nativeObjectName, bool Parents = ["object"], Interfaces = ["ObjCRuntime.INativeObject"] }; + + public static TypeInfo ReturnTypeForNSString (bool isNullable = false) + => new ( + name: "Foundation.NSString", + isNullable: isNullable, + isArray: false, isReferenceType: true) { + IsNSObject = true, + IsINativeObject = true, + Parents = [ + "Foundation.NSObject", + "object" + ], + Interfaces = [ + "Foundation.INSCopying", + "Foundation.INSSecureCoding", + "ObjCRuntime.INativeObject", + "Foundation.INSObjectFactory" + ] + }; } diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/MethodTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/MethodTests.cs index 23ab965e6de..031a9b305f2 100644 --- a/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/MethodTests.cs +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/MethodTests.cs @@ -347,7 +347,7 @@ void TryCreateTests (ApplePlatform platform, (string Source, string Path) source .DescendantNodes ().OfType () .LastOrDefault (); Assert.NotNull (declaration); - Assert.True (Method.TryCreate (declaration, semanticModel, out var parameter)); - Assert.Equal (expectedData, parameter); + Assert.True (Method.TryCreate (declaration, semanticModel, out var method)); + Assert.Equal (expectedData, method); } } diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/PropertyTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/PropertyTests.cs new file mode 100644 index 00000000000..3ae1ec3ce27 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/DataModel/PropertyTests.cs @@ -0,0 +1,319 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Attributes; +using Microsoft.Macios.Generator.Availability; +using Microsoft.Macios.Generator.DataModel; +using Microsoft.Macios.Transformer.Attributes; +using Xamarin.Tests; +using Xamarin.Utils; +using static Microsoft.Macios.Generator.Tests.TestDataFactory; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Microsoft.Macios.Transformer.Tests.DataModel; + +public class PropertyTests : BaseTransformerTestClass { + + class TestDataTryCreateProperties : IEnumerable { + public IEnumerator GetEnumerator () + { + var path = "/some/random/path.cs"; + var availabilityBuilder = SymbolAvailability.CreateBuilder (); + availabilityBuilder.Add (new SupportedOSPlatformData ("ios")); + availabilityBuilder.Add (new SupportedOSPlatformData ("tvos")); + availabilityBuilder.Add (new SupportedOSPlatformData ("macos")); + availabilityBuilder.Add (new SupportedOSPlatformData ("maccatalyst")); + + const string simpleGetter = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Export (""name"")] + string Name { get; } +} +"; + yield return [ + (Source: simpleGetter, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + ExportPropertyData = new ("name"), + } + ]; + + const string simpleGetterSetter = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Export (""name"")] + string Name { get; set; } +} +"; + yield return [ + (Source: simpleGetterSetter, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()), + new Accessor ( + accessorKind: AccessorKind.Setter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + ExportPropertyData = new ("name"), + } + ]; + + const string staticSimpleGetterSetter = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Static, Export (""name"")] + string Name { get; set; } +} +"; + yield return [ + (Source: staticSimpleGetterSetter, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()), + new Accessor ( + accessorKind: AccessorKind.Setter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + Modifiers = [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + ExportPropertyData = new ("name"), + } + ]; + + const string abstractSimpleGetterSetter = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Abstract, Export (""name"")] + string Name { get; set; } +} +"; + yield return [ + (Source: abstractSimpleGetterSetter, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()), + new Accessor ( + accessorKind: AccessorKind.Setter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + Modifiers = [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.AbstractKeyword)], + ExportPropertyData = new ("name"), + } + ]; + + const string accessorsWithExport = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Export (""name"")] + string Name { + [Export (""getName"")] + get; + [Export (""setName"")] + set; + } +} +"; + yield return [ + (Source: accessorsWithExport, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) { + ExportPropertyData = new ("getName") + }, + new Accessor ( + accessorKind: AccessorKind.Setter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) { + ExportPropertyData = new ExportData ("setName") + } + ]) { + Modifiers = [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.VirtualKeyword), Token (SyntaxKind.PartialKeyword)], + ExportPropertyData = new ("name"), + } + ]; + + const string nullableSimpleGetterSetter = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [NullAllowed, Export (""name"")] + string Name { get; set; } +} +"; + yield return [ + (Source: nullableSimpleGetterSetter, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForString (isNullable: true), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()), + new Accessor ( + accessorKind: AccessorKind.Setter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + ExportPropertyData = new ("name"), + } + ]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + class TestDataTryCreateFields : IEnumerable { + public IEnumerator GetEnumerator () + { + var path = "/some/random/path.cs"; + var availabilityBuilder = SymbolAvailability.CreateBuilder (); + availabilityBuilder.Add (new SupportedOSPlatformData ("ios")); + availabilityBuilder.Add (new SupportedOSPlatformData ("tvos")); + availabilityBuilder.Add (new SupportedOSPlatformData ("macos")); + availabilityBuilder.Add (new SupportedOSPlatformData ("maccatalyst")); + + const string simpleField = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Field (""ConstantField"")] + NSString Name { get; } +} +"; + yield return [ + (Source: simpleField, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForNSString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + Modifiers = [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + ExportFieldData = new ("ConstantField"), + } + ]; + + const string fieldWithLibrary = @" +using System; +using Foundation; +using ObjCRuntime; + +interface AVPlayer { + [Field (""ConstantField"", ""LibraryName"")] + NSString Name { get; } +} +"; + yield return [ + (Source: fieldWithLibrary, Path: path), + new Property ( + name: "Name", + returnType: ReturnTypeForNSString (), + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new (), + accessors: [ + new Accessor ( + accessorKind: AccessorKind.Getter, + symbolAvailability: availabilityBuilder.ToImmutable (), + attributes: new ()) + ]) { + Modifiers = [Token (SyntaxKind.PublicKeyword), Token (SyntaxKind.StaticKeyword), Token (SyntaxKind.PartialKeyword)], + ExportFieldData = new ("ConstantField", "LibraryName"), + } + ]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + [AllSupportedPlatformsClassData] + void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, Property expectedData) + { + var compilation = CreateCompilation (platform, sources: source); + var syntaxTree = compilation.SyntaxTrees.ForSource (source); + var trees = compilation.SyntaxTrees.Where (s => s.FilePath == source.Path).ToArray (); + Assert.Single (trees); + Assert.NotNull (syntaxTree); + + var semanticModel = compilation.GetSemanticModel (syntaxTree); + Assert.NotNull (semanticModel); + + var declaration = syntaxTree.GetRoot () + .DescendantNodes ().OfType () + .LastOrDefault (); + Assert.NotNull (declaration); + Assert.True (Property.TryCreate (declaration, semanticModel, out var property)); + Assert.Equal (expectedData, property); + } +}