diff --git a/objc2/CHANGELOG_FOUNDATION.md b/objc2/CHANGELOG_FOUNDATION.md index b290e2c5b..c85a7672d 100644 --- a/objc2/CHANGELOG_FOUNDATION.md +++ b/objc2/CHANGELOG_FOUNDATION.md @@ -8,6 +8,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD +### Added +* Added `NSSet`. +* Added `NSMutableSet`. +* Added `NSMutableDictionary`. +* Added `NSNotFound`. +* Added `NSBundle`. +* Added `NSTimeInterval`. +* Added `NSString::len_utf16` and `NSAttributedString::len_utf16`. +* Added `NSString::concat` and `NSString::join_path`. + + +## objc2 0.3.0-beta.2 - 2022-08-28 + ### Added * Added `NSNumber`. * Added `NSError`. diff --git a/objc2/src/foundation/attributed_string.rs b/objc2/src/foundation/attributed_string.rs index 57cd8ec0d..98e7ed34d 100644 --- a/objc2/src/foundation/attributed_string.rs +++ b/objc2/src/foundation/attributed_string.rs @@ -85,9 +85,7 @@ extern_methods!( /// Alias for `self.string().len_utf16()`. #[doc(alias = "length")] #[sel(length)] - #[allow(unused)] - // TODO: Finish this - fn len_utf16(&self) -> usize; + pub fn len_utf16(&self) -> usize; // /// TODO // /// diff --git a/objc2/src/foundation/bundle.rs b/objc2/src/foundation/bundle.rs new file mode 100644 index 000000000..c946545b1 --- /dev/null +++ b/objc2/src/foundation/bundle.rs @@ -0,0 +1,73 @@ +use core::fmt; +use core::panic::{RefUnwindSafe, UnwindSafe}; + +use super::{NSCopying, NSDictionary, NSObject, NSString}; +use crate::rc::{Id, Shared}; +use crate::{extern_class, extern_methods, msg_send_id, ns_string, ClassType}; + +extern_class!( + /// A representation of the code and resources stored in a bundle + /// directory on disk. + /// + /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsbundle?language=objc). + #[derive(PartialEq, Eq, Hash)] + pub struct NSBundle; + + unsafe impl ClassType for NSBundle { + type Super = NSObject; + } +); + +// SAFETY: Bundles are documented as thread-safe. +unsafe impl Sync for NSBundle {} +unsafe impl Send for NSBundle {} + +impl UnwindSafe for NSBundle {} +impl RefUnwindSafe for NSBundle {} + +extern_methods!( + unsafe impl NSBundle { + pub fn main() -> Id { + unsafe { msg_send_id![Self::class(), mainBundle] } + } + + pub fn info(&self) -> Id, Shared> { + unsafe { msg_send_id![self, infoDictionary] } + } + + pub fn name(&self) -> Option> { + self.info().get(ns_string!("CFBundleName")).map(|name| { + let ptr: *const NSObject = name; + let ptr: *const NSString = ptr.cast(); + // SAFETY: TODO + let name = unsafe { ptr.as_ref().unwrap_unchecked() }; + name.copy() + }) + } + } +); + +impl fmt::Debug for NSBundle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Delegate to NSObject + (**self).fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + use std::println; + + #[test] + #[cfg_attr(not(target_os = "macos"), ignore = "varies between platforms")] + fn try_running_functions() { + // This is mostly empty since cargo doesn't bundle the application + // before executing. + let bundle = NSBundle::main(); + println!("{:?}", bundle); + assert_eq!(format!("{:?}", bundle.info()), "{}"); + assert_eq!(bundle.name(), None); + } +} diff --git a/objc2/src/foundation/mod.rs b/objc2/src/foundation/mod.rs index f5a0af87a..32cfb135a 100644 --- a/objc2/src/foundation/mod.rs +++ b/objc2/src/foundation/mod.rs @@ -51,8 +51,11 @@ #![allow(missing_docs)] #![allow(clippy::missing_safety_doc)] +use std::os::raw::c_double; + pub use self::array::NSArray; pub use self::attributed_string::{NSAttributedString, NSAttributedStringKey}; +pub use self::bundle::NSBundle; pub use self::comparison_result::NSComparisonResult; pub use self::copying::{NSCopying, NSMutableCopying}; pub use self::data::NSData; @@ -84,6 +87,17 @@ pub use self::zone::NSZone; #[doc(no_inline)] pub use crate::ffi::{NSInteger, NSUInteger}; +/// A value indicating that a requested item couldn’t be found or doesn’t exist. +/// +/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsnotfound?language=objc). +#[allow(non_upper_case_globals)] +pub const NSNotFound: NSInteger = crate::ffi::NSIntegerMax; + +/// A number of seconds. +/// +/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nstimeinterval?language=objc). +pub type NSTimeInterval = c_double; + #[cfg(feature = "apple")] #[link(name = "Foundation", kind = "framework")] extern "C" {} @@ -96,6 +110,7 @@ extern "C" {} pub mod __ns_string; mod array; mod attributed_string; +mod bundle; mod comparison_result; mod copying; mod data; diff --git a/objc2/src/foundation/string.rs b/objc2/src/foundation/string.rs index 3b98d252e..3962c4f60 100644 --- a/objc2/src/foundation/string.rs +++ b/objc2/src/foundation/string.rs @@ -10,7 +10,6 @@ use core::str; use std::os::raw::c_char; use super::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NSObject}; -use crate::ffi; use crate::rc::{autoreleasepool, AutoreleasePool, DefaultId, Id, Shared}; use crate::runtime::{Class, Object}; use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; @@ -20,10 +19,6 @@ const UTF8_ENCODING: usize = 4; #[cfg(feature = "gnustep-1-7")] const UTF8_ENCODING: i32 = 4; -#[allow(unused)] -#[allow(non_upper_case_globals)] -const NSNotFound: ffi::NSInteger = ffi::NSIntegerMax; - extern_class!( /// An immutable, plain-text Unicode string object. /// @@ -61,6 +56,57 @@ extern_methods!( unsafe { msg_send_id![Self::class(), new] } } + /// Create a new string by appending the given string to self. + /// + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "gnustep-1-7")] + /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; + /// use objc2::ns_string; + /// let error_tag = ns_string!("Error: "); + /// let error_string = ns_string!("premature end of file."); + /// let error_message = error_tag.concat(error_string); + /// assert_eq!(&*error_message, ns_string!("Error: premature end of file.")); + /// ``` + #[doc(alias = "stringByAppendingString")] + #[doc(alias = "stringByAppendingString:")] + pub fn concat(&self, other: &Self) -> Id { + // SAFETY: The other string is non-null, and won't be retained + // by the function. + unsafe { msg_send_id![self, stringByAppendingString: other] } + } + + /// Create a new string by appending the given string, separated by + /// a path separator. + /// + /// This is similar to [`Path::join`][std::path::Path::join]. + /// + /// Note that this method only works with file paths (not, for + /// example, string representations of URLs). + /// + /// + /// # Examples + /// + /// ``` + /// # #[cfg(feature = "gnustep-1-7")] + /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; + /// use objc2::ns_string; + /// + /// let extension = ns_string!("scratch.tiff"); + /// assert_eq!(&*ns_string!("/tmp").join_path(extension), ns_string!("/tmp/scratch.tiff")); + /// assert_eq!(&*ns_string!("/tmp/").join_path(extension), ns_string!("/tmp/scratch.tiff")); + /// assert_eq!(&*ns_string!("/").join_path(extension), ns_string!("/scratch.tiff")); + /// assert_eq!(&*ns_string!("").join_path(extension), ns_string!("scratch.tiff")); + /// ``` + #[doc(alias = "stringByAppendingPathComponent")] + #[doc(alias = "stringByAppendingPathComponent:")] + pub fn join_path(&self, other: &Self) -> Id { + // SAFETY: Same as `Self::concat`. + unsafe { msg_send_id![self, stringByAppendingPathComponent: other] } + } + /// The number of UTF-8 code units in `self`. #[doc(alias = "lengthOfBytesUsingEncoding")] #[doc(alias = "lengthOfBytesUsingEncoding:")] @@ -68,13 +114,12 @@ extern_methods!( unsafe { msg_send![self, lengthOfBytesUsingEncoding: UTF8_ENCODING] } } - /// The number of UTF-16 code units in `self`. + /// The number of UTF-16 code units in the string. /// /// See also [`NSString::len`]. #[doc(alias = "length")] - // TODO: Finish this #[sel(length)] - fn len_utf16(&self) -> usize; + pub fn len_utf16(&self) -> usize; pub fn is_empty(&self) -> bool { // TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for diff --git a/test-ui/ui/msg_send_id_invalid_return.stderr b/test-ui/ui/msg_send_id_invalid_return.stderr index 44e2d1943..bf28ddc3e 100644 --- a/test-ui/ui/msg_send_id_invalid_return.stderr +++ b/test-ui/ui/msg_send_id_invalid_return.stderr @@ -27,12 +27,12 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied Exception NSArray NSAttributedString + NSBundle NSData NSDictionary NSError NSException - NSMutableArray - and 14 others + and 15 others = note: required for `RetainSemantics` to implement `MsgSendId<&objc2::runtime::Class, objc2::runtime::Class, Shared>` error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied @@ -48,12 +48,12 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied Exception NSArray NSAttributedString + NSBundle NSData NSDictionary NSError NSException - NSMutableArray - and 14 others + and 15 others = note: required for `RetainSemantics` to implement `MsgSendId<&objc2::runtime::Class, objc2::runtime::Class, Shared>` error[E0277]: the trait bound `&objc2::runtime::Object: MaybeUnwrap, _>` is not satisfied @@ -85,12 +85,12 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied Exception NSArray NSAttributedString + NSBundle NSData NSDictionary NSError NSException - NSMutableArray - and 14 others + and 15 others = note: required for `RetainSemantics` to implement `MsgSendId<&objc2::runtime::Class, Allocated, Shared>` error[E0277]: the trait bound `Id: MaybeUnwrap, _>` is not satisfied diff --git a/test-ui/ui/msg_send_super_not_classtype.stderr b/test-ui/ui/msg_send_super_not_classtype.stderr index ea06183aa..91ab9b909 100644 --- a/test-ui/ui/msg_send_super_not_classtype.stderr +++ b/test-ui/ui/msg_send_super_not_classtype.stderr @@ -10,13 +10,13 @@ error[E0277]: the trait bound `objc2::runtime::Object: ClassType` is not satisfi = help: the following other types implement trait `ClassType`: NSArray NSAttributedString + NSBundle NSData NSDictionary NSError NSException NSMutableArray - NSMutableAttributedString - and 12 others + and 13 others note: required by a bound in `__send_super_message_static` --> $WORKSPACE/objc2/src/message/mod.rs | @@ -35,13 +35,13 @@ error[E0277]: the trait bound `objc2::runtime::Object: ClassType` is not satisfi = help: the following other types implement trait `ClassType`: NSArray NSAttributedString + NSBundle NSData NSDictionary NSError NSException NSMutableArray - NSMutableAttributedString - and 12 others + and 13 others note: required by a bound in `__send_super_message_static` --> $WORKSPACE/objc2/src/message/mod.rs |