Skip to content

Commit

Permalink
[Foundation] Improve the NSAttributedString constructors. Fixes #14489.…
Browse files Browse the repository at this point in the history
… (#21727)

Managed constructors can't fail gracefully, which means that constructors with
an "out NSError" parameter doesn't make much sense. Instead bind these
constructors using a factory method.

Ref: #16804 (comment)

Fixes #14489.
  • Loading branch information
rolfbjarne authored Dec 2, 2024
1 parent c2a90c3 commit 5b721d5
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 13 deletions.
52 changes: 52 additions & 0 deletions src/Foundation/NSAttributedString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,58 @@
namespace Foundation {
public partial class NSAttributedString {

/// <summary>Create a new <see cref="NSAttributedString" />.</summary>
/// <param name="url">A url to the document to load.</param>
/// <param name="options">A dictionary of attributes that specifies how to interpret the document contents.</param>
/// <param name="resultDocumentAttributes">Upon return, a dictionary of document-specific keys.</param>
/// <param name="error">The error if an error occurred.</param>
public static NSAttributedString? Create (NSUrl url, NSDictionary options, out NSDictionary resultDocumentAttributes, out NSError error)
{
var rv = new NSAttributedString (NSObjectFlag.Empty);
rv.InitializeHandle (rv._InitWithUrl (url, options, out resultDocumentAttributes, out error), string.Empty, false);
if (rv.Handle == IntPtr.Zero) {
rv.Dispose ();
return null;
}
return rv;
}

/// <summary>Create a new <see cref="NSAttributedString" />.</summary>
/// <param name="url">A url to the document to load.</param>
/// <param name="options">A dictionary of attributes that specifies how to interpret the document contents.</param>
/// <param name="resultDocumentAttributes">Upon return, a dictionary of document-specific keys.</param>
/// <param name="error">The error if an error occurred.</param>
public static NSAttributedString? Create (NSUrl url, NSAttributedStringDocumentAttributes options, out NSDictionary resultDocumentAttributes, out NSError error)
{
return Create (url, options.Dictionary, out resultDocumentAttributes, out error);
}

/// <summary>Create a new <see cref="NSAttributedString" />.</summary>
/// <param name="data">The data to load.</param>
/// <param name="options">A dictionary of attributes that specifies how to interpret the document contents.</param>
/// <param name="resultDocumentAttributes">Upon return, a dictionary of document-specific keys.</param>
/// <param name="error">The error if an error occurred.</param>
public static NSAttributedString? Create (NSData data, NSDictionary options, out NSDictionary resultDocumentAttributes, out NSError error)
{
var rv = new NSAttributedString (NSObjectFlag.Empty);
rv.InitializeHandle (rv._InitWithData (data, options, out resultDocumentAttributes, out error), string.Empty, false);
if (rv.Handle == IntPtr.Zero) {
rv.Dispose ();
return null;
}
return rv;
}

/// <summary>Create a new <see cref="NSAttributedString" />.</summary>
/// <param name="data">The data to load.</param>
/// <param name="options">A dictionary of attributes that specifies how to interpret the document contents.</param>
/// <param name="resultDocumentAttributes">Upon return, a dictionary of document-specific keys.</param>
/// <param name="error">The error if an error occurred.</param>
public static NSAttributedString? Create (NSData data, NSAttributedStringDocumentAttributes options, out NSDictionary resultDocumentAttributes, out NSError error)
{
return Create (data, options.Dictionary, out resultDocumentAttributes, out error);
}

#if __MACOS__ || XAMCORE_5_0
public NSAttributedString (NSUrl url, NSAttributedStringDocumentAttributes documentAttributes, out NSError error)
: this (url, documentAttributes, out var _, out error) {}
Expand Down
12 changes: 9 additions & 3 deletions src/Foundation/NSObject2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -665,19 +665,25 @@ public NativeHandle Handle {
[EditorBrowsable (EditorBrowsableState.Never)]
protected void InitializeHandle (NativeHandle handle)
{
InitializeHandle (handle, "init*");
InitializeHandle (handle, "init*", Class.ThrowOnInitFailure);
}

[EditorBrowsable (EditorBrowsableState.Never)]
protected void InitializeHandle (NativeHandle handle, string initSelector)
{
if (this.handle == NativeHandle.Zero && Class.ThrowOnInitFailure) {
InitializeHandle (handle, initSelector, Class.ThrowOnInitFailure);
}

[EditorBrowsable (EditorBrowsableState.Never)]
internal void InitializeHandle (NativeHandle handle, string initSelector, bool throwOnInitFailure)
{
if (this.handle == NativeHandle.Zero && throwOnInitFailure) {
if (ClassHandle == NativeHandle.Zero)
throw new Exception ($"Could not create an native instance of the type '{GetType ().FullName}': the native class hasn't been loaded.\n{Constants.SetThrowOnInitFailureToFalse}.");
throw new Exception ($"Could not create an native instance of the type '{new Class (ClassHandle).Name}'.\n{Constants.SetThrowOnInitFailureToFalse}.");
}

if (handle == NativeHandle.Zero && Class.ThrowOnInitFailure) {
if (handle == NativeHandle.Zero && throwOnInitFailure) {
Handle = NativeHandle.Zero; // We'll crash if we don't do this.
throw new Exception ($"Could not initialize an instance of the type '{GetType ().FullName}': the native '{initSelector}' method returned nil.\n{Constants.SetThrowOnInitFailureToFalse}.");
}
Expand Down
32 changes: 26 additions & 6 deletions src/foundation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,37 +354,57 @@ partial interface NSAttributedString : NSCoding, NSMutableCopying, NSSecureCodin
[Export ("enumerateAttribute:inRange:options:usingBlock:")]
void EnumerateAttribute (NSString attributeName, NSRange inRange, NSAttributedStringEnumeration options, NSAttributedStringCallback callback);

#if !XAMCORE_5_0
[Obsolete ("Use the 'Create' method instead, because there's no way to return an error from a constructor.")]
[Export ("initWithURL:options:documentAttributes:error:")]
#if !(__MACOS__ || XAMCORE_5_0)
#if !__MACOS__
NativeHandle Constructor (NSUrl url, NSDictionary options, out NSDictionary resultDocumentAttributes, ref NSError error);
#else
NativeHandle Constructor (NSUrl url, NSDictionary options, out NSDictionary resultDocumentAttributes, out NSError error);
#endif
#endif // !XAMCORE_5_0

[Internal]
[Sealed]
[Export ("initWithURL:options:documentAttributes:error:")]
NativeHandle _InitWithUrl (NSUrl url, NSDictionary options, out NSDictionary resultDocumentAttributes, out NSError error);

#if !XAMCORE_5_0
[Obsolete ("Use the 'Create' method instead, because there's no way to return an error from a constructor.")]
[Export ("initWithData:options:documentAttributes:error:")]
#if XAMCORE_5_0
NativeHandle Constructor (NSData data, NSDictionary options, out NSDictionary resultDocumentAttributes, out NSError error);
#elif __MACOS__
#if __MACOS__
NativeHandle Constructor (NSData data, NSDictionary options, out NSDictionary docAttributes, out NSError error);
#else
NativeHandle Constructor (NSData data, NSDictionary options, out NSDictionary resultDocumentAttributes, ref NSError error);
#endif
#endif // !XAMCORE_5_0

#if __MACOS__ || XAMCORE_5_0
[Internal]
[Sealed]
[Export ("initWithData:options:documentAttributes:error:")]
NativeHandle _InitWithData (NSData data, NSDictionary options, out NSDictionary resultDocumentAttributes, out NSError error);

#if !XAMCORE_5_0
[Obsolete ("Use the 'Create' method instead, because there's no way to return an error from a constructor.")]
#if __MACOS__
[Wrap ("this (url, options.GetDictionary ()!, out resultDocumentAttributes, out error)")]
NativeHandle Constructor (NSUrl url, NSAttributedStringDocumentAttributes options, out NSDictionary resultDocumentAttributes, out NSError error);
#else
[Wrap ("this (url, options.GetDictionary ()!, out resultDocumentAttributes, ref error)")]
NativeHandle Constructor (NSUrl url, NSAttributedStringDocumentAttributes options, out NSDictionary resultDocumentAttributes, ref NSError error);
#endif
#endif // !XAMCORE_5_0

#if __MACOS__ || XAMCORE_5_0
[Obsolete ("Use the 'Create' method instead, because there's no way to return an error from a constructor.")]
#if !XAMCORE_5_0
#if __MACOS__
[Wrap ("this (data, options.GetDictionary ()!, out resultDocumentAttributes, out error)")]
NativeHandle Constructor (NSData data, NSAttributedStringDocumentAttributes options, out NSDictionary resultDocumentAttributes, out NSError error);
#else
[Wrap ("this (data, options.GetDictionary ()!, out resultDocumentAttributes, ref error)")]
NativeHandle Constructor (NSData data, NSAttributedStringDocumentAttributes options, out NSDictionary resultDocumentAttributes, ref NSError error);
#endif
#endif // !XAMCORE_5_0

[NoiOS]
[NoMacCatalyst]
Expand Down
4 changes: 0 additions & 4 deletions tests/cecil-tests/Documentation.KnownFailures.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33263,18 +33263,14 @@ M:Foundation.NSArray`1.#ctor(Foundation.NSCoder)
M:Foundation.NSArray`1.FromNSObjects(`0[])
M:Foundation.NSArray`1.FromNSObjects(System.Int32,`0[])
M:Foundation.NSArray`1.ToArray
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSAttributedStringDocumentAttributes,Foundation.NSDictionary@,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSAttributedStringDocumentAttributes,Foundation.NSDictionary@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSAttributedStringDocumentAttributes,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSDictionary,Foundation.NSDictionary@,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSDictionary,Foundation.NSDictionary@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSDictionary@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSData,Foundation.NSUrl,Foundation.NSDictionary@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSFileWrapper,Foundation.NSDictionary@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSUrl,Foundation.NSAttributedStringDocumentAttributes,Foundation.NSDictionary@,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSUrl,Foundation.NSAttributedStringDocumentAttributes,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSUrl,Foundation.NSDictionary,Foundation.NSDictionary@,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSUrl,Foundation.NSDictionary@)
M:Foundation.NSAttributedString.#ctor(Foundation.NSUrl,Foundation.NSError@)
M:Foundation.NSAttributedString.#ctor(System.String,AppKit.NSFont,AppKit.NSColor,AppKit.NSColor,AppKit.NSColor,AppKit.NSColor,AppKit.NSColor,Foundation.NSUnderlineStyle,Foundation.NSUnderlineStyle,AppKit.NSParagraphStyle,System.Single,AppKit.NSShadow,Foundation.NSUrl,System.Boolean,AppKit.NSTextAttachment,Foundation.NSLigatureType,System.Single,System.Single,System.Single,System.Single,AppKit.NSCursor,System.String,System.Int32,AppKit.NSGlyphInfo,Foundation.NSArray,System.Boolean,AppKit.NSTextLayoutOrientation,AppKit.NSTextAlternatives,AppKit.NSSpellingState)
Expand Down
38 changes: 38 additions & 0 deletions tests/monotouch-test/Foundation/AttributedStringTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.IO;

using NUnit.Framework;
using Foundation;
#if MONOMAC
Expand Down Expand Up @@ -135,6 +137,42 @@ public void NullDictionary ()
}
}

[Test]
public void Create_Url_Error ()
{
var obj = NSAttributedString.Create (new NSUrl (""), new NSAttributedStringDocumentAttributes (), out var rda, out var e);
Assert.IsNull (obj, "IsNull");
Assert.IsNotNull (e, "Error");
}

[Test]
public void Create_Url ()
{
var textFile = Path.Combine (NSBundle.MainBundle.ResourcePath, "uncompressed.txt");
var textUrl = NSUrl.CreateFileUrl (textFile);
var obj = NSAttributedString.Create (textUrl, new NSAttributedStringDocumentAttributes (), out var rda, out var e);
Assert.IsNull (e, "Error");
Assert.IsNotNull (obj, "IsNull");
}

[Test]
public void Create_Data_Error ()
{
var attributes = new NSAttributedStringDocumentAttributes ();
attributes.DocumentType = NSDocumentType.RTF;
var obj = NSAttributedString.Create (NSData.FromArray (new byte [42]), attributes, out var rda, out var e);
Assert.IsNull (obj, "IsNull");
Assert.IsNotNull (e, "Error");
}

[Test]
public void Create_Data ()
{
var obj = NSAttributedString.Create (new NSData (), new NSAttributedStringDocumentAttributes (), out var rda, out var e);
Assert.IsNotNull (obj, "IsNull");
Assert.IsNull (e, "Error");
}

#if !__WATCHOS__
[Test]
public void IndirectNullDictionary ()
Expand Down

6 comments on commit 5b721d5

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.