diff --git a/src/ObjCBindings/ExportAttribute.cs b/src/ObjCBindings/ExportAttribute.cs index 147ee0ffce1..0f9b575db36 100644 --- a/src/ObjCBindings/ExportAttribute.cs +++ b/src/ObjCBindings/ExportAttribute.cs @@ -27,6 +27,30 @@ public class ExportAttribute : Attribute where T : Enum { /// public ArgumentSemantic ArgumentSemantic { get; set; } = ArgumentSemantic.None; + /// + /// Get/Set the native prefix to be used in the custom marshal directive. + /// + /// The generator will only respect this value if the CustomMarshalDirective flag is set. + /// If the flag is not set the analyzer will raise an compiling error. + /// + public string? NativePrefix { get; set; } = null; + + /// + /// Get/Set the native suffix to be used in the custom marshal directive. + /// + /// The generator will only respect this value if the CustomMarshalDirective flag is set. + /// If the flag is not set the analyzer will raise an compiling error. + /// + public string? NativeSuffix { get; set; } = null; + + /// + /// Get/Set the library to be used in the custom marshal directive. + /// + /// The generator will only respect this value if the CustomMarshalDirective flag is set. + /// If the flag is not set the analyzer will raise an compiling error. + /// + public string? Library { get; set; } = null; + protected ExportAttribute () { } /// diff --git a/src/ObjCBindings/ExportTag.cs b/src/ObjCBindings/ExportTag.cs index 51ae712c287..d10165e9fbf 100644 --- a/src/ObjCBindings/ExportTag.cs +++ b/src/ObjCBindings/ExportTag.cs @@ -49,6 +49,16 @@ public enum Method : Int64 { /// MarshalNativeExceptions = 1 << 4, + + /// + /// Instruct the generator to use a custom marshal directive for the method. When this flag is applied the + /// following name parameters must be provided: + /// - NativePrefix: The prefix to be used in the native method name. + /// - NativeSuffix: The suffix to be used in the native method name. + /// - Library: The library to be used in the custom marshal directive. + /// + CustomMarshalDirective = 1 << 5, + } /// @@ -78,6 +88,15 @@ public enum Property : Int64 { /// MarshalNativeExceptions = 1 << 4, + /// + /// Instruct the generator to use a custom marshal directive for the method. When this flag is applied the + /// following name parameters must be provided: + /// - NativePrefix: The prefix to be used in the native method name. + /// - NativeSuffix: The suffix to be used in the native method name. + /// - Library: The library to be used in the custom marshal directive. + /// + CustomMarshalDirective = 1 << 5, + } } diff --git a/src/rgen/Microsoft.Macios.Generator/Attributes/ExportData.cs b/src/rgen/Microsoft.Macios.Generator/Attributes/ExportData.cs index 786d4de8590..90854938ec7 100644 --- a/src/rgen/Microsoft.Macios.Generator/Attributes/ExportData.cs +++ b/src/rgen/Microsoft.Macios.Generator/Attributes/ExportData.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.CodeAnalysis; using ObjCRuntime; @@ -28,6 +29,27 @@ namespace Microsoft.Macios.Generator.Attributes; /// public ArgumentSemantic ArgumentSemantic { get; } = ArgumentSemantic.None; + /// + /// Get the native prefix to be used in the custom marshal directive. + /// + /// Should only be present with the CustomeMarshalDirective flag. + /// + public string? NativePrefix { get; init; } + + /// + /// Get the native sufix to be used in the custom marshal directive. + /// + /// Should only be present with the CustomeMarshalDirective flag. + /// + public string? NativeSuffix { get; init; } + + /// + /// Get the library to be used in the custom marshal directive. + /// + /// Should only be present with the CustomeMarshalDirective flag. + /// + public string? Library { get; init; } + public ExportData () { } public ExportData (string? selector) @@ -62,6 +84,12 @@ public static bool TryParse (AttributeData attributeData, string? selector = null; ArgumentSemantic argumentSemantic = ArgumentSemantic.None; T? flags = default; + + // custom marshal directive values + string? nativePrefix = null; + string? nativeSuffix = null; + string? library = null; + switch (count) { case 1: selector = (string?) attributeData.ConstructorArguments [0].Value!; @@ -106,14 +134,35 @@ public static bool TryParse (AttributeData attributeData, case "Flags": flags = (T) value.Value!; break; + case "NativePrefix": + nativePrefix = (string?) value.Value!; + break; + case "NativeSuffix": + nativeSuffix = (string?) value.Value!; + break; + case "Library": + library = (string?) value.Value!; + break; default: data = null; return false; } } - data = flags is not null ? - new (selector, argumentSemantic, flags) : new (selector, argumentSemantic); + if (flags is not null) { + data = new (selector, argumentSemantic, flags) { + NativePrefix = nativePrefix, + NativeSuffix = nativeSuffix, + Library = library + }; + return true; + } + + data = new (selector, argumentSemantic) { + NativePrefix = nativePrefix, + NativeSuffix = nativeSuffix, + Library = library + }; return true; } @@ -124,10 +173,18 @@ public bool Equals (ExportData other) return false; if (ArgumentSemantic != other.ArgumentSemantic) return false; - if (Flags is not null && other.Flags is not null) { - return Flags.Equals (other.Flags); - } - return false; + if (NativePrefix != other.NativePrefix) + return false; + if (NativeSuffix != other.NativeSuffix) + return false; + if (Library != other.Library) + return false; + return (Flags, other.Flags) switch { + (null, null) => true, + (null, _) => false, + (_, null) => false, + (_, _) => Flags!.Equals (other.Flags) + }; } /// @@ -155,6 +212,21 @@ public override int GetHashCode () /// public override string ToString () { - return $"{{ Type: '{typeof (T).FullName}', Selector: '{Selector ?? "null"}', ArgumentSemantic: '{ArgumentSemantic}', Flags: '{Flags}' }}"; + var sb = new StringBuilder ("{ Type: '"); + sb.Append (typeof (T).FullName); + sb.Append ("', Selector: '"); + sb.Append (Selector ?? "null"); + sb.Append ("', ArgumentSemantic: '"); + sb.Append (ArgumentSemantic); + sb.Append ("', Flags: '"); + sb.Append (Flags); + sb.Append ("', NativePrefix: '"); + sb.Append (NativePrefix ?? "null"); + sb.Append ("', NativeSuffix: '"); + sb.Append (NativeSuffix ?? "null"); + sb.Append ("', Library: '"); + sb.Append (Library ?? "null"); + sb.Append ("' }"); + return sb.ToString (); } } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/ExportDataTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/ExportDataTests.cs index 04470f32398..e0384f20ed5 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/ExportDataTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/Attributes/ExportDataTests.cs @@ -54,22 +54,31 @@ public IEnumerator GetEnumerator () yield return [ Method.Default, new ExportData ("symbol", ArgumentSemantic.None, Method.Default), - "{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }" + "{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default', NativePrefix: 'null', NativeSuffix: 'null', Library: 'null' }" ]; yield return [ Method.Default, new ExportData ("symbol"), - "{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }" + "{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default', NativePrefix: 'null', NativeSuffix: 'null', Library: 'null' }" ]; yield return [ Property.Default, new ExportData ("symbol", ArgumentSemantic.Retain, Property.Default), - "{ Type: 'ObjCBindings.Property', Selector: 'symbol', ArgumentSemantic: 'Retain', Flags: 'Default' }" + "{ Type: 'ObjCBindings.Property', Selector: 'symbol', ArgumentSemantic: 'Retain', Flags: 'Default', NativePrefix: 'null', NativeSuffix: 'null', Library: 'null' }" ]; yield return [ Property.Default, new ExportData ("symbol"), - "{ Type: 'ObjCBindings.Property', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default' }" + "{ Type: 'ObjCBindings.Property', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'Default', NativePrefix: 'null', NativeSuffix: 'null', Library: 'null' }" + ]; + yield return [ + Method.Default, + new ExportData ("symbol", ArgumentSemantic.None, + Method.Default | Method.CustomMarshalDirective) { + NativePrefix = "xamarin_", + Library = "__Internal" + }, + "{ Type: 'ObjCBindings.Method', Selector: 'symbol', ArgumentSemantic: 'None', Flags: 'CustomMarshalDirective', NativePrefix: 'xamarin_', NativeSuffix: 'null', Library: '__Internal' }" ]; } @@ -81,6 +90,7 @@ IEnumerator IEnumerable.GetEnumerator () [ClassData (typeof (TestDataToString))] void TestFieldDataToString (T @enum, ExportData x, string expected) where T : Enum { + var str = x.ToString (); Assert.NotNull (@enum); Assert.Equal (expected, x.ToString ()); } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs index 2fe5417c7e8..652c4e4a037 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs @@ -735,6 +735,78 @@ public partial class MyClass { } ]; + const string customMarshallingProperty = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""name"", Flags = Property.CustomMarshalDirective, NativePrefix = ""xamarin_"", Library = ""__Internal"")] + public partial string Name { get; set; } = string.Empty; +} +"; + + yield return [ + customMarshallingProperty, + new CodeChanges ( + bindingInfo: new (new BindingTypeData ()), + name: "MyClass", + @namespace: ["NS"], + fullyQualifiedSymbol: "NS.MyClass", + symbolAvailability: new () + ) { + Base = "object", + Interfaces = ImmutableArray.Empty, + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + UsingDirectives = new HashSet { "ObjCBindings" }, + Modifiers = [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword) + ], + Properties = [ + new ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: new (), + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name", "ObjCBindings.Property.CustomMarshalDirective", "xamarin_", "__Internal"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new ( + accessorKind: AccessorKind.Getter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + new ( + accessorKind: AccessorKind.Setter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + ] + ) { + ExportPropertyData = new ( + selector: "name", + argumentSemantic: ArgumentSemantic.None, + flags: Property.Default | Property.CustomMarshalDirective) { + NativePrefix = "xamarin_", + Library = "__Internal" + } + } + ] + } + ]; + const string multiPropertyClass = @" using ObjCBindings; diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs index 0383be448c2..99190ba0bf1 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs @@ -9,6 +9,7 @@ using Microsoft.Macios.Generator.Attributes; using Microsoft.Macios.Generator.DataModel; using ObjCBindings; +using ObjCRuntime; using Xamarin.Tests; using Xamarin.Utils; using Xunit; @@ -420,6 +421,78 @@ public partial interface IProtocol { } ]; + const string customMarshallingProperty = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface MyClass { + [Export (""name"", Flags = Property.CustomMarshalDirective, NativePrefix = ""xamarin_"", Library = ""__Internal"")] + public partial string Name { get; set; } = string.Empty; +} +"; + + yield return [ + customMarshallingProperty, + new CodeChanges ( + bindingInfo: new (new BindingTypeData ()), + name: "MyClass", + @namespace: ["NS"], + fullyQualifiedSymbol: "NS.MyClass", + symbolAvailability: new () + ) { + Base = null, + Interfaces = [], + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + UsingDirectives = new HashSet { "ObjCBindings" }, + Modifiers = [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword) + ], + Properties = [ + new ( + name: "Name", + returnType: ReturnTypeForString (), + symbolAvailability: new (), + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name", "ObjCBindings.Property.CustomMarshalDirective", "xamarin_", "__Internal"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new ( + accessorKind: AccessorKind.Getter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + new ( + accessorKind: AccessorKind.Setter, + symbolAvailability: new (), + exportPropertyData: null, + attributes: [], + modifiers: [] + ), + ] + ) { + ExportPropertyData = new ( + selector: "name", + argumentSemantic: ArgumentSemantic.None, + flags: Property.Default | Property.CustomMarshalDirective) { + NativePrefix = "xamarin_", + Library = "__Internal" + } + } + ] + } + ]; + const string multiPropertyInterface = @" using ObjCBindings;