diff --git a/docs/website/binding_types_reference_guide.md b/docs/website/binding_types_reference_guide.md index 28bc5eecc1..91b72f6612 100644 --- a/docs/website/binding_types_reference_guide.md +++ b/docs/website/binding_types_reference_guide.md @@ -1900,8 +1900,6 @@ var strongDemo = new Demo (); demo.Delegate = new MyDelegate (); ``` - - Another use of the `Wrap` attribute is to support strongly typed version of methods. For example: @@ -1937,6 +1935,42 @@ interface FooExplorer { } ``` +`[Wrap]` can also be used directly in property getters and setters, +this allows to have full control on them and adjust the code the way you need, +for example using smart enums, consider the following API definition: + +```csharp +// Smart enum. +enum PersonRelationship { + [Field (null)] + None, + + [Field ("FMFather", "__Internal")] + Father, + + [Field ("FMMother", "__Internal")] + Mother +} +``` + +Interface definition: + +``` +// Property definition. + + [Export ("presenceType")] + NSString _PresenceType { get; set; } + + PersonRelationship PresenceType { + [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType)")] + get; + [Wrap ("_PresenceType = value.GetConstant ()")] + set; + } +``` + + + # Parameter Attributes This section describes the attributes that you can apply to the parameters in diff --git a/docs/website/generator-errors.md b/docs/website/generator-errors.md index 0b95a9f08e..1fa378b47e 100644 --- a/docs/website/generator-errors.md +++ b/docs/website/generator-errors.md @@ -176,6 +176,8 @@ This usually indicates a bug in Xamarin.iOS/Xamarin.Mac; please [file a bug repo ### BI1062: The member '*' contains ref/out parameters and must not be decorated with [Async]. +### BI1063: The 'WrapAttribute' can only be used at the property or at getter/setter level at a given time. Property: '*'. + # BI11xx: warnings diff --git a/src/generator-attribute-manager.cs b/src/generator-attribute-manager.cs index 383943ac1d..602ec4b763 100644 --- a/src/generator-attribute-manager.cs +++ b/src/generator-attribute-manager.cs @@ -283,6 +283,8 @@ public static class AttributeManager public static T GetCustomAttribute (ICustomAttributeProvider provider) where T : System.Attribute { + if (provider is null) + return null; var rv = GetCustomAttributes (provider); if (rv == null || rv.Length == 0) return null; diff --git a/src/generator.cs b/src/generator.cs index 6a6bb84b8e..44804af460 100644 --- a/src/generator.cs +++ b/src/generator.cs @@ -426,13 +426,30 @@ public interface IMemberGatherer { IEnumerable GetTypeContractMethods (Type source); } +class WrapPropMemberInformation +{ + public bool HasWrapOnGetter { get => WrapGetter != null; } + public bool HasWrapOnSetter { get => WrapSetter != null; } + public string WrapGetter { get; private set; } + public string WrapSetter { get; private set; } + + public WrapPropMemberInformation (PropertyInfo pi) + { + WrapGetter = AttributeManager.GetCustomAttribute (pi?.GetMethod)?.MethodName; + WrapSetter = AttributeManager.GetCustomAttribute (pi?.SetMethod)?.MethodName; + } +} + + public class MemberInformation { public readonly MemberInfo mi; public readonly Type type; public readonly Type category_extension_type; + internal readonly WrapPropMemberInformation wpmi; public readonly bool is_abstract, is_protected, is_internal, is_unified_internal, is_override, is_new, is_sealed, is_static, is_thread_static, is_autorelease, is_wrapper, is_forced; public readonly bool is_type_sealed, ignore_category_static_warnings, is_basewrapper_protocol_method; + public readonly bool has_inner_wrap_attribute; public readonly Generator.ThreadCheck threadCheck; public bool is_unsafe, is_virtual_method, is_export, is_category_extension, is_variadic, is_interface_impl, is_extension_method, is_appearance, is_model, is_ctor; public bool is_return_release; @@ -572,6 +589,17 @@ public class MemberInformation is_virtual_method = false; else is_virtual_method = !is_static; + + // Properties can have WrapAttribute on getter/setter so we need to check for this + // but only if no Export is already found on property level. + if (export is null) { + wpmi = new WrapPropMemberInformation (pi); + has_inner_wrap_attribute = wpmi.HasWrapOnGetter || wpmi.HasWrapOnSetter; + + // Wrap can only be used either at property level or getter/setter level at a given time. + if (wrap_method != null && has_inner_wrap_attribute) + throw new BindingException (1063, true, $"The 'WrapAttribute' can only be used at the property or at getter/setter level at a given time. Property: '{pi.DeclaringType}.{pi.Name}'"); + } } public string GetVisibility () @@ -2264,7 +2292,10 @@ public partial class Generator : IMemberGatherer { if (AttributeManager.HasAttribute (pi)) continue; - + + if (AttributeManager.HasAttribute (pi.GetGetMethod ()) || AttributeManager.HasAttribute (pi.GetSetMethod ())) + continue; + throw new BindingException (1018, true, "No [Export] attribute on property {0}.{1}", t.FullName, pi.Name); } if (AttributeManager.HasAttribute (pi)) @@ -4653,6 +4684,39 @@ public partial class Generator : IMemberGatherer { indent--; print ("}\n"); return; + } else if (minfo.has_inner_wrap_attribute) { + // If property getter or setter has its own WrapAttribute we let the user do whatever their heart desires + if (pi.CanRead) { + PrintAttributes (pi, platform: true); + PrintAttributes (pi.GetGetMethod (), platform: true, preserve: true, advice: true); + print ("get {"); + indent++; + + print ($"return {minfo.wpmi.WrapGetter};"); + + indent--; + print ("}"); + } + if (pi.CanWrite) { + var setter = pi.GetSetMethod (); + var not_implemented_attr = AttributeManager.GetCustomAttribute (setter); + + PrintAttributes (pi, platform: true); + PrintAttributes (setter, platform: true, preserve: true, advice: true, notImplemented: true); + print ("set {"); + indent++; + + if (not_implemented_attr != null) + print ("throw new NotImplementedException ({0});", not_implemented_attr.Message == null ? "" : $@"""{not_implemented_attr.Message}"""); + else + print ($"{minfo.wpmi.WrapSetter};"); + + indent--; + print ("}"); + } + indent--; + print ("}\n"); + return; } if (pi.CanRead){ diff --git a/tests/generator/BGenTests.cs b/tests/generator/BGenTests.cs index 593c7a1fbd..8fafe61357 100644 --- a/tests/generator/BGenTests.cs +++ b/tests/generator/BGenTests.cs @@ -522,15 +522,24 @@ namespace GeneratorTests [Test] public void Bug57531 () => BuildFile (Profile.iOS, "bug57531.cs"); + [Test] + public void Bug57870 () => BuildFile (Profile.iOS, true, true, "bug57870.cs"); + BGenTool BuildFile (Profile profile, params string [] filenames) { - return BuildFile (profile, true, filenames); + return BuildFile (profile, true, false, filenames); } BGenTool BuildFile (Profile profile, bool nowarnings, params string [] filenames) + { + return BuildFile (profile, nowarnings, false, filenames); + } + + BGenTool BuildFile (Profile profile, bool nowarnings, bool processEnums, params string [] filenames) { var bgen = new BGenTool (); bgen.Profile = profile; + bgen.ProcessEnums = processEnums; bgen.Defines = BGenTool.GetDefaultDefines (bgen.Profile); bgen.CreateTemporaryBinding (filenames.Select ((filename) => File.ReadAllText (Path.Combine (Configuration.SourceRoot, "tests", "generator", filename))).ToArray ()); bgen.AssertExecute ("build"); diff --git a/tests/generator/ErrorTests.cs b/tests/generator/ErrorTests.cs index 091df65e3c..7daa1ddddd 100644 --- a/tests/generator/ErrorTests.cs +++ b/tests/generator/ErrorTests.cs @@ -535,6 +535,49 @@ namespace BI1062Tests { bgen.AssertError (1062, "The member 'FooObject.FooMethod' contains ref/out parameters and must not be decorated with [Async]."); } + [Test] + public void BI1063_NoDoubleWrapTest () + { + var bgen = new BGenTool { + Profile = Profile.iOS, + ProcessEnums = true + }; + bgen.CreateTemporaryBinding (@" +using System; +using Foundation; + +namespace BI1063Tests { + + enum PersonRelationship { + [Field (null)] + None, + + [Field (""INPersonRelationshipFather"", ""__Internal"")] + Father, + + [Field (""INPersonRelationshipMother"", ""__Internal"")] + Mother + } + + [BaseType (typeof (NSObject))] + interface Wrappers { + + // SmartEnum -- Normal Wrap getter Property + + [Export (""presenceType"")] + NSString _PresenceType { get; } + + [Wrap (""PersonRelationshipExtensions.GetValue (_PresenceType)"")] + PersonRelationship PresenceType { + [Wrap (""PersonRelationshipExtensions.GetValue (_PresenceType)"")] + get; + } + } +}"); + bgen.AssertExecuteError ("build"); + bgen.AssertError (1063, "The 'WrapAttribute' can only be used at the property or at getter/setter level at a given time. Property: 'BI1063Tests.Wrappers.PresenceType'"); + } + [Test] [TestCase (Profile.iOS)] [TestCase (Profile.macOSFull)] diff --git a/tests/generator/bug57870.cs b/tests/generator/bug57870.cs new file mode 100644 index 0000000000..191c8f61d7 --- /dev/null +++ b/tests/generator/bug57870.cs @@ -0,0 +1,62 @@ +using System; +using Foundation; + +namespace Bug57870 { + + enum PersonRelationship { + [Field (null)] + None, + + [Field ("INPersonRelationshipFather", "__Internal")] + Father, + + [Field ("INPersonRelationshipMother", "__Internal")] + Mother + } + + [BaseType (typeof (NSObject))] + interface Wrappers { + + // SmartEnum -- Normal Wrap getter Property + + [Export ("presenceType")] + NSString _PresenceType { get; } + + [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType)")] + PersonRelationship PresenceType { get; } + + // SmartEnum -- getter Wrap + NotImplemented setter + + [Export ("presenceType2")] + NSString _PresenceType2 { get; [NotImplemented] set; } + + PersonRelationship PresenceType2 { + [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType2)")] + get; + [NotImplemented ("Nope nope nope")] + set; + } + + // SmartEnum -- getter Wrap Only + + [Export ("presenceType3")] + NSString _PresenceType3 { get; } + + PersonRelationship PresenceType3 { + [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType3)")] + get; + } + + // SmartEnum -- Wrap getter and setter + + [Export ("presenceType4")] + NSString _PresenceType4 { get; set; } + + PersonRelationship PresenceType4 { + [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType4)")] + get; + [Wrap ("_PresenceType4 = value.GetConstant ()")] + set; + } + } +}