[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:
Родитель
edc088ad7b
Коммит
1fa43189b5
|
@ -103,6 +103,8 @@ namespace CoreAnimation {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
|
||||
[Preserve (Conditional = true)]
|
||||
void OnDispose ()
|
||||
{
|
||||
if (calayerdelegate != null) {
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace UIKit {
|
|||
// 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
|
||||
// UI thread.
|
||||
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
|
||||
[Preserve (Conditional = true)]
|
||||
void OnDispose ()
|
||||
{
|
||||
var copyOfRecognizers = recognizers;
|
||||
|
|
|
@ -15103,16 +15103,16 @@ namespace AppKit {
|
|||
void WillRemoveSubview ([NullAllowed] NSView subview);
|
||||
|
||||
[Export ("removeFromSuperview")]
|
||||
[PreSnippet ("var mySuper = Superview;")]
|
||||
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}")]
|
||||
[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}", Optimizable = true)]
|
||||
void RemoveFromSuperview ();
|
||||
|
||||
[Export ("replaceSubview:with:")][PostGet ("Subviews")]
|
||||
void ReplaceSubviewWith (NSView oldView, NSView newView);
|
||||
|
||||
[Export ("removeFromSuperviewWithoutNeedingDisplay")]
|
||||
[PreSnippet ("var mySuper = Superview;")]
|
||||
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}")]
|
||||
[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}", Optimizable = true)]
|
||||
void RemoveFromSuperviewWithoutNeedingDisplay ();
|
||||
|
||||
[Export ("resizeSubviewsWithOldSize:")]
|
||||
|
@ -19670,16 +19670,16 @@ namespace AppKit {
|
|||
CGRect ContentRectFor (CGRect frameRect);
|
||||
|
||||
[Export ("init")]
|
||||
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")]
|
||||
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
|
||||
IntPtr Constructor ();
|
||||
|
||||
[DesignatedInitializer]
|
||||
[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);
|
||||
|
||||
[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);
|
||||
|
||||
[Export ("title")]
|
||||
|
|
|
@ -12412,7 +12412,7 @@ namespace AVFoundation {
|
|||
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
|
||||
[PostSnippet ("loopingEnabled = false;")]
|
||||
[PostSnippet ("loopingEnabled = false;", Optimizable = true)]
|
||||
#endif
|
||||
[Export ("disableLooping")]
|
||||
void DisableLooping ();
|
||||
|
|
|
@ -186,7 +186,7 @@ namespace CoreAnimation {
|
|||
}
|
||||
|
||||
[BaseType (typeof (NSObject))]
|
||||
[Dispose ("OnDispose ();")]
|
||||
[Dispose ("OnDispose ();", Optimizable = true)]
|
||||
interface CALayer : CAMediaTiming, NSSecureCoding {
|
||||
[Export ("layer")][Static]
|
||||
CALayer Create ();
|
||||
|
@ -432,7 +432,7 @@ namespace CoreAnimation {
|
|||
string Name { get; set; }
|
||||
|
||||
[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")]
|
||||
[Protocolize]
|
||||
|
|
|
@ -473,7 +473,7 @@ namespace Foundation
|
|||
[Mac (10,15), iOS (13,0)]
|
||||
[Static]
|
||||
[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")]
|
||||
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
||||
void LoadFromHtml (NSUrlRequest request, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
|
||||
|
@ -489,7 +489,7 @@ namespace Foundation
|
|||
[Mac (10,15), iOS (13,0)]
|
||||
[Static]
|
||||
[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")]
|
||||
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
||||
void LoadFromHtml (NSUrl fileUrl, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
|
||||
|
@ -505,7 +505,7 @@ namespace Foundation
|
|||
[Mac (10,15), iOS (13,0)]
|
||||
[Static]
|
||||
[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")]
|
||||
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
||||
void LoadFromHtml (string @string, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
|
||||
|
@ -521,7 +521,7 @@ namespace Foundation
|
|||
[Mac (10,15), iOS (13,0)]
|
||||
[Static]
|
||||
[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")]
|
||||
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
||||
void LoadFromHtml (NSData data, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
|
||||
|
@ -3634,11 +3634,11 @@ namespace Foundation
|
|||
[BaseType (typeof (NSData))]
|
||||
interface NSMutableData {
|
||||
[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);
|
||||
|
||||
[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);
|
||||
|
||||
[Static, Export ("data")] [Autorelease]
|
||||
|
@ -3648,7 +3648,7 @@ namespace Foundation
|
|||
IntPtr MutableBytes { get; }
|
||||
|
||||
[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);
|
||||
|
||||
[Export ("appendData:")]
|
||||
|
@ -5050,7 +5050,7 @@ namespace Foundation
|
|||
}
|
||||
|
||||
[BaseType (typeof(NSObject))]
|
||||
[Dispose ("if (disposing) { Invalidate (); } ")]
|
||||
[Dispose ("if (disposing) { Invalidate (); } ", Optimizable = true)]
|
||||
// init returns NIL
|
||||
[DisableDefaultCtor]
|
||||
interface NSTimer {
|
||||
|
@ -5086,6 +5086,8 @@ namespace Foundation
|
|||
[Export ("fireDate", ArgumentSemantic.Copy)]
|
||||
NSDate FireDate { get; set; }
|
||||
|
||||
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
|
||||
[Preserve (Conditional = true)]
|
||||
[Export ("invalidate")]
|
||||
void Invalidate ();
|
||||
|
||||
|
@ -5402,9 +5404,9 @@ namespace Foundation
|
|||
// 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.
|
||||
// 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;
|
||||
[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;
|
||||
}
|
||||
|
||||
|
@ -8481,11 +8483,11 @@ namespace Foundation
|
|||
[Export ("initWithCapacity:")]
|
||||
IntPtr Constructor (nint capacity);
|
||||
|
||||
[PreSnippet ("Check (index);")]
|
||||
[PreSnippet ("Check (index);", Optimizable = true)]
|
||||
[Export ("insertString:atIndex:")]
|
||||
void Insert (NSString str, nint index);
|
||||
|
||||
[PreSnippet ("Check (range);")]
|
||||
[PreSnippet ("Check (range);", Optimizable = true)]
|
||||
[Export ("deleteCharactersInRange:")]
|
||||
void DeleteCharacters (NSRange range);
|
||||
|
||||
|
@ -8495,7 +8497,7 @@ namespace Foundation
|
|||
[Export ("setString:")]
|
||||
void SetString (NSString str);
|
||||
|
||||
[PreSnippet ("Check (range);")]
|
||||
[PreSnippet ("Check (range);", Optimizable = true)]
|
||||
[Export ("replaceOccurrencesOfString:withString:options:range:")]
|
||||
nuint ReplaceOcurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range);
|
||||
|
||||
|
@ -8840,12 +8842,12 @@ namespace Foundation
|
|||
[Export ("methodForSelector:")]
|
||||
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")]
|
||||
[return: Release ()]
|
||||
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")]
|
||||
[return: Release ()]
|
||||
NSObject MutableCopy ();
|
||||
|
@ -10637,7 +10639,7 @@ namespace Foundation
|
|||
NSNotificationCenter DefaultCenter { get; }
|
||||
|
||||
[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);
|
||||
|
||||
[Export ("postNotification:")]
|
||||
|
@ -10650,11 +10652,11 @@ namespace Foundation
|
|||
void PostNotificationName (string aName, [NullAllowed] NSObject anObject, [NullAllowed] NSDictionary aUserInfo);
|
||||
|
||||
[Export ("removeObserver:")]
|
||||
[PostSnippet ("RemoveObserversFromList (observer, null, null);")]
|
||||
[PostSnippet ("RemoveObserversFromList (observer, null, null);", Optimizable = true)]
|
||||
void RemoveObserver (NSObject observer);
|
||||
|
||||
[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);
|
||||
|
||||
[Export ("addObserverForName:object:queue:usingBlock:")]
|
||||
|
|
|
@ -583,10 +583,13 @@ public class SnippetAttribute : Attribute {
|
|||
Code = s;
|
||||
}
|
||||
public string Code { get; set; }
|
||||
|
||||
public bool Optimizable { get; set; }
|
||||
}
|
||||
|
||||
//
|
||||
// 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 PreSnippetAttribute (string s) : base (s) {}
|
||||
|
@ -594,6 +597,7 @@ public class PreSnippetAttribute : SnippetAttribute {
|
|||
|
||||
//
|
||||
// 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 PrologueSnippetAttribute (string s) : base (s) {}
|
||||
|
@ -601,13 +605,15 @@ public class PrologueSnippetAttribute : SnippetAttribute {
|
|||
|
||||
//
|
||||
// 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 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)]
|
||||
public class DisposeAttribute : SnippetAttribute {
|
||||
|
|
|
@ -4034,7 +4034,18 @@ public partial class Generator : IMemberGatherer {
|
|||
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]
|
||||
public enum BodyOption {
|
||||
None = 0x0,
|
||||
|
@ -4956,7 +4967,7 @@ public partial class Generator : IMemberGatherer {
|
|||
}
|
||||
}
|
||||
|
||||
print_generated_code ();
|
||||
print_generated_code (optimizable: IsOptimizable (pi));
|
||||
PrintPropertyAttributes (pi, minfo.type);
|
||||
|
||||
PrintAttributes (pi, preserve:true, advice:true, bindAs:true);
|
||||
|
@ -5511,7 +5522,7 @@ public partial class Generator : IMemberGatherer {
|
|||
var mod = minfo.GetVisibility ();
|
||||
|
||||
var is_abstract = minfo.is_abstract;
|
||||
print_generated_code ();
|
||||
print_generated_code (optimizable: IsOptimizable (minfo.mi));
|
||||
print ("{0} {1}{2}{3}",
|
||||
mod,
|
||||
minfo.GetModifiers (),
|
||||
|
@ -7423,15 +7434,19 @@ public partial class Generator : IMemberGatherer {
|
|||
// Do we need a dispose method?
|
||||
//
|
||||
if (!is_static_class){
|
||||
var disposeAttr = AttributeManager.GetCustomAttributes<DisposeAttribute> (type);
|
||||
if (disposeAttr.Length > 0 || instance_fields_to_clear_on_dispose.Count > 0){
|
||||
print_generated_code (optimizable: disposeAttr.Length == 0);
|
||||
var attrs = AttributeManager.GetCustomAttributes<DisposeAttribute> (type);
|
||||
// historical note: unlike many attributes our `DisposeAttribute` has `AllowMultiple=true`
|
||||
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 ("{");
|
||||
indent++;
|
||||
if (disposeAttr.Length > 0){
|
||||
var snippet = disposeAttr [0];
|
||||
Inject (snippet);
|
||||
if (has_dispose_attributes) {
|
||||
foreach (var da in attrs)
|
||||
Inject (da);
|
||||
}
|
||||
|
||||
print ("base.Dispose (disposing);");
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace SafariServices {
|
|||
|
||||
[Static, Export ("supportsURL:")]
|
||||
// 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);
|
||||
|
||||
[Export ("addReadingListItemWithURL:title:previewText:error:")]
|
||||
|
|
|
@ -2035,7 +2035,7 @@ namespace UIKit {
|
|||
void OpenUrl (NSUrl url, UIApplicationOpenUrlOptions options, [NullAllowed] Action<bool> completion);
|
||||
|
||||
[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);
|
||||
|
||||
[Export ("sendEvent:")]
|
||||
|
@ -5562,7 +5562,7 @@ namespace UIKit {
|
|||
|
||||
#if !WATCH
|
||||
[BaseType (typeof(NSObject), Delegates=new string [] {"WeakDelegate"}, Events=new Type[] {typeof (UIGestureRecognizerDelegate)})]
|
||||
[Dispose ("OnDispose ();")]
|
||||
[Dispose ("OnDispose ();", Optimizable = true)]
|
||||
interface UIGestureRecognizer {
|
||||
[DesignatedInitializer]
|
||||
[Export ("initWithTarget:action:")]
|
||||
|
@ -13243,7 +13243,7 @@ namespace UIKit {
|
|||
[Export ("typingAttributes", ArgumentSemantic.Copy)]
|
||||
NSDictionary TypingAttributes {
|
||||
// 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;
|
||||
set;
|
||||
}
|
||||
|
|
|
@ -645,6 +645,85 @@ namespace GeneratorTests
|
|||
[Test]
|
||||
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)
|
||||
{
|
||||
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 ();
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче