diff --git a/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs b/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs index be0db1ff8e84..b631ea0d3e04 100644 --- a/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs +++ b/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs @@ -137,6 +137,9 @@ void ParseBindTestImpl (string path, string variant, string bindArguments) binder.Massagers.Add ((massagerName.TrimStart ('+', '-'), enable)); i++; break; + case "-custom-delegates": + binder.UseCustomDelegates = true; + break; default: clangArguments.Add (bindArgs [i]); break; diff --git a/tests/sharpie/Tests/Nullability.h b/tests/sharpie/Tests/Nullability.h index 87c2af23c6f7..5728ff72c2f4 100644 --- a/tests/sharpie/Tests/Nullability.h +++ b/tests/sharpie/Tests/Nullability.h @@ -13,6 +13,27 @@ void Func (const char * __nullable str); +// Functions with nullable return types +extern NSObject * _Nullable NullableReturnObject (void); +extern NSString * _Nullable NullableReturnString (int code); + +// Functions with nullable parameters +extern void FuncWithNullableObject (NSObject * _Nullable obj); +extern void FuncWithNullableString (NSString * _Nullable str); +extern void FuncWithMixedNullability (NSObject * _Nonnull required, NSObject * _Nullable optional); + +// Functions with both nullable params and return +extern NSObject * _Nullable FuncNullableInAndOut (NSString * _Nullable input); + +// Functions with nullable block parameters +extern void FuncWithNullableBlock (void (^ _Nullable block)(void)); +extern void FuncWithNullableBlockParam (void (^ _Nullable block)(NSObject * _Nullable obj)); +extern void FuncWithNonnullBlock (void (^ _Nonnull block)(void)); + +// Functions returning nullable block +typedef void (^SimpleBlock)(void); +extern SimpleBlock _Nullable FuncReturningNullableBlock (void); + @interface Foo @property (nullable) SEL selector; diff --git a/tests/sharpie/Tests/Nullability.iphoneos.cs b/tests/sharpie/Tests/Nullability.iphoneos.cs index 81dc7a469a4c..8d7463f2df86 100644 --- a/tests/sharpie/Tests/Nullability.iphoneos.cs +++ b/tests/sharpie/Tests/Nullability.iphoneos.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using Foundation; using ObjCRuntime; @@ -7,8 +8,65 @@ static class CFunctions { [DllImport ("__Internal")] [Verify (PlatformInvoke)] static extern unsafe void Func ([NullAllowed] sbyte* str); + + // extern NSObject * _Nullable NullableReturnObject (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject NullableReturnObject (); + + // extern NSString * _Nullable NullableReturnString (int code); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSString NullableReturnString (int code); + + // extern void FuncWithNullableObject (NSObject * _Nullable obj); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableObject ([NullAllowed] NSObject obj); + + // extern void FuncWithNullableString (NSString * _Nullable str); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableString ([NullAllowed] NSString str); + + // extern void FuncWithMixedNullability (NSObject * _Nonnull required, NSObject * _Nullable optional); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithMixedNullability (NSObject required, [NullAllowed] NSObject optional); + + // extern NSObject * _Nullable FuncNullableInAndOut (NSString * _Nullable input); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject FuncNullableInAndOut ([NullAllowed] NSString input); + + // extern void FuncWithNullableBlock (void (^ _Nullable)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlock ([NullAllowed] Action block); + + // extern void FuncWithNullableBlockParam (void (^ _Nullable)(NSObject * _Nullable) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlockParam ([NullAllowed] Action block); + + // extern void FuncWithNonnullBlock (void (^ _Nonnull)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNonnullBlock (Action block); + + // extern SimpleBlock _Nullable FuncReturningNullableBlock (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern SimpleBlock FuncReturningNullableBlock (); } +// typedef void (^SimpleBlock)(); +delegate void SimpleBlock (); + // @interface Foo interface Foo { // @property SEL _Nullable selector; diff --git a/tests/sharpie/Tests/Nullability.macosx.cs b/tests/sharpie/Tests/Nullability.macosx.cs index 81dc7a469a4c..8d7463f2df86 100644 --- a/tests/sharpie/Tests/Nullability.macosx.cs +++ b/tests/sharpie/Tests/Nullability.macosx.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using Foundation; using ObjCRuntime; @@ -7,8 +8,65 @@ static class CFunctions { [DllImport ("__Internal")] [Verify (PlatformInvoke)] static extern unsafe void Func ([NullAllowed] sbyte* str); + + // extern NSObject * _Nullable NullableReturnObject (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject NullableReturnObject (); + + // extern NSString * _Nullable NullableReturnString (int code); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSString NullableReturnString (int code); + + // extern void FuncWithNullableObject (NSObject * _Nullable obj); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableObject ([NullAllowed] NSObject obj); + + // extern void FuncWithNullableString (NSString * _Nullable str); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableString ([NullAllowed] NSString str); + + // extern void FuncWithMixedNullability (NSObject * _Nonnull required, NSObject * _Nullable optional); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithMixedNullability (NSObject required, [NullAllowed] NSObject optional); + + // extern NSObject * _Nullable FuncNullableInAndOut (NSString * _Nullable input); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject FuncNullableInAndOut ([NullAllowed] NSString input); + + // extern void FuncWithNullableBlock (void (^ _Nullable)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlock ([NullAllowed] Action block); + + // extern void FuncWithNullableBlockParam (void (^ _Nullable)(NSObject * _Nullable) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlockParam ([NullAllowed] Action block); + + // extern void FuncWithNonnullBlock (void (^ _Nonnull)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNonnullBlock (Action block); + + // extern SimpleBlock _Nullable FuncReturningNullableBlock (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern SimpleBlock FuncReturningNullableBlock (); } +// typedef void (^SimpleBlock)(); +delegate void SimpleBlock (); + // @interface Foo interface Foo { // @property SEL _Nullable selector; diff --git a/tests/sharpie/Tests/Types/Blocks.custom_delegates.cs b/tests/sharpie/Tests/Types/Blocks.custom_delegates.cs new file mode 100644 index 000000000000..4e021e30f0ce --- /dev/null +++ b/tests/sharpie/Tests/Types/Blocks.custom_delegates.cs @@ -0,0 +1,129 @@ +using System; +using Foundation; +using ObjCRuntime; + +// typedef void (^Action)(); +delegate void Action (); + +// typedef int (^Anon_Func_Long_Int)(long long); +delegate int Anon_Func_Long_Int (long arg0); + +// typedef int (^Named_Func_Long_Int)(long long); +delegate int Named_Func_Long_Int (long arg0); + +// typedef void (^Variadic)(int, ...); +delegate void Variadic (int arg0, IntPtr varArgs); + +// @interface TypedefBlockTests +interface TypedefBlockTests { + // -(Action)get_Action; + [Export ("get_Action")] + [Verify (MethodToProperty)] + Action Get_Action { get; } + + // -(Anon_Func_Long_Int)get_Anon_Func_Long_Int; + [Export ("get_Anon_Func_Long_Int")] + [Verify (MethodToProperty)] + Anon_Func_Long_Int Get_Anon_Func_Long_Int { get; } + + // -(Named_Func_Long_Int)get_Named_Func_Long_Int; + [Export ("get_Named_Func_Long_Int")] + [Verify (MethodToProperty)] + Named_Func_Long_Int Get_Named_Func_Long_Int { get; } +} + +delegate void ActionHandler (); + +delegate void Action_intHandler (int arg0); + +delegate void Action_actionHandler (Action arg0); + +delegate void Action_action_intHandler (Action arg0); + +delegate void Action_action_actionHandler (Action arg0); + +delegate int Func_int_intHandler (int arg0); + +delegate nint Func_short_nintHandler (short arg0); + +// @interface PropertyBlockTests +interface PropertyBlockTests { + // @property (readonly, copy) void (^action)(); + [Export ("action", ArgumentSemantic.Copy)] + ActionHandler Action { get; } + + // @property (readonly, copy) void (^action_int)(int); + [Export ("action_int", ArgumentSemantic.Copy)] + Action_intHandler Action_int { get; } + + // @property (readonly, copy) void (^action_action)(void (^)()); + [Export ("action_action", ArgumentSemantic.Copy)] + Action_actionHandler Action_action { get; } + + // @property (readonly, copy) void (^action_action_int)(void (^)(int)); + [Export ("action_action_int", ArgumentSemantic.Copy)] + Action_action_intHandler Action_action_int { get; } + + // @property (readonly, copy) void (^action_action_action)(void (^)(void (^)())); + [Export ("action_action_action", ArgumentSemantic.Copy)] + Action_action_actionHandler Action_action_action { get; } + + // @property (readonly, copy) int (^func_int_int)(int); + [Export ("func_int_int", ArgumentSemantic.Copy)] + Func_int_intHandler Func_int_int { get; } + + // @property (readonly, copy) long (^func_short_nint)(short); + [Export ("func_short_nint", ArgumentSemantic.Copy)] + Func_short_nintHandler Func_short_nint { get; } +} + +delegate void Set_ActionHandler (); + +delegate void Set_Action_intHandler (int arg0); + +delegate void Set_Action_short_int_longHandler (short arg0, int arg1, long arg2); + +delegate int Set_Func_intHandler (); + +delegate int Set_Func_int_intHandler (int arg0); + +delegate bool Set_Func_short_int_long_boolHandler (short arg0, int arg1, long arg2); + +delegate bool Set_Func_Func_short_Action_long_short_boolHandler (Func arg0, short arg1); + +delegate bool Set_Func_Func_short_Action_Action_int_nint_long_short_boolHandler (Func>, long> arg0, short arg1); + +// @interface AnonymousBlockTests +interface AnonymousBlockTests { + // -(void)set_Action:(void (^)())handler; + [Export ("set_Action:")] + void Set_Action (Set_ActionHandler handler); + + // -(void)set_Action_int:(void (^)(int))handler; + [Export ("set_Action_int:")] + void Set_Action_int (Set_Action_intHandler handler); + + // -(void)set_Action_short_int_long:(void (^)(short, int, long long))handler; + [Export ("set_Action_short_int_long:")] + void Set_Action_short_int_long (Set_Action_short_int_longHandler handler); + + // -(void)set_Func_int:(int (^)())handler; + [Export ("set_Func_int:")] + void Set_Func_int (Set_Func_intHandler handler); + + // -(void)set_Func_int_int:(int (^)(int))handler; + [Export ("set_Func_int_int:")] + void Set_Func_int_int (Set_Func_int_intHandler handler); + + // -(void)set_Func_short_int_long_bool:(_Bool (^)(short, int, long long))handler; + [Export ("set_Func_short_int_long_bool:")] + void Set_Func_short_int_long_bool (Set_Func_short_int_long_boolHandler handler); + + // -(void)set_Func_Func_short_Action_long_short_bool:(_Bool (^)(long long (^)(short, void (^)()), short))handler; + [Export ("set_Func_Func_short_Action_long_short_bool:")] + void Set_Func_Func_short_Action_long_short_bool (Set_Func_Func_short_Action_long_short_boolHandler handler); + + // -(void)set_Func_Func_short_Action_Action_int_nint_long_short_bool:(_Bool (^)(long long (^)(short, void (^)(void (^)(int, long))), short))handler; + [Export ("set_Func_Func_short_Action_Action_int_nint_long_short_bool:")] + void Set_Func_Func_short_Action_Action_int_nint_long_short_bool (Set_Func_Func_short_Action_Action_int_nint_long_short_boolHandler handler); +} diff --git a/tests/sharpie/Tests/Types/Blocks.h b/tests/sharpie/Tests/Types/Blocks.h index f75e4297fd88..d0fe7f691376 100644 --- a/tests/sharpie/Tests/Types/Blocks.h +++ b/tests/sharpie/Tests/Types/Blocks.h @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// RUN: -x objective-c +// RUN custom_delegates: -x objective-c -custom-delegates + typedef void (^Action)(); typedef int (^Anon_Func_Long_Int)(long long); typedef int (^Named_Func_Long_Int)(long long longArg); diff --git a/tests/xtro-sharpie/Makefile b/tests/xtro-sharpie/Makefile index eb3f4d554531..26d0e6debd16 100644 --- a/tests/xtro-sharpie/Makefile +++ b/tests/xtro-sharpie/Makefile @@ -126,6 +126,7 @@ IGNORED_MACCATALYST_FRAMEWORKS = \ COMMON_SHARPIE_ARGUMENTS = \ -a arm64 \ -modules true \ + --custom-delegates \ --clang-resource-dir $(TOP)/tools/sharpie/clang \ IOS_SHARPIE_ARGUMENTS = \ diff --git a/tools/sharpie/Sharpie.Bind/BindingMassager.cs b/tools/sharpie/Sharpie.Bind/BindingMassager.cs index ef107c47f462..a4e820574a08 100644 --- a/tools/sharpie/Sharpie.Bind/BindingMassager.cs +++ b/tools/sharpie/Sharpie.Bind/BindingMassager.cs @@ -18,6 +18,7 @@ public BindingMassager (ObjectiveCBinder binder) } static readonly List defaultMassagers = new List { + typeof (CustomDelegateMassager), typeof (DefaultConstructorMassager), typeof (DelegateMassager), typeof (MethodToPropertyMassager), @@ -80,6 +81,8 @@ static MassagerBase CreateMassager (ObjectiveCBinder binder, string name) name = name.Substring (ns.Length + 1); switch (name) { + case nameof (CustomDelegateMassager): + return new CustomDelegateMassager (binder); case nameof (DefaultConstructorMassager): return new DefaultConstructorMassager (binder); case nameof (DelegateMassager): diff --git a/tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs b/tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs new file mode 100644 index 000000000000..3685d9b32fcb --- /dev/null +++ b/tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using ICSharpCode.NRefactory.CSharp; + +using Sharpie.Bind.Types; + +namespace Sharpie.Bind.Massagers; + +/// +/// Converts anonymous Action<...>/Func<...> delegate types in method +/// parameters and property return types into custom named delegate declarations. +/// +[RegisterBefore (typeof (GenerateUsingStatementsMassager))] +public sealed class CustomDelegateMassager : Massager { + readonly HashSet usedNames = new HashSet (); + readonly List<(AstNode InsertBefore, DelegateDeclaration Delegate)> pendingDelegates = new (); + + public CustomDelegateMassager (ObjectiveCBinder binder) + : base (binder) + { + } + + public override bool Initialize () + { + return Binder.UseCustomDelegates; + } + + public override void VisitMethodDeclaration (MethodDeclaration methodDeclaration) + { + if (HasVisited (methodDeclaration)) + return; + + MarkVisited (methodDeclaration); + + var parent = methodDeclaration.Parent as TypeDeclaration; + if (parent is null) + return; + + foreach (var param in methodDeclaration.Parameters.ToList ()) { + if (param.Type is not DelegateType delegateType) + continue; + + var delegateName = GenerateUniqueName (methodDeclaration.Name, param.Name.UCFirst ()); + var del = CreateDelegateDeclaration (delegateName, delegateType); + pendingDelegates.Add ((parent, del)); + + var newType = new SimpleType (delegateName); + delegateType.CopyAnnotationsTo (newType); + param.Type = newType; + } + + if (methodDeclaration.ReturnType is DelegateType returnDelegateType) { + var delegateName = GenerateUniqueName (methodDeclaration.Name, "Return"); + var del = CreateDelegateDeclaration (delegateName, returnDelegateType); + pendingDelegates.Add ((parent, del)); + + var newType = new SimpleType (delegateName); + returnDelegateType.CopyAnnotationsTo (newType); + methodDeclaration.ReturnType = newType; + } + } + + public override void VisitPropertyDeclaration (PropertyDeclaration propertyDeclaration) + { + if (HasVisited (propertyDeclaration)) + return; + + MarkVisited (propertyDeclaration); + + if (propertyDeclaration.ReturnType is not DelegateType delegateType) + return; + + var parent = propertyDeclaration.Parent as TypeDeclaration; + if (parent is null) + return; + + var delegateName = GenerateUniqueName (propertyDeclaration.Name, "Handler"); + var del = CreateDelegateDeclaration (delegateName, delegateType); + pendingDelegates.Add ((parent, del)); + + var newType = new SimpleType (delegateName); + delegateType.CopyAnnotationsTo (newType); + propertyDeclaration.ReturnType = newType; + } + + public override void VisitSyntaxTree (SyntaxTree syntaxTree) + { + base.VisitSyntaxTree (syntaxTree); + FlushPendingDelegates (); + } + + public override void VisitNamespaceDeclaration (NamespaceDeclaration namespaceDeclaration) + { + base.VisitNamespaceDeclaration (namespaceDeclaration); + FlushPendingDelegates (); + } + + void FlushPendingDelegates () + { + foreach (var (insertBefore, del) in pendingDelegates) { + var container = insertBefore.Parent; + if (container is SyntaxTree syntaxTree) + syntaxTree.Members.InsertBefore (insertBefore, del); + else if (container is NamespaceDeclaration ns) + ns.Members.InsertBefore (insertBefore, del); + } + pendingDelegates.Clear (); + } + + static DelegateDeclaration CreateDelegateDeclaration (string name, DelegateType delegateType) + { + var del = new DelegateDeclaration { + Name = name + }; + + int i = 0; + foreach (var typeArg in delegateType.TypeArguments) { + var clone = typeArg.Clone (); + del.Parameters.Add (new ParameterDeclaration (clone, String.Format ("arg{0}", i++))); + } + + if (delegateType is FuncType) { + // For Func, the last type argument is the return type + var returnParam = del.Parameters.LastOrNullObject (); + var returnType = returnParam.Type; + returnType.Remove (); + returnParam.Remove (); + del.ReturnType = returnType; + } else { + del.ReturnType = new PrimitiveType ("void"); + } + + return del; + } + + string GenerateUniqueName (string memberName, string? suffix) + { + var baseName = memberName + suffix; + var name = baseName; + var counter = 2; + while (!usedNames.Add (name)) + name = baseName + counter++; + return name; + } +} diff --git a/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs b/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs index 01f5ef80dacd..306d065170d0 100644 --- a/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs +++ b/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs @@ -35,6 +35,7 @@ public string Sdk { public bool? EnableModules { get; set; } public bool SplitDocuments { get; set; } = true; public bool DeepSplit { get; set; } + public bool UseCustomDelegates { get; set; } public string ApiDefinitionName { get; set; } = "ApiDefinition.cs"; public string StructsAndEnumsName { get; set; } = "StructsAndEnums.cs"; @@ -254,6 +255,10 @@ protected virtual bool AddArguments (List args) args.Add ("--deepsplit"); } + if (UseCustomDelegates) { + args.Add ("--custom-delegates"); + } + if (EnableModules.HasValue) { if (EnableModules.Value) args.Add ("--modules=true"); diff --git a/tools/sharpie/Sharpie.Bind/Tools.cs b/tools/sharpie/Sharpie.Bind/Tools.cs index fbc0bc23b027..bd8dc79dc146 100644 --- a/tools/sharpie/Sharpie.Bind/Tools.cs +++ b/tools/sharpie/Sharpie.Bind/Tools.cs @@ -45,6 +45,7 @@ public static int Bind (string [] arguments) { "m|massage=", "Register (+ prefix) or exclude (- prefix) a massager by name.", v => binder.AddMassager (v) }, { "nosplit", "Do not split the generated binding into multiple files.", v => binder.SplitDocuments = false }, { "deepsplit", "Split the generated binding into one file per source header.", v => binder.DeepSplit = true }, + { "custom-delegates", "Generate custom named delegates instead of Func<>/Action<>.", v => binder.UseCustomDelegates = true }, }; os.EndOfParsingArguments.Clear (); @@ -114,6 +115,7 @@ public static bool Visit (string [] arguments, Func createV { "clang-resource-dir=", "Specify the Clang resource directory.", v => binder.ClangResourceDirectory = v }, { "platform-assembly=", "Specify the platform assembly to use for binding.", v => binder.PlatformAssembly = v }, { "nosplit", "Do not split the generated binding into multiple files.", v => binder.SplitDocuments = false }, + { "custom-delegates", "Generate custom named delegates instead of Func<>/Action<>.", v => binder.UseCustomDelegates = true }, }; os.EndOfParsingArguments.Clear ();