[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:
Alex Soto 2018-02-05 21:14:08 -06:00 коммит произвёл GitHub
Родитель e390fefe08
Коммит dca6d79881
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 220 добавлений и 4 удалений

Просмотреть файл

@ -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;
}
}
}