[generator] Teach generator about WrapAttribute on Getters and Setters (#3388)
* [generator] Teach generator about WrapAttribute on Getters and Setters https://bugzilla.xamarin.com/show_bug.cgi?id=57870 `WrapAttribute` can now be used in property getters and setters, this allows to Wrap virtually anything the way you need, for example smart enums, consider the following API definition: ```csharp // Smart enum. enum PersonRelationship { [Field (null)] None, [Field ("FMFather", "__Internal")] Father, [Field ("FMMother", "__Internal")] Mother } ``` ```csharp // Property definition. [Export ("presenceType")] NSString _PresenceType { get; set; } PersonRelationship PresenceType { [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType)")] get; [Wrap ("_PresenceType = value.GetConstant ()")] set; } ``` * Fix Feedback * Fix doc error * Update error message
This commit is contained in:
Родитель
e390fefe08
Коммит
dca6d79881
|
@ -1900,8 +1900,6 @@ var strongDemo = new Demo ();
|
|||
demo.Delegate = new MyDelegate ();
|
||||
```
|
||||
|
||||
<a name="Parameter_Attributes" />
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Parameter_Attributes" />
|
||||
|
||||
# Parameter Attributes
|
||||
|
||||
This section describes the attributes that you can apply to the parameters in
|
||||
|
|
|
@ -176,6 +176,8 @@ This usually indicates a bug in Xamarin.iOS/Xamarin.Mac; please [file a bug repo
|
|||
|
||||
### <a name='BI1062'/>BI1062: The member '*' contains ref/out parameters and must not be decorated with [Async].
|
||||
|
||||
### <a name='BI1063'/>BI1063: The 'WrapAttribute' can only be used at the property or at getter/setter level at a given time. Property: '*'.
|
||||
|
||||
# BI11xx: warnings
|
||||
|
||||
<!-- 11xx: warnings -->
|
||||
|
|
|
@ -283,6 +283,8 @@ public static class AttributeManager
|
|||
|
||||
public static T GetCustomAttribute<T> (ICustomAttributeProvider provider) where T : System.Attribute
|
||||
{
|
||||
if (provider is null)
|
||||
return null;
|
||||
var rv = GetCustomAttributes<T> (provider);
|
||||
if (rv == null || rv.Length == 0)
|
||||
return null;
|
||||
|
|
|
@ -426,13 +426,30 @@ public interface IMemberGatherer {
|
|||
IEnumerable<MethodInfo> 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<WrapAttribute> (pi?.GetMethod)?.MethodName;
|
||||
WrapSetter = AttributeManager.GetCustomAttribute<WrapAttribute> (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<CoreImageFilterPropertyAttribute> (pi))
|
||||
continue;
|
||||
|
||||
|
||||
if (AttributeManager.HasAttribute<WrapAttribute> (pi.GetGetMethod ()) || AttributeManager.HasAttribute<WrapAttribute> (pi.GetSetMethod ()))
|
||||
continue;
|
||||
|
||||
throw new BindingException (1018, true, "No [Export] attribute on property {0}.{1}", t.FullName, pi.Name);
|
||||
}
|
||||
if (AttributeManager.HasAttribute<StaticAttribute> (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<NotImplementedAttribute> (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){
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче