[generator] Do not generate `BindingImplOptions.Optimizable` when code snippets are provided (#12165)

unless the snippet-based attribute also set the `Optimizable` property.

IOW this is now required to _opt-in_ (to optimize) instead of default,
with no ~easy~ way to turn it off.

Re-enabled each of the `[Dispose]` cases after protecting the required
extra calls they make.

Fix https://github.com/xamarin/xamarin-macios/issues/12150

* Add test for [Dispose] and SnippetAttribute subclasses

* Opt-in all [*Snippet] to be optimizable

This is exactly what we have been shipping for years (no changes).

Unlike [Dispose] there is no change in the generated code or the
optimizations.

Future snippets should come with tests - which is _normal_ for any
manual code (which they are) added to the SDK
This commit is contained in:
Sebastien Pouliot 2021-07-27 09:36:22 -04:00 коммит произвёл GitHub
Родитель edc088ad7b
Коммит 1fa43189b5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 232 добавлений и 42 удалений

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

@ -103,6 +103,8 @@ namespace CoreAnimation {
} }
} }
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
[Preserve (Conditional = true)]
void OnDispose () void OnDispose ()
{ {
if (calayerdelegate != null) { if (calayerdelegate != null) {

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

@ -34,6 +34,8 @@ namespace UIKit {
// Called by the Dispose() method, because this can run from a finalizer, we need to // Called by the Dispose() method, because this can run from a finalizer, we need to
// (a) reference the handle, that we will release later, and (b) to remove the targets on the // (a) reference the handle, that we will release later, and (b) to remove the targets on the
// UI thread. // UI thread.
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
[Preserve (Conditional = true)]
void OnDispose () void OnDispose ()
{ {
var copyOfRecognizers = recognizers; var copyOfRecognizers = recognizers;

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

@ -15103,16 +15103,16 @@ namespace AppKit {
void WillRemoveSubview ([NullAllowed] NSView subview); void WillRemoveSubview ([NullAllowed] NSView subview);
[Export ("removeFromSuperview")] [Export ("removeFromSuperview")]
[PreSnippet ("var mySuper = Superview;")] [PreSnippet ("var mySuper = Superview;", Optimizable = true)]
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}")] [PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}", Optimizable = true)]
void RemoveFromSuperview (); void RemoveFromSuperview ();
[Export ("replaceSubview:with:")][PostGet ("Subviews")] [Export ("replaceSubview:with:")][PostGet ("Subviews")]
void ReplaceSubviewWith (NSView oldView, NSView newView); void ReplaceSubviewWith (NSView oldView, NSView newView);
[Export ("removeFromSuperviewWithoutNeedingDisplay")] [Export ("removeFromSuperviewWithoutNeedingDisplay")]
[PreSnippet ("var mySuper = Superview;")] [PreSnippet ("var mySuper = Superview;", Optimizable = true)]
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}")] [PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}", Optimizable = true)]
void RemoveFromSuperviewWithoutNeedingDisplay (); void RemoveFromSuperviewWithoutNeedingDisplay ();
[Export ("resizeSubviewsWithOldSize:")] [Export ("resizeSubviewsWithOldSize:")]
@ -19670,16 +19670,16 @@ namespace AppKit {
CGRect ContentRectFor (CGRect frameRect); CGRect ContentRectFor (CGRect frameRect);
[Export ("init")] [Export ("init")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")] [PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
IntPtr Constructor (); IntPtr Constructor ();
[DesignatedInitializer] [DesignatedInitializer]
[Export ("initWithContentRect:styleMask:backing:defer:")] [Export ("initWithContentRect:styleMask:backing:defer:")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")] [PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
IntPtr Constructor (CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation); IntPtr Constructor (CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation);
[Export ("initWithContentRect:styleMask:backing:defer:screen:")] [Export ("initWithContentRect:styleMask:backing:defer:screen:")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")] [PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
IntPtr Constructor (CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation, NSScreen screen); IntPtr Constructor (CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation, NSScreen screen);
[Export ("title")] [Export ("title")]

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

@ -12412,7 +12412,7 @@ namespace AVFoundation {
IntPtr Constructor (AVQueuePlayer player, AVPlayerItem itemToLoop, CMTimeRange loopRange); IntPtr Constructor (AVQueuePlayer player, AVPlayerItem itemToLoop, CMTimeRange loopRange);
#if !XAMCORE_4_0 // This API got introduced in Xcode 8.0 binding but is not currently present nor in Xcode 8.3 or Xcode 9.0 needs research #if !XAMCORE_4_0 // This API got introduced in Xcode 8.0 binding but is not currently present nor in Xcode 8.3 or Xcode 9.0 needs research
[PostSnippet ("loopingEnabled = false;")] [PostSnippet ("loopingEnabled = false;", Optimizable = true)]
#endif #endif
[Export ("disableLooping")] [Export ("disableLooping")]
void DisableLooping (); void DisableLooping ();

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

@ -186,7 +186,7 @@ namespace CoreAnimation {
} }
[BaseType (typeof (NSObject))] [BaseType (typeof (NSObject))]
[Dispose ("OnDispose ();")] [Dispose ("OnDispose ();", Optimizable = true)]
interface CALayer : CAMediaTiming, NSSecureCoding { interface CALayer : CAMediaTiming, NSSecureCoding {
[Export ("layer")][Static] [Export ("layer")][Static]
CALayer Create (); CALayer Create ();
@ -432,7 +432,7 @@ namespace CoreAnimation {
string Name { get; set; } string Name { get; set; }
[Export ("delegate", ArgumentSemantic.Weak)][NullAllowed] [Export ("delegate", ArgumentSemantic.Weak)][NullAllowed]
NSObject WeakDelegate { get; [PostSnippet (@"SetCALayerDelegate (value as CALayerDelegate);")] set; } NSObject WeakDelegate { get; [PostSnippet (@"SetCALayerDelegate (value as CALayerDelegate);", Optimizable = true)] set; }
[Wrap ("WeakDelegate")] [Wrap ("WeakDelegate")]
[Protocolize] [Protocolize]

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

@ -473,7 +473,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)] [Mac (10,15), iOS (13,0)]
[Static] [Static]
[Export ("loadFromHTMLWithRequest:options:completionHandler:")] [Export ("loadFromHTMLWithRequest:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")] [PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")] [Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)] [EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (NSUrlRequest request, NSDictionary options, NSAttributedStringCompletionHandler completionHandler); void LoadFromHtml (NSUrlRequest request, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -489,7 +489,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)] [Mac (10,15), iOS (13,0)]
[Static] [Static]
[Export ("loadFromHTMLWithFileURL:options:completionHandler:")] [Export ("loadFromHTMLWithFileURL:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")] [PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")] [Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)] [EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (NSUrl fileUrl, NSDictionary options, NSAttributedStringCompletionHandler completionHandler); void LoadFromHtml (NSUrl fileUrl, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -505,7 +505,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)] [Mac (10,15), iOS (13,0)]
[Static] [Static]
[Export ("loadFromHTMLWithString:options:completionHandler:")] [Export ("loadFromHTMLWithString:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")] [PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")] [Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)] [EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (string @string, NSDictionary options, NSAttributedStringCompletionHandler completionHandler); void LoadFromHtml (string @string, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -521,7 +521,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)] [Mac (10,15), iOS (13,0)]
[Static] [Static]
[Export ("loadFromHTMLWithData:options:completionHandler:")] [Export ("loadFromHTMLWithData:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")] [PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")] [Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)] [EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (NSData data, NSDictionary options, NSAttributedStringCompletionHandler completionHandler); void LoadFromHtml (NSData data, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -3634,11 +3634,11 @@ namespace Foundation
[BaseType (typeof (NSData))] [BaseType (typeof (NSData))]
interface NSMutableData { interface NSMutableData {
[Static, Export ("dataWithCapacity:")] [Autorelease] [Static, Export ("dataWithCapacity:")] [Autorelease]
[PreSnippet ("if (capacity < 0 || capacity > nint.MaxValue) throw new ArgumentOutOfRangeException ();")] [PreSnippet ("if (capacity < 0 || capacity > nint.MaxValue) throw new ArgumentOutOfRangeException ();", Optimizable = true)]
NSMutableData FromCapacity (nint capacity); NSMutableData FromCapacity (nint capacity);
[Static, Export ("dataWithLength:")] [Autorelease] [Static, Export ("dataWithLength:")] [Autorelease]
[PreSnippet ("if (length < 0 || length > nint.MaxValue) throw new ArgumentOutOfRangeException ();")] [PreSnippet ("if (length < 0 || length > nint.MaxValue) throw new ArgumentOutOfRangeException ();", Optimizable = true)]
NSMutableData FromLength (nint length); NSMutableData FromLength (nint length);
[Static, Export ("data")] [Autorelease] [Static, Export ("data")] [Autorelease]
@ -3648,7 +3648,7 @@ namespace Foundation
IntPtr MutableBytes { get; } IntPtr MutableBytes { get; }
[Export ("initWithCapacity:")] [Export ("initWithCapacity:")]
[PreSnippet ("if (capacity > (ulong) nint.MaxValue) throw new ArgumentOutOfRangeException ();")] [PreSnippet ("if (capacity > (ulong) nint.MaxValue) throw new ArgumentOutOfRangeException ();", Optimizable = true)]
IntPtr Constructor (nuint capacity); IntPtr Constructor (nuint capacity);
[Export ("appendData:")] [Export ("appendData:")]
@ -5050,7 +5050,7 @@ namespace Foundation
} }
[BaseType (typeof(NSObject))] [BaseType (typeof(NSObject))]
[Dispose ("if (disposing) { Invalidate (); } ")] [Dispose ("if (disposing) { Invalidate (); } ", Optimizable = true)]
// init returns NIL // init returns NIL
[DisableDefaultCtor] [DisableDefaultCtor]
interface NSTimer { interface NSTimer {
@ -5086,6 +5086,8 @@ namespace Foundation
[Export ("fireDate", ArgumentSemantic.Copy)] [Export ("fireDate", ArgumentSemantic.Copy)]
NSDate FireDate { get; set; } NSDate FireDate { get; set; }
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
[Preserve (Conditional = true)]
[Export ("invalidate")] [Export ("invalidate")]
void Invalidate (); void Invalidate ();
@ -5402,9 +5404,9 @@ namespace Foundation
// in turns, means that the Intents.framework is loaded into memory and this makes the // in turns, means that the Intents.framework is loaded into memory and this makes the
// selectors (getter and setter) work at runtime. Other selectors do not need it. // selectors (getter and setter) work at runtime. Other selectors do not need it.
// reference: https://github.com/xamarin/xamarin-macios/issues/4894 // reference: https://github.com/xamarin/xamarin-macios/issues/4894
[PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory")] [PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory", Optimizable = true)]
get; get;
[PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory")] [PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory", Optimizable = true)]
set; set;
} }
@ -8481,11 +8483,11 @@ namespace Foundation
[Export ("initWithCapacity:")] [Export ("initWithCapacity:")]
IntPtr Constructor (nint capacity); IntPtr Constructor (nint capacity);
[PreSnippet ("Check (index);")] [PreSnippet ("Check (index);", Optimizable = true)]
[Export ("insertString:atIndex:")] [Export ("insertString:atIndex:")]
void Insert (NSString str, nint index); void Insert (NSString str, nint index);
[PreSnippet ("Check (range);")] [PreSnippet ("Check (range);", Optimizable = true)]
[Export ("deleteCharactersInRange:")] [Export ("deleteCharactersInRange:")]
void DeleteCharacters (NSRange range); void DeleteCharacters (NSRange range);
@ -8495,7 +8497,7 @@ namespace Foundation
[Export ("setString:")] [Export ("setString:")]
void SetString (NSString str); void SetString (NSString str);
[PreSnippet ("Check (range);")] [PreSnippet ("Check (range);", Optimizable = true)]
[Export ("replaceOccurrencesOfString:withString:options:range:")] [Export ("replaceOccurrencesOfString:withString:options:range:")]
nuint ReplaceOcurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range); nuint ReplaceOcurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range);
@ -8840,12 +8842,12 @@ namespace Foundation
[Export ("methodForSelector:")] [Export ("methodForSelector:")]
IntPtr GetMethodForSelector (Selector sel); IntPtr GetMethodForSelector (Selector sel);
[PreSnippet ("if (!(this is INSCopying)) throw new InvalidOperationException (\"Type does not conform to NSCopying\");")] [PreSnippet ("if (!(this is INSCopying)) throw new InvalidOperationException (\"Type does not conform to NSCopying\");", Optimizable = true)]
[Export ("copy")] [Export ("copy")]
[return: Release ()] [return: Release ()]
NSObject Copy (); NSObject Copy ();
[PreSnippet ("if (!(this is INSMutableCopying)) throw new InvalidOperationException (\"Type does not conform to NSMutableCopying\");")] [PreSnippet ("if (!(this is INSMutableCopying)) throw new InvalidOperationException (\"Type does not conform to NSMutableCopying\");", Optimizable = true)]
[Export ("mutableCopy")] [Export ("mutableCopy")]
[return: Release ()] [return: Release ()]
NSObject MutableCopy (); NSObject MutableCopy ();
@ -10637,7 +10639,7 @@ namespace Foundation
NSNotificationCenter DefaultCenter { get; } NSNotificationCenter DefaultCenter { get; }
[Export ("addObserver:selector:name:object:")] [Export ("addObserver:selector:name:object:")]
[PostSnippet ("AddObserverToList (observer, aName, anObject);")] [PostSnippet ("AddObserverToList (observer, aName, anObject);", Optimizable = true)]
void AddObserver (NSObject observer, Selector aSelector, [NullAllowed] NSString aName, [NullAllowed] NSObject anObject); void AddObserver (NSObject observer, Selector aSelector, [NullAllowed] NSString aName, [NullAllowed] NSObject anObject);
[Export ("postNotification:")] [Export ("postNotification:")]
@ -10650,11 +10652,11 @@ namespace Foundation
void PostNotificationName (string aName, [NullAllowed] NSObject anObject, [NullAllowed] NSDictionary aUserInfo); void PostNotificationName (string aName, [NullAllowed] NSObject anObject, [NullAllowed] NSDictionary aUserInfo);
[Export ("removeObserver:")] [Export ("removeObserver:")]
[PostSnippet ("RemoveObserversFromList (observer, null, null);")] [PostSnippet ("RemoveObserversFromList (observer, null, null);", Optimizable = true)]
void RemoveObserver (NSObject observer); void RemoveObserver (NSObject observer);
[Export ("removeObserver:name:object:")] [Export ("removeObserver:name:object:")]
[PostSnippet ("RemoveObserversFromList (observer, aName, anObject);")] [PostSnippet ("RemoveObserversFromList (observer, aName, anObject);", Optimizable = true)]
void RemoveObserver (NSObject observer, [NullAllowed] string aName, [NullAllowed] NSObject anObject); void RemoveObserver (NSObject observer, [NullAllowed] string aName, [NullAllowed] NSObject anObject);
[Export ("addObserverForName:object:queue:usingBlock:")] [Export ("addObserverForName:object:queue:usingBlock:")]

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

@ -583,10 +583,13 @@ public class SnippetAttribute : Attribute {
Code = s; Code = s;
} }
public string Code { get; set; } public string Code { get; set; }
public bool Optimizable { get; set; }
} }
// //
// PreSnippet code is inserted after the parameters have been validated/marshalled // PreSnippet code is inserted after the parameters have been validated/marshalled
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
// //
public class PreSnippetAttribute : SnippetAttribute { public class PreSnippetAttribute : SnippetAttribute {
public PreSnippetAttribute (string s) : base (s) {} public PreSnippetAttribute (string s) : base (s) {}
@ -594,6 +597,7 @@ public class PreSnippetAttribute : SnippetAttribute {
// //
// PrologueSnippet code is inserted before any code is generated // PrologueSnippet code is inserted before any code is generated
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
// //
public class PrologueSnippetAttribute : SnippetAttribute { public class PrologueSnippetAttribute : SnippetAttribute {
public PrologueSnippetAttribute (string s) : base (s) {} public PrologueSnippetAttribute (string s) : base (s) {}
@ -601,13 +605,15 @@ public class PrologueSnippetAttribute : SnippetAttribute {
// //
// PostSnippet code is inserted before returning, before paramters are disposed/released // PostSnippet code is inserted before returning, before paramters are disposed/released
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
// //
public class PostSnippetAttribute : SnippetAttribute { public class PostSnippetAttribute : SnippetAttribute {
public PostSnippetAttribute (string s) : base (s) {} public PostSnippetAttribute (string s) : base (s) {}
} }
// //
// Code to run from a generated Dispose method // Code to run from a generated Dispose method, before any generated code is executed
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
// //
[AttributeUsage(AttributeTargets.Interface, AllowMultiple=true)] [AttributeUsage(AttributeTargets.Interface, AllowMultiple=true)]
public class DisposeAttribute : SnippetAttribute { public class DisposeAttribute : SnippetAttribute {

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

@ -4034,7 +4034,18 @@ public partial class Generator : IMemberGatherer {
print (l); print (l);
} }
} }
bool IsOptimizable (MemberInfo method)
{
var optimizable = true;
var snippets = AttributeManager.GetCustomAttributes<SnippetAttribute> (method);
if (snippets.Length > 0) {
foreach (SnippetAttribute snippet in snippets)
optimizable &= snippet.Optimizable;
}
return optimizable;
}
[Flags] [Flags]
public enum BodyOption { public enum BodyOption {
None = 0x0, None = 0x0,
@ -4956,7 +4967,7 @@ public partial class Generator : IMemberGatherer {
} }
} }
print_generated_code (); print_generated_code (optimizable: IsOptimizable (pi));
PrintPropertyAttributes (pi, minfo.type); PrintPropertyAttributes (pi, minfo.type);
PrintAttributes (pi, preserve:true, advice:true, bindAs:true); PrintAttributes (pi, preserve:true, advice:true, bindAs:true);
@ -5511,7 +5522,7 @@ public partial class Generator : IMemberGatherer {
var mod = minfo.GetVisibility (); var mod = minfo.GetVisibility ();
var is_abstract = minfo.is_abstract; var is_abstract = minfo.is_abstract;
print_generated_code (); print_generated_code (optimizable: IsOptimizable (minfo.mi));
print ("{0} {1}{2}{3}", print ("{0} {1}{2}{3}",
mod, mod,
minfo.GetModifiers (), minfo.GetModifiers (),
@ -7423,15 +7434,19 @@ public partial class Generator : IMemberGatherer {
// Do we need a dispose method? // Do we need a dispose method?
// //
if (!is_static_class){ if (!is_static_class){
var disposeAttr = AttributeManager.GetCustomAttributes<DisposeAttribute> (type); var attrs = AttributeManager.GetCustomAttributes<DisposeAttribute> (type);
if (disposeAttr.Length > 0 || instance_fields_to_clear_on_dispose.Count > 0){ // historical note: unlike many attributes our `DisposeAttribute` has `AllowMultiple=true`
print_generated_code (optimizable: disposeAttr.Length == 0); var has_dispose_attributes = attrs.Length > 0;
if (has_dispose_attributes || (instance_fields_to_clear_on_dispose.Count > 0)) {
// if there'a any [Dispose] attribute then they all must opt-in in order for the generated Dispose method to be optimizable
bool optimizable = !has_dispose_attributes || IsOptimizable (type);
print_generated_code (optimizable: optimizable);
print ("protected override void Dispose (bool disposing)"); print ("protected override void Dispose (bool disposing)");
print ("{"); print ("{");
indent++; indent++;
if (disposeAttr.Length > 0){ if (has_dispose_attributes) {
var snippet = disposeAttr [0]; foreach (var da in attrs)
Inject (snippet); Inject (da);
} }
print ("base.Dispose (disposing);"); print ("base.Dispose (disposing);");

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

@ -59,7 +59,7 @@ namespace SafariServices {
[Static, Export ("supportsURL:")] [Static, Export ("supportsURL:")]
// Apple says it's __nonnull so let's be safe and maintain compatibility with our current behaviour // Apple says it's __nonnull so let's be safe and maintain compatibility with our current behaviour
[PreSnippet ("if (url is null) return false;")] [PreSnippet ("if (url is null) return false;", Optimizable = true)]
bool SupportsUrl ([NullAllowed] NSUrl url); bool SupportsUrl ([NullAllowed] NSUrl url);
[Export ("addReadingListItemWithURL:title:previewText:error:")] [Export ("addReadingListItemWithURL:title:previewText:error:")]

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

@ -2035,7 +2035,7 @@ namespace UIKit {
void OpenUrl (NSUrl url, UIApplicationOpenUrlOptions options, [NullAllowed] Action<bool> completion); void OpenUrl (NSUrl url, UIApplicationOpenUrlOptions options, [NullAllowed] Action<bool> completion);
[Export ("canOpenURL:")] [Export ("canOpenURL:")]
[PreSnippet ("if (url is null) return false;")] // null not really allowed (but it's a behaviour change with known bug reports) [PreSnippet ("if (url is null) return false;", Optimizable = true)] // null not really allowed (but it's a behaviour change with known bug reports)
bool CanOpenUrl ([NullAllowed] NSUrl url); bool CanOpenUrl ([NullAllowed] NSUrl url);
[Export ("sendEvent:")] [Export ("sendEvent:")]
@ -5562,7 +5562,7 @@ namespace UIKit {
#if !WATCH #if !WATCH
[BaseType (typeof(NSObject), Delegates=new string [] {"WeakDelegate"}, Events=new Type[] {typeof (UIGestureRecognizerDelegate)})] [BaseType (typeof(NSObject), Delegates=new string [] {"WeakDelegate"}, Events=new Type[] {typeof (UIGestureRecognizerDelegate)})]
[Dispose ("OnDispose ();")] [Dispose ("OnDispose ();", Optimizable = true)]
interface UIGestureRecognizer { interface UIGestureRecognizer {
[DesignatedInitializer] [DesignatedInitializer]
[Export ("initWithTarget:action:")] [Export ("initWithTarget:action:")]
@ -13243,7 +13243,7 @@ namespace UIKit {
[Export ("typingAttributes", ArgumentSemantic.Copy)] [Export ("typingAttributes", ArgumentSemantic.Copy)]
NSDictionary TypingAttributes { NSDictionary TypingAttributes {
// this avoids a crash (see unit tests) and behave like UITextField does (return null) // this avoids a crash (see unit tests) and behave like UITextField does (return null)
[PreSnippet ("if (SelectedRange.Length == 0) return null;")] [PreSnippet ("if (SelectedRange.Length == 0) return null;", Optimizable = true)]
get; get;
set; set;
} }

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

@ -645,6 +645,85 @@ namespace GeneratorTests
[Test] [Test]
public void GHIssue9065_Sealed () => BuildFile (Profile.iOS, nowarnings: true, "ghissue9065.cs"); public void GHIssue9065_Sealed () => BuildFile (Profile.iOS, nowarnings: true, "ghissue9065.cs");
// looking for [BindingImpl (BindingImplOptions.Optimizable)]
bool IsOptimizable (MethodDefinition method)
{
const int Optimizable = 0x2; // BindingImplOptions flag
if (!method.HasCustomAttributes)
return false;
foreach (var ca in method.CustomAttributes) {
if (ca.AttributeType.Name != "BindingImplAttribute")
continue;
foreach (var a in ca.ConstructorArguments)
return (((int) a.Value & Optimizable) == Optimizable);
}
return false;
}
[Test]
public void DisposeAttributeOptimizable ()
{
var bgen = BuildFile (Profile.iOS, "tests/dispose-attribute.cs");
// processing custom attributes (like its properties) will call Resolve so we must be able to find the platform assembly to run this test
var platform_dll = Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/Xamarin.iOS.dll");
var resolver = bgen.ApiAssembly.MainModule.AssemblyResolver as BaseAssemblyResolver;
resolver.AddSearchDirectory (Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/"));
// [Dispose] is, by default, not optimizable
var with_dispose = bgen.ApiAssembly.MainModule.GetType ("NS", "WithDispose").Methods.First ((v) => v.Name == "Dispose");
Assert.NotNull (with_dispose, "WithDispose");
Assert.That (IsOptimizable (with_dispose), Is.False, "WithDispose/Optimizable");
// [Dispose] can opt-in being optimizable
var with_dispose_optin = bgen.ApiAssembly.MainModule.GetType ("NS", "WithDisposeOptInOptimizable").Methods.First ((v) => v.Name == "Dispose");
Assert.NotNull (with_dispose_optin, "WithDisposeOptInOptimizable");
Assert.That (IsOptimizable (with_dispose_optin), Is.True, "WithDisposeOptInOptimizable/Optimizable");
// Without a [Dispose] attribute the generated method is optimizable
var without_dispose = bgen.ApiAssembly.MainModule.GetType ("NS", "WithoutDispose").Methods.First ((v) => v.Name == "Dispose");
Assert.NotNull (without_dispose, "WitoutDispose");
Assert.That (IsOptimizable (without_dispose), Is.True, "WitoutDispose/Optimizable");
}
[Test]
public void SnippetAttributesOptimizable ()
{
var bgen = BuildFile (Profile.iOS, "tests/snippet-attributes.cs");
// processing custom attributes (like its properties) will call Resolve so we must be able to find the platform assembly to run this test
var platform_dll = Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/Xamarin.iOS.dll");
var resolver = bgen.ApiAssembly.MainModule.AssemblyResolver as BaseAssemblyResolver;
resolver.AddSearchDirectory (Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/"));
// [SnippetAttribute] subclasses are, by default, not optimizable
var not_opt = bgen.ApiAssembly.MainModule.GetType ("NS", "NotOptimizable");
Assert.NotNull (not_opt, "NotOptimizable");
var pre_not_opt = not_opt.Methods.First ((v) => v.Name == "Pre");
Assert.That (IsOptimizable (pre_not_opt), Is.False, "NotOptimizable/Pre");
var prologue_not_opt = not_opt.Methods.First ((v) => v.Name == "Prologue");
Assert.That (IsOptimizable (prologue_not_opt), Is.False, "NotOptimizable/Prologue");
var post_not_opt = not_opt.Methods.First ((v) => v.Name == "Post");
Assert.That (IsOptimizable (post_not_opt), Is.False, "NotOptimizable/Post");
// [SnippetAttribute] subclasses can opt-in being optimizable
var optin_opt = bgen.ApiAssembly.MainModule.GetType ("NS", "OptInOptimizable");
Assert.NotNull (optin_opt, "OptInOptimizable");
var pre_optin_opt = optin_opt.Methods.First ((v) => v.Name == "Pre");
Assert.That (IsOptimizable (pre_optin_opt), Is.True, "OptInOptimizable/Pre");
var prologue_optin_opt = optin_opt.Methods.First ((v) => v.Name == "Prologue");
Assert.That (IsOptimizable (prologue_optin_opt), Is.True, "OptInOptimizable/Prologue");
var post_optin_opt = optin_opt.Methods.First ((v) => v.Name == "Post");
Assert.That (IsOptimizable (post_optin_opt), Is.True, "OptInOptimizable/Post");
// Without a [SnippetAttribute] subclass attribute the generated method is optimizable
var nothing = bgen.ApiAssembly.MainModule.GetType ("NS", "NoSnippet").Methods.First ((v) => v.Name == "Nothing");
Assert.NotNull (nothing, "NoSnippet");
Assert.That (IsOptimizable (nothing), Is.True, "Nothing/Optimizable");
}
BGenTool BuildFile (Profile profile, params string [] filenames) BGenTool BuildFile (Profile profile, params string [] filenames)
{ {
return BuildFile (profile, true, false, filenames); return BuildFile (profile, true, false, filenames);

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

@ -0,0 +1,36 @@
using System;
using Foundation;
using ObjCRuntime;
namespace NS {
// injecting custom code makes the Dispose method not-optimizable by default
[Dispose ("Console.WriteLine (\"Disposing!\");")]
[BaseType (typeof (NSObject))]
interface WithDispose {
[Export ("delegate", ArgumentSemantic.Weak)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
// but we can opt-in to make it optimizable
[Dispose ("// just a comment, that's safe to optimize, if not very useful otherwise", Optimizable = true)]
[BaseType (typeof (NSObject))]
interface WithDisposeOptInOptimizable {
[Export ("delegate", ArgumentSemantic.Weak)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
// if nothing is injected then we know we generate code that our tools can optimize
[BaseType (typeof (NSObject))]
interface WithoutDispose {
// this ensure we have a Dispose method generated for the type
[Export ("delegate", ArgumentSemantic.Weak)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
}

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

@ -0,0 +1,48 @@
using System;
using Foundation;
using ObjCRuntime;
namespace NS {
// injecting custom code makes the method method not-optimizable by default
[BaseType (typeof (NSObject))]
interface NotOptimizable {
[PreSnippet ("Console.WriteLine (\"Pre!\");")]
[Export ("pre")]
void Pre ();
[PrologueSnippet ("Console.WriteLine (\"Prologue!\");")]
[Export ("prologue")]
void Prologue ();
[PostSnippet ("Console.WriteLine (\"Post!\");")]
[Export ("post")]
void Post ();
}
// but we can opt-in to make it optimizable
[BaseType (typeof (NSObject))]
interface OptInOptimizable {
[PreSnippet ("Console.WriteLine (\"Pre!\");", Optimizable = true)]
[Export ("pre")]
void Pre ();
[PrologueSnippet ("Console.WriteLine (\"Prologue!\");", Optimizable = true)]
[Export ("prologue")]
void Prologue ();
[PostSnippet ("Console.WriteLine (\"Post!\");", Optimizable = true)]
[Export ("post")]
void Post ();
}
// if nothing is injected then we know we generate code that our tools can optimize
[BaseType (typeof (NSObject))]
interface NoSnippet {
[Export ("nothing")]
void Nothing ();
}
}