Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

File diff suppressed because one or more lines are too long

784
third-party/vendor/objc2/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,784 @@
# Changelog
Notable changes to this crate will be documented in this file. See the
`CHANGELOG_FOUNDATION.md` file for changes to the `objc2::foundation` module!
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased - YYYY-MM-DD
## 0.3.0-beta.3 - 2022-09-01
### Added
* Added `Ivar::write`, `Ivar::as_ptr` and `Ivar::as_mut_ptr` for safely
querying and modifying instance variables inside `init` methods.
* Added `IvarDrop<T>` to allow storing complex `Drop` values in ivars
(currently `rc::Id<T, O>`, `Box<T>`, `Option<rc::Id<T, O>>` or
`Option<Box<T>>`).
* **BREAKING**: Added required `ClassType::NAME` constant for statically
determining the name of a specific class.
* Allow directly specifying class name in declare_class! macro.
### Removed
* **BREAKING**: `MaybeUninit` no longer implements `IvarType` directly; use
`Ivar::write` instead.
## 0.3.0-beta.2 - 2022-08-28
### Added
* Added the `"unstable-static-class"` and `"unstable-static-class-inlined"`
feature flags to make the `class!` macro zero cost.
* Moved the external crate `objc2_foundation` into `objc2::foundation` under
(default) feature flag `"foundation"`.
* Added `declare_class!`, `extern_class!` and `ns_string!` macros from
`objc2-foundation`.
* Added helper method `ClassBuilder::add_static_ivar`.
* **BREAKING**: Added `ClassType` trait, and moved the associated `class`
methods that `extern_class!` and `declare_class!` generated to that. This
means you'll have to `use objc2::ClassType` whenever you want to use e.g.
`NSData::class()`.
* Added `Id::into_super`.
* Added `extern_methods!` macro.
* Added ability to call `msg_send![super(obj), ...]` without explicitly
specifying the superclass.
* Added automatic conversion of `bool` to/from the Objective-C `BOOL` in
`msg_send!`, `msg_send_id!`, `extern_methods!` and `declare_class!`.
Example:
```rust
// Before
use objc2::{msg_send, msg_send_bool};
use objc2::rc::{Id, Shared};
use objc2::runtime::{Bool, Object};
let obj: Id<Object, Shared>;
let _: () = unsafe { msg_send![&obj, setArg: Bool::YES] };
let is_equal = unsafe { msg_send_bool![&obj, isEqual: &*obj] };
// After
use objc2::msg_send;
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
let obj: Id<Object, Shared>;
let _: () = unsafe { msg_send![&obj, setArg: true] };
let is_equal: bool = unsafe { msg_send![&obj, isEqual: &*obj] };
```
### Changed
* **BREAKING**: Change syntax in `extern_class!` macro to be more Rust-like.
* **BREAKING**: Change syntax in `declare_class!` macro to be more Rust-like.
* **BREAKING**: Renamed `Id::from_owned` to `Id::into_shared`.
* **BREAKING**: The return type of `msg_send_id!` is now more generic; it can
now either be `Option<Id<_, _>>` or `Id<_, _>` (if the latter, it'll simply
panic).
Example:
```rust
// Before
let obj: Id<Object, Shared> = unsafe {
msg_send_id![msg_send_id![class!(MyObject), alloc], init].unwrap()
};
// After
let obj: Id<Object, Shared> = unsafe {
msg_send_id![msg_send_id![class!(MyObject), alloc], init]
};
```
* Updated `ffi` module to `objc-sys v0.2.0-beta.2`.
* **BREAKING**: Updated `encode` module `objc2-encode v2.0.0-pre.2`.
In particular, `Encoding` no longer has a lifetime parameter:
```rust
// Before
#[repr(C)]
pub struct NSRange {
pub location: usize,
pub length: usize,
}
unsafe impl Encode for NSRange {
const ENCODING: Encoding<'static> = Encoding::Struct(
"_NSRange", // This is how the struct is defined in C header files
&[usize::ENCODING, usize::ENCODING]
);
}
unsafe impl RefEncode for NSRange {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}
// After
#[repr(C)]
pub struct NSRange {
pub location: usize,
pub length: usize,
}
unsafe impl Encode for NSRange {
const ENCODING: Encoding = Encoding::Struct(
"_NSRange", // This is how the struct is defined in C header files
&[usize::ENCODING, usize::ENCODING]
);
}
unsafe impl RefEncode for NSRange {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
```
### Deprecated
* Depreacted `msg_send_bool!` in favour of new functionality on `msg_send!`
that allows seamlessly handling `bool`.
## 0.3.0-beta.1 - 2022-07-19
### Added
* Added `msg_send_id!` to help with following Objective-C's memory management
rules. **It is highly recommended that you use this instead of doing memory
management yourself!**
Example:
```rust
// Before
let obj: Id<Object, Shared> = unsafe {
let obj: *mut Object = msg_send![class!(MyObject), alloc];
let obj: *mut Object = msg_send![obj, init];
Id::new(obj).unwrap()
};
// After
let obj: Id<Object, Shared> = unsafe {
msg_send_id![msg_send_id![class!(MyObject), alloc], init].unwrap()
};
```
* Added the `"unstable-static-sel"` and `"unstable-static-sel-inlined"`
feature flags to make the `sel!` macro (and by extension, the `msg_send!`
macros) faster.
* Added `"unstable-c-unwind"` feature.
* Added unsafe function `Id::cast` for converting between different types of
objects.
* Added `Object::ivar_ptr` to allow direct access to instance variables
through `&Object`.
* Added `VerificationError` as more specific return type from
`Class::verify_sel`.
* Added `rc::Allocated` struct which is used within `msg_send_id!`.
* Added `Class::responds_to`.
* Added `exception::Exception` object to improve error messages from caught
exceptions.
* Added `declare::Ivar<T>` helper struct. This is useful for building safe
abstractions that access instance variables.
* Added `Id::from_owned` helper function.
### Changed
* **BREAKING**: `Sel` is now required to be non-null, which means that you
have to ensure that any selectors you receive from method calls are
non-null before using them.
* **BREAKING**: `ClassBuilder::root` is now generic over the function pointer,
meaning you will have to coerce initializer functions to pointers like in
`ClassBuilder::add_method` before you can use it.
* **BREAKING**: Moved `MessageReceiver::verify_message` to `Class::verify_sel`
and changed return type.
* Improved debug output with `verify_message` feature enabled.
* **BREAKING**: Changed `MessageReceiver::send_message` to panic instead of
returning an error.
* **BREAKING**: Renamed `catch_all` feature to `catch-all`.
* **BREAKING**: Made passing the function pointer argument to
`ClassBuilder::add_method`, `ClassBuilder::add_class_method` and similar
more ergonomic.
Let's say you have the following code:
```rust
// Before
let init: extern "C" fn(&mut Object, Sel) -> *mut Object = init;
builder.add_method(sel!(init), init);
```
Unfortunately, you will now encounter a very confusing error:
```
|
2 | builder.add_method(sel!(init), init);
| ^^^^^^^^^^ implementation of `MethodImplementation` is not general enough
|
= note: `MethodImplementation` would have to be implemented for the type `for<'r> extern "C" fn(&'r mut Object, Sel) -> *mut Object`
= note: ...but `MethodImplementation` is actually implemented for the type `extern "C" fn(&'0 mut Object, Sel) -> *mut Object`, for some specific lifetime `'0`
```
Fret not, the fix is easy! Just let the compiler infer the argument and
return types:
```rust
// After
let init: extern "C" fn(_, _) -> _ = init;
builder.add_method(sel!(init), init);
```
* Updated `ffi` module to `objc-sys v0.2.0-beta.1`.
* **BREAKING**: Updated `encode` module `objc2-encode v2.0.0-pre.1`.
### Fixed
* **BREAKING**: Disallow throwing `nil` exceptions in `exception::throw`.
### Removed
* **BREAKING**: Removed the `Sel::from_ptr` method.
* **BREAKING**: Removed `MessageError`.
## 0.3.0-beta.0 - 2022-06-13
### Added
* Added deprecated `Object::get_ivar` and `Object::get_mut_ivar` to make
upgrading easier.
* Allow using `From`/`TryFrom` to convert between `rc::Id` and `rc::WeakId`.
* Added `Bool::as_bool` (more descriptive name than `Bool::is_true`).
* Added convenience method `Id::as_ptr` and `Id::as_mut_ptr`.
* The `objc2-encode` dependency is now exposed as `objc2::encode`.
* Added `Id::retain_autoreleased` to allow following Cocoas memory management
rules more efficiently.
* Consistently allow trailing commas in `msg_send!`.
* Added `msg_send_bool!`, a less error-prone version of `msg_send!` for
Objective-C methods that return `BOOL`.
* Implemented `MethodImplementation` for `unsafe` function pointers.
### Changed
* **BREAKING**: Changed signature of `Id::new` and `Id::retain` from
`fn(NonNull<T>) -> Id<T>` to `fn(*mut T) -> Option<Id<T>>`.
Concretely, you will have to change your code as follows.
```rust
// Before
let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
let obj = NonNull::new(obj).expect("Failed to allocate object.");
let obj = unsafe { Id::new(obj) };
// After
let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
let obj = unsafe { Id::new(obj) }.expect("Failed to allocate object.");
```
* Allow specifying any receiver `T: Message` for methods added with
`ClassBuilder::add_method`.
* Renamed `ClassDecl` and `ProtocolDecl` to `ClassBuilder` and
`ProtocolBuilder`. The old names are kept as deprecated aliases.
* **BREAKING**: Changed how `msg_send!` works wrt. capturing its arguments.
This will require changes to your code wherever you used `Id`, for example:
```rust
// Before
let obj: Id<Object, Owned> = ...;
let p: i32 = unsafe { msg_send![obj, parameter] };
let _: () = unsafe { msg_send![obj, setParameter: p + 1] };
// After
let mut obj: Id<Object, Owned> = ...;
let p: i32 = unsafe { msg_send![&obj, parameter] };
let _: () = unsafe { msg_send![&mut obj, setParameter: p + 1] };
```
Notice that we now clearly pass `obj` by reference, and therein also
communicate the mutability of the object (in the first case, immutable, and
in the second, mutable).
If you previously used `*mut Object` or `&Object` as the receiver, message
sending should work exactly as before.
* **BREAKING**: `Class` no longer implements `Message` (but it can still be
used as the receiver in `msg_send!`, so this is unlikely to break anything
in practice).
* **BREAKING**: Sealed the `MethodImplementation` trait, and made its `imp`
method privat.
* **BREAKING**: Updated `ffi` module to `objc-sys v0.2.0-beta.0`.
* **BREAKING**: Updated `objc2-encode` (`Encoding`, `Encode`, `RefEncode` and
`EncodeArguments`) to `v2.0.0-pre.0`.
### Fixed
* Properly sealed the `MessageArguments` trait (it already had a hidden
method, so this is not really a breaking change).
### Removed
* **BREAKING**: `ManuallyDrop` no longer implements `Message` directly.
* **BREAKING**: `MessageReceiver::as_raw_receiver` is no longer public.
## 0.3.0-alpha.6 - 2022-01-03
### Added
* Implement `Hash` for `Sel`, `Ivar`, `Class`, `Method` and `MessageError`.
* Implement `PartialEq` and `Eq` for `Ivar`, `Method` and `MessageError`.
* Implement `fmt::Pointer` for `Sel` and `rc::AutoreleasePool`.
* Implement `fmt::Debug` for `ClassDecl`, `ProtocolDecl` and `rc::AutoreleasePool`.
### Changed
* **BREAKING**: Renamed:
- `Object::get_ivar` -> `Object::ivar`
- `Object::get_mut_ivar` -> `Object::ivar_mut`
* Vastly improved documentation.
* **BREAKING**: Updated `ffi` module to `objc-sys v0.2.0-alpha.1`.
* **BREAKING**: Updated `objc2-encode` (`Encoding`, `Encode`, `RefEncode` and
`EncodeArguments`) to `v2.0.0-beta.2`.
## 0.3.0-alpha.5 - 2021-12-22
### Added
* Export `objc-sys` as `ffi` module.
* Added common trait impls on `rc::Owned` and `rc::Shared` (useful in generic
contexts).
* Implement `RefEncode` for `runtime::Protocol`.
* Added `Message` and `MessageReceiver` implementation for `ManuallyDrop<T>`
(where `T` is appropriately bound). This allows patterns like:
```rust
let obj = Id::new(msg_send![class!(MyObject), alloc]);
let obj = ManuallyDrop::new(obj);
// `init` takes ownership and possibly returns a new object.
let obj = Id::new(msg_send![obj, init]);
```
* New cargo feature `"malloc"`, which allows cutting down on dependencies,
most crates don't need the introspection features that this provides.
### Changed
* Deprecated `runtime::BOOL`, `runtime::YES` and `runtime::NO`. Use the
newtype `Bool` instead, or low-level `ffi::BOOL`, `ffi::YES` and `ffi::NO`.
* **BREAKING**: The following methods now require the new `"malloc"` feature
flag to be enabled:
- `MessageReceiver::verify_message` (temporarily)
- `Method::return_type`
- `Method::argument_type`
- `Class::classes`
- `Class::instance_methods`
- `Class::adopted_protocols`
- `Class::instance_variables`
- `Protocol::protocols`
- `Protocol::adopted_protocols`
* Relaxed `Sized` bound on `rc::Id` and `rc::WeakId` to prepare for
`extern type` support.
* **BREAKING**: Relaxed `Sized` bound on `rc::SliceId` and `rc::DefaultId`.
* **BREAKING**: Updated `objc-sys` to `v0.2.0-alpha.0`.
* Updated `objc2-encode` (`Encoding`, `Encode`, `RefEncode` and
`EncodeArguments`) to `v2.0.0-beta.1`.
### Removed
* **BREAKING**: Removed the raw FFI functions from the `runtime` module. These
are available in the new `ffi` module instead.
### Fixed
* An issue with inlining various `rc` methods.
* Most types (e.g. `Class` and `Method`) are now `Send`, `Sync`, `UnwindSafe`
and `RefUnwindSafe` again.
Notable exception is `Object`, because that depends on the specific
subclass.
## 0.3.0-alpha.4 - 2021-11-22
Note: To use this version, specify `objc2-encode = "=2.0.0-beta.0"` in your
`Cargo.toml` as well.
### Added
* **BREAKING**: GNUStep users must depend on, and specify the appropriate
feature flag on `objc-sys` for the version they're using.
* Moved `objc_exception` crate into `exception` module (under feature flag).
* Added support for `_Complex` types.
* Added `rc::SliceId`, `rc::SliceIdMut` and `rc::DefaultId` helper traits for
extra functionality on `rc::Id`.
### Changed
* **BREAKING**: The `exception` feature now just enables the `exception`
module for general use. Use the new `catch_all` feature to wrap all message
sends in a `@try/@catch`.
* **BREAKING**: Updated `objc-sys` to `v0.1.0`.
* **BREAKING**: Updated `objc2-encode` (`Encoding`, `Encode`, `RefEncode` and
`EncodeArguments`) to `v2.0.0-beta.0`.
## 0.3.0-alpha.3 - 2021-09-05
Note: To use this version, specify `objc2-encode = "=2.0.0-alpha.1"` in your
`Cargo.toml` as well.
### Added
* Now uses the `objc-sys` (`v0.0.1`) crate for possibly better
interoperability with other crates that link to `libobjc`.
* Added newtype `runtime::Bool` to fix soundness issues with using
`runtime::BOOL` or `bool`.
* Moved `objc_id` crate into `rc` module. Notable changes:
- Vastly improved documentation
- Added `Id::autorelease`
- Added `Id::from_shared`
- Added a lot of forwarding implementations on `Id` for easier use
- `Id` and `WeakId` are now able to use the null-pointer optimization
- **BREAKING**: Added `T: Message` bounds on `Id`
- **BREAKING**: Remove `ShareId` type alias
- **BREAKING**: `Id` no longer have a default `Ownership`, you must specify
it everywhere as either `Id<T, Shared>` or `Id<T, Owned>`
- **BREAKING**: Sealed the `Ownership` trait
- **BREAKING**: Renamed `Id::from_ptr` to `Id::retain`
- **BREAKING**: Renamed `Id::from_retained_ptr` to `Id::new`
- **BREAKING**: Changed `Id::share` to a `From` implementation (usage of
`obj.share()` can be changed to `obj.into()`)
- **BREAKING**: Fixed soundness issues with missing `Send` and `Sync` bounds
on `Id` and `WeakId`
* Added sealed (for now) trait `MessageReceiver` to specify types that can
be used as the receiver of a message (instead of only allowing pointer
types).
* Add `MessageReceiver::send_super_message` method for dynamic selectors.
### Changed
* **BREAKING**: Change types of parameters to FFI functions exported in the
`runtime` module.
* **BREAKING**: Most types are now `!UnwindSafe`, to discourage trying to use
them after an unwind. This restriction may be lifted in the future.
* **BREAKING**: Most types are now `!Send` and `!Sync`. This was an oversight
that is fixed in a later version.
* A lot of smaller things.
* **BREAKING**: Dynamic message sending with `Message::send_message` is moved
to `MessageReceiver`.
* **BREAKING** Make `MessageArguments` a subtrait of `EncodeArguments`.
* Allow an optional comma after each argument to `msg_send!`.
### Removed
* **BREAKING**: Removed `rc::StrongPtr`. Use `Option<rc::Id<Object, Shared>>`
instead (beware: This has stronger safety invariants!).
* **BREAKING**: Removed `rc::WeakPtr`. Use `rc::WeakId<Object>` instead.
### Fixed
* **BREAKING**: Stop unsafely dereferencing `msg_send!`s first argument. The
`MessageReceiver` trait was introduced to avoid most breakage from this
change.
## 0.3.0-alpha.2 - 2021-09-05
### Added
* Added `rc::AutoreleasePool` and `rc::AutoreleaseSafe` to make accessing
autoreleased objects safe, by binding references to it using the
`ptr_as_ref` and `ptr_as_mut` methods.
### Changed
* **BREAKING**: The closure in `rc::autoreleasepool` now takes an argument
`&rc::AutoreleasePool`. This reference can be given to functions like
`INSString::as_str` so that it knows which lifetime to bound the returned
`&str` with.
Simple migration:
```rust
// Change
autoreleasepool(|| {
// Some code that autoreleases objects
});
// To
autoreleasepool(|_pool| {
// Some code that autoreleases objects
});
```
### Fixed
* The encoding of `BOOL` on `GNUStep`.
## 0.3.0-alpha.1 - 2021-09-02
### Added
* More documentation of safety requirements, and in general.
### Changed
* **BREAKING**: Change `objc-encode` dependency to `objc2-encode` version
`2.0.0-alpha.1`, and re-export the new `RefEncode` trait from that.
* **BREAKING**: Require that the receiver, arguments and return types of
messages always implement `Encode`. This helps ensuring that only types made
to go across the FFI boundary (`repr(C)`, ...) may. These requirements were
already present when the `verify_message` feature was enabled.
This is a very _disruptive change_, since libraries are now required to
implement `Encode` and `RefEncode` for all types intended to go across the
FFI-boundary to Objective-C. The change is justified because it helps
ensuring that users only pass valid types to `msg_send!` (for example, this
prevents users from accidentally passing `Drop` types to `msg_send`).
See the following examples for how to implement these traits, and otherwise
refer to the documentation of `objc2-encode` (`v2.0.0-alpha.1` or above).
```rust
use objc2::{Encode, Encoding, RefEncode};
/// Example struct.
#[repr(C)]
pub struct NSRange {
pub location: usize,
pub length: usize,
}
unsafe impl Encode for NSRange {
const ENCODING: Encoding<'static> = Encoding::Struct(
"_NSRange", // This is how the struct is defined in C header files
&[usize::ENCODING, usize::ENCODING]
);
}
unsafe impl RefEncode for NSRange {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}
/// Example object.
#[repr(C)]
pub struct __CFString(c_void);
pub type CFStringRef = *const __CFString;
unsafe impl RefEncode for __CFString {
const ENCODING_REF: Encoding<'static> = Encoding::Object;
}
```
* Temporarily disabled iOS tests.
### Fixed
* Statically find the correct `objc_msgSend[_X]` function to use based on the
`Encode` implementation of the return type. This fixes using functions that
return e.g. `type CGFloat = f32 / f64;`.
* Documentation links.
## 0.3.0-alpha.0 - 2021-08-29
Note: This is the version that is, as of this writing, available on the
`master` branch in the original `objc` project.
### Added
* Improve macro hygiene.
```rust
// You can now do
use objc2::{sel, class, msg_send};
// Instead of
#[macro_use]
extern crate objc2;
```
* Update to Rust 2018.
* Other internal improvements.
### Changed
* **BREAKING**: Forked the project, so the crate name is now `objc2`.
* **BREAKING**: Updated encoding utilities to use `objc-encode`. See that for
how to use the updated type `Encoding` and trait `Encode`.
In short, you will likely need to change your implementations of `Encode`
like this:
```rust
use objc2::{Encode, Encoding};
pub type CGFloat = ...; // Varies based on target_pointer_width
#[repr(C)]
pub struct NSPoint {
pub x: CGFloat,
pub y: CGFloat,
}
// Before
unsafe impl Encode for NSPoint {
fn encode() -> Encoding {
let encoding = format!(
"{{CGPoint={}{}}}",
CGFloat::encode().as_str(),
CGFloat::encode().as_str(),
);
unsafe { Encoding::from_str(&encoding) }
}
}
// After
unsafe impl Encode for NSPoint {
const ENCODING: Encoding<'static> = Encoding::Struct(
"CGPoint",
&[CGFloat::ENCODING, CGFloat::ENCODING]
);
}
```
* **BREAKING**: Updated public dependency `malloc_buf` to `1.0`.
* **BREAKING**: `Method::return_type` and `Method::argument_type` now return
`Malloc<str>` instead of `Encoding`.
* **BREAKING**: `Ivar::type_encoding` now return `&str` instead of `Encoding`.
### Removed
* **BREAKING**: Removed hidden `sel_impl!` macro.
## [0.2.7] (`objc` crate) - 2019-10-19
### Fixed
* **BREAKING**: Uses of `msg_send!` will now correctly fail to compile if no
return type can be inferred, instead of relying on an edge case of the
compiler that will soon change and silently cause undefined behavior.
## [0.2.6] (`objc` crate) - 2019-03-25
### Fixed
* Suppressed a deprecation warning in `sel!`, `msg_send!`, and `class!`.
## [0.2.5] (`objc` crate) - 2018-07-24
### Added
* **BREAKING**: `autoreleasepool` returns the value returned by its body
closure.
## [0.2.4] (`objc` crate) - 2018-07-22
### Added
* Added an `rc` module with reference counting utilities:
`StrongPtr`, `WeakPtr`, and `autoreleasepool`.
* Added some reference counting ABI foreign functions to the `runtime` module.
### Fixed
* Messaging nil under GNUstep now correctly returns zeroed results for all
return types.
## [0.2.3] (`objc` crate) - 2018-07-07
### Added
* Added a `class!` macro for getting statically-known classes. The result is
non-optional (avoiding a need to unwrap) and cached so each usage will only
look up the class once.
* Added caching to the `sel!` macro so that each usage will only register the
selector once.
### Fixed
* Implementation of `objc2::runtime` structs so there can't be unsound
references to uninhabited types.
## [0.2.2] (`objc` crate) - 2016-10-30
### Added
* Implemented `Sync` and `Send` for `Sel`.
## [0.2.1] (`objc` crate) - 2016-04-23
### Added
* Added support for working with protocols with the `Protocol` struct.
The protocols a class conforms to can be examined with the new
`Class::adopted_protocols` and `Class::conforms_to` methods.
* Protocols can be declared using the new `ProtocolDecl` struct.
## [0.2.0] (`objc` crate) - 2016-03-20
### Added
* Added verification for the types used when sending messages.
This can be enabled for all messages with the `"verify_message"` feature,
or you can test before sending specific messages with the
`Message::verify_message` method. Verification errors are reported using the
new `MessageError` struct.
* Added support for the GNUstep runtime!
Operating systems besides OSX and iOS will fall back to the GNUstep runtime.
* Root classes can be declared by using the `ClassDecl::root` constructor.
### Changed
* **BREAKING**: C types are now used from `std::os::raw` rather than `libc`.
This means `Encode` may not be implemented for `libc` types; switch them to
the `std::os::raw` equivalents instead. This avoids an issue that would
arise from simultaneously using different versions of the libc crate.
* **BREAKING**: Dynamic messaging was moved into the `Message` trait; instead
of `().send(obj, sel!(description))`, use
`obj.send_message(sel!(description), ())`.
* **BREAKING**: Rearranged the parameters to `ClassDecl::new` for consistency;
instead of `ClassDecl::new(superclass, "MyObject")`, use
`ClassDecl::new("MyObject", superclass)`.
* **BREAKING**: Overhauled the `MethodImplementation` trait. Encodings are now
accessed through the `MethodImplementation::Args` associated type. The
`imp_for` method was replaced with `imp` and no longer takes a selector or
returns an `UnequalArgsError`, although `ClassDecl::add_method` still
validates the number of arguments.
* **BREAKING**: Updated the definition of `Imp` to not use the old dispatch
prototypes. To invoke an `Imp`, it must first be transmuted to the correct
type.
### Removed
**BREAKING**: `objc_msgSend` functions from the `runtime` module; the
availability of these functions varies and they shouldn't be called without
trasmuting, so they are now hidden as an implementation detail of messaging.
### Fixed
* Corrected alignment of ivars in `ClassDecl`; declared classes may now have a
smaller size.
* With the `"exception"` or `"verify_message"` feature enabled, panics from
`msg_send!` will now be triggered from the line and file where the macro is
used, rather than from within the implementation of messaging.
## [0.1.8] (`objc` crate) - 2015-11-06
### Changed
* Updated `libc` dependency.
## [0.1.7] (`objc` crate) - 2015-09-23
### Fixed
* `improper_ctypes` warning.
## [0.1.6] (`objc` crate) - 2015-08-08
### Added
* Added `"exception"` feature which catches Objective-C exceptions and turns
them into Rust panics.
* Added support for `ARM`, `ARM64` and `x86` architectures.
* **BREAKING**: Added `Any` bound on message return types. In practice this
probably won't break anything.
* Start testing on iOS.
## [0.1.5] (`objc` crate) - 2015-05-02
### Changed
* **BREAKING**: Renamed `IntoMethodImp` to `MethodImplementation`.
* **BREAKING**: Renamed `MethodImplementation::into_imp` to `::imp_for`.
* **BREAKING**: Relaxed `Sized` bounds on `Encode` and `Message`. In practice
this probably won't break anything.
### Removed
* **BREAKING**: Removed `Id`, `Owned`, `Ownership`, `Shared`, `ShareId` and
`WeakId`. Use them from the `objc_id` crate instead.
* **BREAKING**: Removed `Method::set_implementation` and
`Method::exchange_implementation`.
## [0.1.4] (`objc` crate) - 2015-04-17
### Removed
* **BREAKING**: Removed `block` module. Use them from the `block` crate
instead.
## [0.1.3] (`objc` crate) - 2015-04-11
### Added
* Implement `fmt::Pointer` for `Id`.
### Fixed
* Odd lifetime bug.
## [0.1.2] (`objc` crate) - 2015-04-04
### Fixed
* **BREAKING**: Replace uses of `PhantomFn` with `Sized`.
## [0.1.1] (`objc` crate) - 2015-03-27
### Added
* Implement `Error` for `UnequalArgsError`.
### Removed
* **BREAKING**: Move `objc::foundation` into new crate `objc_foundation`.
[0.2.7]: https://github.com/madsmtm/objc2/compare/objc-0.2.6...objc-0.2.7
[0.2.6]: https://github.com/madsmtm/objc2/compare/objc-0.2.5...objc-0.2.6
[0.2.5]: https://github.com/madsmtm/objc2/compare/objc-0.2.4...objc-0.2.5
[0.2.4]: https://github.com/madsmtm/objc2/compare/objc-0.2.3...objc-0.2.4
[0.2.3]: https://github.com/madsmtm/objc2/compare/objc-0.2.2...objc-0.2.3
[0.2.2]: https://github.com/madsmtm/objc2/compare/objc-0.2.1...objc-0.2.2
[0.2.1]: https://github.com/madsmtm/objc2/compare/objc-0.2.0...objc-0.2.1
[0.2.0]: https://github.com/madsmtm/objc2/compare/objc-0.1.8...objc-0.2.0
[0.1.8]: https://github.com/madsmtm/objc2/compare/objc-0.1.7...objc-0.1.8
[0.1.7]: https://github.com/madsmtm/objc2/compare/objc-0.1.6...objc-0.1.7
[0.1.6]: https://github.com/madsmtm/objc2/compare/objc-0.1.5...objc-0.1.6
[0.1.5]: https://github.com/madsmtm/objc2/compare/objc-0.1.4...objc-0.1.5
[0.1.4]: https://github.com/madsmtm/objc2/compare/objc-0.1.3...objc-0.1.4
[0.1.3]: https://github.com/madsmtm/objc2/compare/objc-0.1.2...objc-0.1.3
[0.1.2]: https://github.com/madsmtm/objc2/compare/objc-0.1.1...objc-0.1.2
[0.1.1]: https://github.com/madsmtm/objc2/compare/objc-0.1.0...objc-0.1.1

View file

@ -0,0 +1,318 @@
# Changelog
Changes to the `objc2::foundation` module will be documented in this file.
This previously existed as a separate crate `objc2_foundation`, hence the
separation.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased - YYYY-MM-DD
## objc2 0.3.0-beta.3 - 2022-09-01
### 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`.
* Added `CGSize`, `CGPoint` and `CGRect` (just aliases to equivalent
`NS`-types, but helps readability).
### Changed
* **BREAKING**: `NSSize::new` no longer requires it's arguments to be
non-negative. Use `NSSize::abs` or `NSRect::standardize` if the API you're
binding to requires a non-negative size.
## objc2 0.3.0-beta.2 - 2022-08-28
### Added
* Added `NSNumber`.
* Added `NSError`.
* Implement `UnwindSafe` and `RefUnwindSafe` for all objects.
* Implemented `IntoIterator` for references to `NSArray`, `NSMutableArray`,
`NSData` and `NSMutableData`.
* Implemented `Extend` for `NSMutableArray`.
* Add extra `Extend<&u8>` impl for `NSMutableData`.
* Added function `NSValue::contains_encoding` for determining if the encoding
of the `NSValue` matches the encoding of the given type.
* Added functions `get_range`, `get_point`, `get_size` and `get_rect` to
`NSValue` to help safely returning various types it will commonly contain.
* `NSArray` and `NSMutableArray` now have sensible defaults for the ownership
of the objects they contain.
### Changed
* **BREAKING**: Moved from external crate `objc2_foundation` into
`objc2::foundation`.
* **BREAKING**: Made `NSValue` not generic any more. While we loose some
type-safety from this, it makes `NSValue` much more useful in the real
world!
* **BREAKING**: Made `NSArray::new` generic over ownership.
* **BREAKING**: Made `NSObject::is_kind_of` take a generic `T: ClassType`
instead of a `runtime::Class`.
### Fixed
* Made `Debug` impls for all objects print something useful.
### Removed
* `NSObject::hash_code`, `NSObject::is_equal` and `NSObject::description` in
favour of just having the trait implementations `Hash`, `PartiqalEq` and
`Debug`.
## objc2-foundation 0.2.0-alpha.6 - 2022-07-19
### Added
* Added `MainThreadMarker` to help with designing APIs where a method is only
safe to call on the main thread.
* Added `NSException` object.
* Added `extern_class!` macro to help with defining interfaces to classes.
Further changelog for this can be found in `CHANGELOG.md`.
* Added `declare_class!` macro to help with declaring custom classes.
Further changelog for this can be found in `CHANGELOG.md`.
* Expose the `objc2` version that this uses in the crate root.
* Added `NSZone`.
### Changed
* Changed a few `Debug` impls.
## objc2-foundation 0.2.0-alpha.5 - 2022-06-13
### Added
* Objects now `Deref` to their superclasses. E.g. `NSMutableArray` derefs to
`NSArray`, which derefs to `NSObject`, which derefs to `Object`.
This allows more ergonomic usage.
* Implement `PartialOrd` and `Ord` for `NSString` and `NSRange`.
* Added `NSString::has_prefix` and `NSString::has_suffix`.
* Added `NSRange` methods `new`, `is_empty`, `contains` and `end`.
* Added `NSThread` object.
* Added `is_multi_threaded` and `is_main_thread` helper functions.
* Added `NSProcessInfo` object.
* Added `NSMutableData` methods `from_data`, `with_capacity` and `push`.
* Added `io::Write` and `iter::Extend` implementation for `NSMutableData`.
* Added `NSUUID` object.
* Added `NSMutableString` object.
* Added basic `NSAttributedString` object.
* Added basic `NSMutableAttributedString` object.
* Added `NSInteger` and `NSUInteger` (type aliases to `isize` and `usize`).
* Added `CGFloat`.
* Added `NSPoint`.
* Added `NSSize`.
* Added `NSRect`.
* Implement `Borrow` and `BorrowMut` for all objects.
* Implement `ToOwned` for copyable types.
### Changed
* **BREAKING**: Removed the following helper traits in favor of inherent
methods on the objects themselves:
- `INSMutableArray`
- `INSArray`
- `INSMutableData`
- `INSData`
- `INSDictionary`
- `INSString`
- `INSValue`
- `INSObject`
This changed because objects now deref to their superclasses.
* **BREAKING**: Relaxed a lot of bounds from `INSObject` to `Message`. At some
point in the future a new trait will be introduced which remedies this
change.
* **BREAKING**: Removed the `I` prefix from:
- `INSCopying` (now `NSCopying`)
- `INSMutableCopying` (now `NSMutableCopying`)
- `INSFastEnumeration` (now `NSFastEnumeration`)
* **BREAKING**: Renamed `NSMutableData::append` to `extend_from_slice`.
## 0.2.0-alpha.4 - 2022-01-03
### Added
* Implement `PartialOrd` and `Ord` for `NSComparisonResult` and `NSValue`.
* Implement `fmt::Display` for `NSValue`.
* Implement `DefaultId` for relevant objects.
* Implement `AsRef` and `Index` for `NSData` and `NSMutableData`.
* Implement `AsMut` and `IndexMut` for `NSMutableData`.
### Changed
* **BREAKING**: Renamed `INSFastEnumeration::enumerator` to
`INSFastEnumeration::iter_fast`.
### Removed
* **BREAKING**: Removed `Deref` and `DerefMut` from `NSData` and
`NSMutableData`, since these invoke a non-trivial amount of code, and could
easily lead to hard-to-diagnose performance issues.
## objc2-foundation 0.2.0-alpha.3 - 2021-12-22
### Added
* **BREAKING**: Added associated `Ownership` type to `NSCopying`.
* **BREAKING**: Added associated `Ownership` type to `INSData`.
* **BREAKING**: Added associated `Ownership` type to `INSArray`.
* Added common trait impls (`PartialEq`, `Eq`, `Hash` and `Debug`) to
`NSValue`, `NSDictionary`, `NSArray` and `NSMutableArray`.
### Changed
* **BREAKING**: Made some creation methods a bit less generic (e.g.
`INSDictionary::from_keys_and_objects` now always returns `Id<_, Shared>`).
* Relax bounds on generic `INSObject` impls.
### Removed
* **BREAKING**: Removed associated `Ownership` type from `INSObject`; instead,
it is present on the types that actually need it (for example `NSCopying`).
* **BREAKING**: Removed `Sized` bound on `INSObject`.
### Fixed
* Soundness issue with `NSValue`, `NSDictionary`, `NSArray` and
`NSMutableArray` not being `#[repr(C)]`.
* **BREAKING**: `NSObject` is no longer `Send` and `Sync` (because its
subclasses may not be).
## objc2-foundation 0.2.0-alpha.2 - 2021-11-22
### Added
* **BREAKING**: Added associated `Ownership` type to `INSObject` to specify
whether the type can be mutated or not. `NSString` is a prime example of a
type that you may never get a `Owned/&mut` reference to, since it is very
easy to create two `NSString`s with the same underlying allocation.
* Added helper `is_empty` methods.
* Added `INSArray::first_mut` and `INSArray::last_mut`.
### Changed
* **BREAKING**: Renamed a lot of methods to better match Rusts naming scheme:
- `INSArray`
- `count` -> `len`
- `object_at` -> `get`
- `mut_object_at` -> `get_mut`
- `shared_object_at` -> `get_retained`
- `first_object` -> `first`
- `last_object` -> `last`
- `object_enumerator` -> `iter`
- `INSMutableArray`
- `add_object` -> `push`
- `insert_object_at` -> `insert`
- `replace_object_at` -> `replace`
- `remove_object_at` -> `remove`
- `remove_last_object` -> `pop`
- `remove_all_objects` -> `clear`
- `INSDictionary`
- `count` -> `len`
- `object_for` -> `get`
- `key_enumerator` -> `iter_keys`
- `object_enumerator` -> `iter_values`
- `INSValue`
- `value` -> `get`
- `from_value` -> `new`
- `NSComparisonResult`
- `from_ordering` -> `from`
- `as_ordering` -> `into`
- `NSRange`
- `from_range` -> `from`
- `as_range` -> `into`
* Use `SliceId` for better performance when creating arrays and dictionaries.
### Removed
* **BREAKING**: Removed the `object_struct!` macro. It may be re-added in
another form in the future.
* **BREAKING**: Removed `NSMutableSharedArray<T>` and `NSSharedArray<T>` type
aliases. Use `NSMutableArray<T, Shared>` and `NSArray<T, Shared>` instead.
* **BREAKING**: Removed `Any / 'static` bound on `INSObject`. This allows
implementing it for objects that contain lifetimes from the outer scope.
### Fixed
* **BREAKING**: Marked `INS...` traits as `unsafe` to implement.
* **BREAKING**: Removed `new` method from `INSObject` since some classes don't
want this called. It has been re-added to other `INS...` traits on a case by
case basis (in particular not `NSValue`).
* **BREAKING**: `INSString::as_str` now takes an a reference to
`objc2::rc::AutoreleasePool`. This ensure that the returned `&str` is only
used while the current autorelease pool is valid.
* Fixed `NSData::from_vec` on GNUStep.
## objc2-foundation 0.2.0-alpha.1 - 2021-10-28
### Added
* Implement new `RefEncode` trait for objects.
* Implement `Encode` for `NSComparisonResult` and `NSFastEnumerationState`.
* Implement `RefEncode` for objects and `NSFastEnumerationState`.
### Changed
* **BREAKING**: Uses `Id` from `objc2::rc` module instead of `objc_id` crate.
* **BREAKING**: `INSValue::encoding` now returns `&str` instead of `Encoding`.
### Fixed
* Use proper `#[repr(C)]` structs to represent Objective-C objects.
* `INSString::from_str` on GNUStep (`UTF8_ENCODING` was the wrong type).
## objc2-foundation 0.2.0-alpha.0 - 2021-08-29
Note: This is the version that is, as of this writing, available on the
`master` branch in the original `objc-foundation` project.
### Added
* Implement `Display` for `NSString`.
* Make `INSObject::class` faster using the `objc::class!` macro.
### Changed
* **BREAKING**: Forked the project, the crate name is now `objc2-foundation`.
### Fixed
* Fixed types in various calls to `objc::msg_send!` for better verification.
## objc-foundation [0.1.1] - 2016-06-19
### Fixed
* An issue with passing functions (instead of function pointers) in
`INSMutableArray::sort_by`.
## objc-foundation [0.1.0] - 2016-03-20
### Changed
* Update `objc` to `v0.2`.
* Update `objc_id` to `v0.1`.
## objc-foundation [0.0.4] - 2015-12-09
### Removed
* `libc` dependency.
## objc-foundation [0.0.3] - 2015-11-07
### Added
* `object_struct!` macro.
### Changed
* `libc` version can both be `0.1` and `0.2`.
## objc-foundation [0.0.2] - 2015-09-03
### Added
* `Any` bound on `INSObject`, because of a change in `objc` `v0.1.6`.
## objc-foundation [0.0.1] - 2015-06-13
Initial release.
[0.1.1]: https://github.com/madsmtm/objc2/compare/objc-foundation-0.1.0...objc-foundation-0.1.1
[0.1.0]: https://github.com/madsmtm/objc2/compare/objc-foundation-0.0.4...objc-foundation-0.1.0
[0.0.4]: https://github.com/madsmtm/objc2/compare/objc-foundation-0.0.3...objc-foundation-0.0.4
[0.0.3]: https://github.com/madsmtm/objc2/compare/objc-foundation-0.0.2...objc-foundation-0.0.3
[0.0.2]: https://github.com/madsmtm/objc2/compare/objc-foundation-0.0.1...objc-foundation-0.0.2
[0.0.1]: https://github.com/madsmtm/objc2/releases/tag/objc-foundation-0.0.1

92
third-party/vendor/objc2/Cargo.lock generated vendored Normal file
View file

@ -0,0 +1,92 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "block-sys"
version = "0.1.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146"
dependencies = [
"objc-sys",
]
[[package]]
name = "block2"
version = "0.2.0-alpha.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42"
dependencies = [
"block-sys",
"objc2-encode",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "iai"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678"
[[package]]
name = "libc"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "malloc_buf"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f32c8c0575eee8637bf462087c00098fe16d6cb621f1abb6ebab4da414d57fd"
dependencies = [
"libc",
]
[[package]]
name = "objc-sys"
version = "0.2.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
dependencies = [
"cc",
]
[[package]]
name = "objc2"
version = "0.3.0-beta.3.patch-leaks.3"
dependencies = [
"block2",
"iai",
"malloc_buf",
"objc-sys",
"objc2-encode",
"objc2-proc-macros",
"uuid",
]
[[package]]
name = "objc2-encode"
version = "2.0.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512"
dependencies = [
"objc-sys",
]
[[package]]
name = "objc2-proc-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b907b825b8c265dd8c4581472273184e75c12825f67d31ba18c5c8cdb5d5431e"
[[package]]
name = "uuid"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"

153
third-party/vendor/objc2/Cargo.toml vendored Normal file
View file

@ -0,0 +1,153 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "objc2"
version = "0.3.0-beta.3.patch-leaks.3"
authors = [
"Steven Sheldon",
"Mads Marquart <mads@marquart.dk>",
]
description = "Objective-C interface and bindings to the Cocoa Foundation framework"
documentation = "https://docs.rs/objc2/"
readme = "README.md"
keywords = [
"objective-c",
"macos",
"ios",
"objc_msgSend",
"cocoa",
]
categories = [
"api-bindings",
"development-tools::ffi",
"os::macos-apis",
]
license = "MIT"
repository = "https://github.com/madsmtm/objc2"
[package.metadata.docs.rs]
default-target = "x86_64-apple-darwin"
features = [
"exception",
"malloc",
"block",
"uuid",
"unstable-docsrs",
]
targets = [
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"aarch64-apple-ios",
"x86_64-apple-ios",
"x86_64-unknown-linux-gnu",
"i686-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
]
[[bench]]
name = "autorelease"
harness = false
[dependencies.block2]
version = "=0.2.0-alpha.6"
optional = true
default-features = false
[dependencies.malloc_buf]
version = "1.0"
optional = true
[dependencies.objc-sys]
version = "=0.2.0-beta.2"
default-features = false
[dependencies.objc2-encode]
version = "=2.0.0-pre.2"
default-features = false
[dependencies.objc2-proc-macros]
version = "0.1.0"
optional = true
[dependencies.uuid]
version = "1.1.2"
optional = true
default-features = false
[dev-dependencies.iai]
version = "0.1"
[features]
alloc = [
"objc2-encode/alloc",
"objc-sys/alloc",
"block2?/alloc",
]
apple = [
"objc-sys/apple",
"objc2-encode/apple",
"block2?/apple",
]
block = ["block2"]
catch-all = ["exception"]
default = [
"std",
"apple",
"foundation",
]
exception = ["objc-sys/unstable-exception"]
foundation = []
gnustep-1-7 = [
"objc-sys/gnustep-1-7",
"objc2-encode/gnustep-1-7",
"block2?/gnustep-1-7",
]
gnustep-1-8 = [
"gnustep-1-7",
"objc-sys/gnustep-1-8",
"objc2-encode/gnustep-1-8",
"block2?/gnustep-1-8",
]
gnustep-1-9 = [
"gnustep-1-8",
"objc-sys/gnustep-1-9",
"objc2-encode/gnustep-1-9",
"block2?/gnustep-1-9",
]
gnustep-2-0 = [
"gnustep-1-9",
"objc-sys/gnustep-2-0",
"objc2-encode/gnustep-2-0",
"block2?/gnustep-2-0",
]
gnustep-2-1 = [
"gnustep-2-0",
"objc-sys/gnustep-2-1",
"objc2-encode/gnustep-2-1",
"block2?/gnustep-2-1",
]
malloc = ["malloc_buf"]
std = [
"alloc",
"objc2-encode/std",
"objc-sys/std",
"block2?/std",
]
unstable-autoreleasesafe = []
unstable-c-unwind = []
unstable-docsrs = []
unstable-static-class = ["objc2-proc-macros"]
unstable-static-class-inlined = ["unstable-static-class"]
unstable-static-sel = ["objc2-proc-macros"]
unstable-static-sel-inlined = ["unstable-static-sel"]
verify_message = ["malloc"]

35
third-party/vendor/objc2/README.md vendored Normal file
View file

@ -0,0 +1,35 @@
# `objc2`
[![Latest version](https://badgen.net/crates/v/objc2)](https://crates.io/crates/objc2)
[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt)
[![Documentation](https://docs.rs/objc2/badge.svg)](https://docs.rs/objc2/)
[![CI](https://github.com/madsmtm/objc2/actions/workflows/ci.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/ci.yml)
Objective-C interface and bindings to the `Foundation` framework in Rust.
Most of the core libraries and frameworks that are in use on Apple systems are
written in Objective-C; this crate enables you to interract with those, and
provides ready-made bindings for the `Foundation` framework in particular.
## Example
```rust
use objc2::{class, msg_send, msg_send_id};
use objc2::ffi::NSUInteger;
use objc2::rc::{Id, Owned};
use objc2::runtime::Object;
let cls = class!(NSObject);
let obj: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
let hash: NSUInteger = unsafe { msg_send![&obj, hash] };
println!("NSObject hash: {}", hash);
```
See [the docs](https://docs.rs/objc2/) for a more thorough overview, or jump
right into the [examples].
This crate is part of the [`objc2` project](https://github.com/madsmtm/objc2),
see that for related crates.
[examples]: https://github.com/madsmtm/objc2/tree/master/objc2/examples

View file

@ -0,0 +1,165 @@
use core::ffi::c_void;
use std::mem::ManuallyDrop;
use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::runtime::{Class, Object, Sel};
use objc2::{class, msg_send, sel};
const BYTES: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
fn empty() {
#[cfg(feature = "gnustep-1-7")]
unsafe {
objc2::__gnustep_hack::get_class_to_force_linkage()
};
}
fn pool_cleanup() {
autoreleasepool(|_| {})
}
fn class() -> &'static Class {
class!(NSObject)
}
fn sel() -> Sel {
sel!(alloc)
}
fn send_message() -> &'static Class {
unsafe { msg_send![class!(NSObject), class] }
}
fn alloc_nsobject() -> *mut Object {
unsafe { msg_send![class!(NSObject), alloc] }
}
fn new_nsobject() -> Id<Object, Shared> {
let obj = alloc_nsobject();
let obj: *mut Object = unsafe { msg_send![obj, init] };
unsafe { Id::new(obj).unwrap_unchecked() }
}
fn new_nsdata() -> Id<Object, Shared> {
let bytes_ptr = BYTES.as_ptr() as *const c_void;
let obj: *mut Object = unsafe { msg_send![class!(NSData), alloc] };
let obj: *mut Object = unsafe {
msg_send![
obj,
initWithBytes: bytes_ptr,
length: BYTES.len(),
]
};
unsafe { Id::new(obj).unwrap_unchecked() }
}
fn new_leaked_nsdata() -> *const Object {
Id::as_ptr(&*ManuallyDrop::new(new_nsdata()))
}
fn autoreleased_nsdata() -> *const Object {
// let bytes_ptr = BYTES.as_ptr() as *const c_void;
// unsafe {
// msg_send![
// class!(NSData),
// dataWithBytes: bytes_ptr,
// length: BYTES.len(),
// ]
// }
unsafe { msg_send![new_leaked_nsdata(), autorelease] }
}
fn new_nsstring() -> Id<Object, Shared> {
let obj: *mut Object = unsafe { msg_send![class!(NSString), alloc] };
let obj: *mut Object = unsafe { msg_send![obj, init] };
unsafe { Id::new(obj).unwrap_unchecked() }
}
fn new_leaked_nsstring() -> *const Object {
Id::as_ptr(&*ManuallyDrop::new(new_nsstring()))
}
fn autoreleased_nsstring() -> *const Object {
// unsafe { msg_send![class!(NSString), string] }
unsafe { msg_send![new_leaked_nsstring(), autorelease] }
}
fn retain_autoreleased(obj: *const Object) -> Id<Object, Shared> {
unsafe { Id::retain_autoreleased((obj as *mut Object).cast()).unwrap_unchecked() }
}
fn autoreleased_nsdata_pool_cleanup() -> *const Object {
autoreleasepool(|_| autoreleased_nsdata())
}
fn autoreleased_nsdata_fast_caller_cleanup() -> Id<Object, Shared> {
retain_autoreleased(autoreleased_nsdata())
}
fn autoreleased_nsdata_fast_caller_cleanup_pool_cleanup() -> Id<Object, Shared> {
autoreleasepool(|_| retain_autoreleased(autoreleased_nsdata()))
}
fn autoreleased_nsstring_pool_cleanup() -> *const Object {
autoreleasepool(|_| autoreleased_nsstring())
}
fn autoreleased_nsstring_fast_caller_cleanup() -> Id<Object, Shared> {
retain_autoreleased(autoreleased_nsstring())
}
fn autoreleased_nsstring_fast_caller_cleanup_pool_cleanup() -> Id<Object, Shared> {
autoreleasepool(|_| retain_autoreleased(autoreleased_nsstring()))
}
macro_rules! main_with_warmup {
($($f:ident,)+) => {
mod warmup_fns {
$(
#[inline(never)]
pub fn $f() {
let _ = iai::black_box(super::$f());
}
)+
}
// Required to get DYLD to resolve the stubs on x86_64
fn warmup() {
$(
warmup_fns::$f();
)+
}
iai::main! {
warmup,
$(
$f,
)+
}
};
}
main_with_warmup! {
// Baseline
empty,
pool_cleanup,
class,
sel,
send_message,
alloc_nsobject,
new_nsobject,
// NSData
new_nsdata,
new_leaked_nsdata,
autoreleased_nsdata,
autoreleased_nsdata_pool_cleanup,
autoreleased_nsdata_fast_caller_cleanup,
autoreleased_nsdata_fast_caller_cleanup_pool_cleanup,
// NSString
new_nsstring,
new_leaked_nsstring,
autoreleased_nsstring,
autoreleased_nsstring_pool_cleanup,
autoreleased_nsstring_fast_caller_cleanup,
autoreleased_nsstring_fast_caller_cleanup_pool_cleanup,
}

View file

@ -0,0 +1,44 @@
use objc2::foundation::{NSArray, NSDictionary, NSObject};
use objc2::ns_string;
use objc2::rc::autoreleasepool;
fn main() {
// Create and compare NSObjects
let obj = NSObject::new();
#[allow(clippy::eq_op)]
{
println!("{:?} == {:?}? {:?}", obj, obj, obj == obj);
}
let obj2 = NSObject::new();
println!("{:?} == {:?}? {:?}", obj, obj2, obj == obj2);
// Create an NSArray from a Vec
let objs = vec![obj, obj2];
let array = NSArray::from_vec(objs);
for obj in array.iter() {
println!("{:?}", obj);
}
println!("{}", array.len());
// Turn the NSArray back into a Vec
let mut objs = NSArray::into_vec(array);
let obj = objs.pop().unwrap();
// Create a static NSString
let string = ns_string!("Hello, world!");
// Use an autoreleasepool to get the `str` contents of the NSString
autoreleasepool(|pool| {
println!("{}", string.as_str(pool));
});
// Or simply use the `Display` implementation
let _s = string.to_string(); // Using ToString
println!("{}", string); // Or Display directly
// Create a dictionary mapping strings to objects
let keys = &[string];
let vals = vec![obj];
let dict = NSDictionary::from_keys_and_objects(keys, vals);
println!("{:?}", dict.get(string));
println!("{}", dict.len());
}

View file

@ -0,0 +1,142 @@
//! A custom Objective-C class with a lifetime parameter.
//!
//! Note that we can't use the `declare_class!` macro for this, it doesn't
//! support such use-cases. Instead, we'll declare the class manually!
#![deny(unsafe_op_in_unsafe_fn)]
use std::marker::PhantomData;
use std::sync::Once;
use objc2::declare::{ClassBuilder, Ivar, IvarType};
use objc2::foundation::NSObject;
use objc2::rc::{Id, Owned};
use objc2::runtime::{Class, Object, Sel};
use objc2::{msg_send, msg_send_id, sel};
use objc2::{ClassType, Encoding, Message, RefEncode};
/// Helper type for the instance variable
struct NumberIvar<'a> {
// Doesn't actually matter what we put here, but we have to use the
// lifetime parameter somehow
p: PhantomData<&'a mut u8>,
}
unsafe impl<'a> IvarType for NumberIvar<'a> {
type Type = &'a mut u8;
const NAME: &'static str = "_number_ptr";
}
/// Struct that represents our custom object.
#[repr(C)]
pub struct MyObject<'a> {
// Required to give MyObject the proper layout
superclass: NSObject,
// SAFETY: The ivar is declared below, and is properly initialized in the
// designated initializer.
//
// Note! Attempting to acess the ivar before it has been initialized is
// undefined behaviour!
number: Ivar<NumberIvar<'a>>,
}
unsafe impl RefEncode for MyObject<'_> {
const ENCODING_REF: Encoding = Object::ENCODING_REF;
}
unsafe impl Message for MyObject<'_> {}
impl<'a> MyObject<'a> {
unsafe extern "C" fn init_with_ptr(
&mut self,
_cmd: Sel,
ptr: Option<&'a mut u8>,
) -> Option<&'a mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
// Properly initialize the number reference
Ivar::write(&mut this.number, ptr.expect("got NULL number ptr"));
this
})
}
pub fn new(number: &'a mut u8) -> Id<Self, Owned> {
// SAFETY: The lifetime of the reference is properly bound to the
// returned type
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithPtr: number]
}
}
pub fn get(&self) -> &u8 {
&self.number
}
pub fn set(&mut self, number: u8) {
**self.number = number;
}
}
unsafe impl<'a> ClassType for MyObject<'a> {
type Super = NSObject;
const NAME: &'static str = "MyObject";
fn class() -> &'static Class {
// TODO: Use std::lazy::LazyCell
static REGISTER_CLASS: Once = Once::new();
REGISTER_CLASS.call_once(|| {
let superclass = NSObject::class();
let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap();
builder.add_static_ivar::<NumberIvar<'a>>();
unsafe {
builder.add_method(
sel!(initWithPtr:),
Self::init_with_ptr as unsafe extern "C" fn(_, _, _) -> _,
);
}
let _cls = builder.register();
});
Class::get("MyObject").unwrap()
}
fn as_super(&self) -> &Self::Super {
&self.superclass
}
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.superclass
}
}
fn main() {
let mut number = 54;
let mut obj = MyObject::new(&mut number);
// It is not possible to convert to `Id<NSObject, Owned>` since that would
// loose the lifetime information that `MyObject` stores
// let obj = Id::into_super(obj);
println!("Number: {}", obj.get());
obj.set(7);
// Won't compile, since `obj` holds a mutable reference to number
// println!("Number: {}", number);
println!("Number: {}", obj.get());
let obj = Id::into_shared(obj);
let obj2 = obj.clone();
// We gave up ownership above, so can't edit the number any more!
// obj.set(7);
println!("Number: {}", obj.get());
println!("Number: {}", obj2.get());
drop(obj);
drop(obj2);
println!("Number: {}", number);
}

View file

@ -0,0 +1,118 @@
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{NSCopying, NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{declare_class, extern_class, msg_send, msg_send_id, ns_string, ClassType};
#[cfg(all(feature = "apple", target_os = "macos"))]
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
#[cfg(all(feature = "apple", target_os = "macos"))]
extern_class!(
#[derive(Debug)]
struct NSResponder;
unsafe impl ClassType for NSResponder {
type Super = NSObject;
}
);
#[cfg(all(feature = "apple", target_os = "macos"))]
declare_class!(
#[derive(Debug)]
struct CustomAppDelegate {
pub ivar: u8,
another_ivar: bool,
box_ivar: IvarDrop<Box<i32>>,
maybe_box_ivar: IvarDrop<Option<Box<i32>>>,
id_ivar: IvarDrop<Id<NSString, Shared>>,
maybe_id_ivar: IvarDrop<Option<Id<NSString, Shared>>>,
}
unsafe impl ClassType for CustomAppDelegate {
#[inherits(NSObject)]
type Super = NSResponder;
const NAME: &'static str = "MyCustomAppDelegate";
}
unsafe impl CustomAppDelegate {
#[sel(initWith:another:)]
fn init_with(self: &mut Self, ivar: u8, another_ivar: bool) -> Option<&mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
// TODO: `ns_string` can't be used inside closures; investigate!
let s = ns_string!("def");
this.map(|this| {
Ivar::write(&mut this.ivar, ivar);
*this.another_ivar = another_ivar;
*this.maybe_box_ivar = None;
*this.maybe_id_ivar = Some(s.copy());
Ivar::write(&mut this.box_ivar, Box::new(2));
Ivar::write(&mut this.id_ivar, NSString::from_str("abc"));
this
})
}
#[sel(myClassMethod)]
fn my_class_method() {
println!("A class method!");
}
}
// For some reason, `NSApplicationDelegate` is not a "real" protocol we
// can retrieve using `objc_getProtocol` - it seems it is created by
// `clang` only when used in Objective-C...
//
// TODO: Investigate this!
unsafe impl CustomAppDelegate {
/// This is `unsafe` because it expects `sender` to be valid
#[sel(applicationDidFinishLaunching:)]
unsafe fn did_finish_launching(&self, sender: *mut Object) {
println!("Did finish launching!");
// Do something with `sender`
dbg!(sender);
}
/// Some comment before `sel`.
#[sel(applicationWillTerminate:)]
/// Some comment after `sel`.
fn will_terminate(&self, _: *mut Object) {
println!("Will terminate!");
}
}
);
#[cfg(all(feature = "apple", target_os = "macos"))]
impl CustomAppDelegate {
pub fn new(ivar: u8, another_ivar: bool) -> Id<Self, Shared> {
let cls = Self::class();
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWith: ivar,
another: another_ivar,
]
}
}
}
#[cfg(all(feature = "apple", target_os = "macos"))]
fn main() {
let delegate = CustomAppDelegate::new(42, true);
println!("{:?}", delegate);
println!("{:?}", delegate.ivar);
println!("{:?}", delegate.another_ivar);
println!("{:?}", delegate.box_ivar);
println!("{:?}", delegate.maybe_box_ivar);
println!("{:?}", delegate.id_ivar);
println!("{:?}", delegate.maybe_id_ivar);
}
#[cfg(not(all(feature = "apple", target_os = "macos")))]
fn main() {
panic!("This example uses AppKit, which is only present on macOS");
}

View file

@ -0,0 +1,58 @@
use objc2::foundation::NSObject;
use objc2::runtime::Class;
use objc2::{sel, ClassType, Encode};
fn main() {
// Get the class representing `NSObject`
let cls = NSObject::class();
// Inspect various properties of the class
println!("NSObject superclass: {:?}", cls.superclass());
println!("NSObject size: {}", cls.instance_size());
println!(
"-[NSObject alloc] would work: {}",
cls.responds_to(sel!(alloc))
);
println!(
"+[NSObject alloc] would work: {}",
cls.metaclass().responds_to(sel!(alloc))
);
// Inspect an instance variable on the class
//
// Note: You should not rely on the `isa` ivar being available,
// this is only for demonstration.
let ivar = cls
.instance_variable("isa")
.expect("No ivar with name 'isa' found on NSObject");
println!(
"Instance variable {} has type encoding {:?}",
ivar.name(),
ivar.type_encoding()
);
assert!(<*const Class>::ENCODING.equivalent_to_str(ivar.type_encoding()));
// Inspect a method of the class
let method = cls.instance_method(sel!(hash)).unwrap();
println!(
"-[NSObject hash] takes {} parameters",
method.arguments_count()
);
#[cfg(feature = "malloc")]
{
let hash_return = method.return_type();
println!("-[NSObject hash] return type: {:?}", hash_return);
assert!(usize::ENCODING.equivalent_to_str(&hash_return));
}
// Create an instance
let obj = NSObject::new();
println!("NSObject address: {:p}", obj);
// Access an ivar of the object
//
// As before, you should not rely on the `isa` ivar being available!
let isa = unsafe { *obj.ivar::<*const Class>("isa") };
println!("NSObject isa: {:?}", isa);
}

View file

@ -0,0 +1,144 @@
//! Read from the global pasteboard, and write a new string into it.
//!
//! Works on macOS 10.7+
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
use std::mem::ManuallyDrop;
use objc2::foundation::{NSArray, NSDictionary, NSInteger, NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::{Class, Object};
use objc2::{extern_class, msg_send, msg_send_id, ClassType};
type NSPasteboardType = NSString;
type NSPasteboardReadingOptionKey = NSString;
#[cfg(all(feature = "apple", target_os = "macos"))]
#[link(name = "AppKit", kind = "framework")]
extern "C" {
/// <https://developer.apple.com/documentation/appkit/nspasteboardtypestring?language=objc>
static NSPasteboardTypeString: Option<&'static NSPasteboardType>;
}
#[cfg(all(feature = "apple", target_os = "macos"))]
extern_class!(
/// <https://developer.apple.com/documentation/appkit/nspasteboard?language=objc>
pub struct NSPasteboard;
// SAFETY: NSPasteboard actually inherits from NSObject.
unsafe impl ClassType for NSPasteboard {
type Super = NSObject;
}
);
#[cfg(all(feature = "apple", target_os = "macos"))]
impl NSPasteboard {
/// We return a `Shared` `Id` because `general` can easily be called
/// again, and it would return the same object, resulting in two aliasing
/// mutable references if we returned an `Owned` Id.
///
/// Besides, even if we could prevent this, there might be Objective-C
/// code somewhere else that accesses this instance.
///
/// TODO: Is this safe to call outside the main thread?
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1530091-generalpasteboard?language=objc>
pub fn general() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), generalPasteboard] }
}
/// Simple, straightforward implementation
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1533566-stringfortype?language=objc>
pub fn text_impl_1(&self) -> Id<NSString, Shared> {
let s = unsafe { NSPasteboardTypeString }.unwrap();
unsafe { msg_send_id![self, stringForType: s] }
}
/// More complex implementation using `readObjectsForClasses:options:`,
/// intended to show some how some patterns might require more knowledge
/// of nitty-gritty details.
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1524454-readobjectsforclasses?language=objc>
pub fn text_impl_2(&self) -> Id<NSString, Shared> {
// The NSPasteboard API is a bit weird, it requires you to pass
// classes as objects, which `objc2::foundation::NSArray` was not
// really made for - so we convert the class to an `Object` type
// instead. Also, we wrap it in `ManuallyDrop` because I'm not sure
// how classes handle `release` calls?
//
// TODO: Investigate and find a better way to express this in objc2.
let string_classes: ManuallyDrop<[Id<Object, Shared>; 1]> = {
let cls: *const Class = NSString::class();
let cls = cls as *mut Object;
unsafe { ManuallyDrop::new([Id::new(cls).unwrap()]) }
};
// Temporary, see https://github.com/rust-lang/rust-clippy/issues/9101
#[allow(unknown_lints, clippy::explicit_auto_deref)]
let class_array = NSArray::from_slice(&*string_classes);
let options = NSDictionary::new();
let objects = unsafe { self.read_objects_for_classes(&class_array, &options) };
// TODO: Should perhaps return Id<Object, Shared>?
let ptr: *const Object = objects.first().unwrap();
// And this part is weird as well, since we now have to convert the
// object into an NSString, which we know it to be since that's what
// we told `readObjectsForClasses:options:`.
let ptr = ptr as *mut NSString;
unsafe { Id::retain(ptr) }.unwrap()
}
/// Defined here to make it easier to declare which types are expected.
/// This is a common pattern that I can wholeheartedly recommend!
///
/// SAFETY: `class_array` must contain classes!
unsafe fn read_objects_for_classes(
&self,
class_array: &NSArray<Object, Shared>,
options: &NSDictionary<NSPasteboardReadingOptionKey, Object>,
) -> Id<NSArray<Object, Shared>, Shared> {
unsafe { msg_send_id![self, readObjectsForClasses: class_array, options: options] }
}
/// This takes `&self` even though `writeObjects:` would seem to mutate
/// the pasteboard. "What is going on?", you might rightfully ask!
///
/// We do this because we can't soundly get a mutable reference to the
/// global `NSPasteboard` instance, see [`NSPasteboard::general`].
///
/// This is sound because `NSPasteboard` contains `NSObject`, which in
/// turn contains `UnsafeCell`, allowing interior mutability.
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1533599-clearcontents?language=objc>
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1525945-writeobjects?language=objc>
pub fn set_text(&self, text: Id<NSString, Shared>) {
let _: NSInteger = unsafe { msg_send![self, clearContents] };
let string_array = NSArray::from_slice(&[text]);
let res: bool = unsafe { msg_send![self, writeObjects: &*string_array] };
if !res {
panic!("Failed writing to pasteboard");
}
}
}
#[cfg(all(feature = "apple", target_os = "macos"))]
fn main() {
let pasteboard = NSPasteboard::general();
let impl_1 = pasteboard.text_impl_1();
let impl_2 = pasteboard.text_impl_2();
println!("Pasteboard text from implementation 1 was: {:?}", impl_1);
println!("Pasteboard text from implementation 2 was: {:?}", impl_2);
assert_eq!(impl_1, impl_2);
let s = NSString::from_str("Hello, world!");
pasteboard.set_text(s.clone());
println!("Now the pasteboard text should be: {:?}", s);
assert_eq!(s, pasteboard.text_impl_1());
}
#[cfg(not(all(feature = "apple", target_os = "macos")))]
fn main() {
panic!("this example only works on macOS");
}

View file

@ -0,0 +1,178 @@
//! Speak synthethized text.
//!
//! This uses `NSSpeechSynthesizer` on macOS, and `AVSpeechSynthesizer` on
//! other Apple platforms. Note that `AVSpeechSynthesizer` _is_ available on
//! macOS, but only since 10.15!
//!
//! TODO: Unsure about when to use `&mut` here?
//!
//! Works on macOS >= 10.7 and iOS > 7.0.
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "gnustep-1-7", allow(unused))]
use std::thread;
use std::time::Duration;
use objc2::foundation::{NSObject, NSString};
use objc2::rc::{Id, Owned};
use objc2::{extern_class, msg_send, msg_send_id, ns_string, ClassType};
#[cfg(all(feature = "apple", target_os = "macos"))]
mod appkit {
use objc2::{foundation::NSCopying, rc::Shared};
use super::*;
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
extern_class!(
/// <https://developer.apple.com/documentation/appkit/nsspeechsynthesizer?language=objc>
pub struct NSSpeechSynthesizer;
unsafe impl ClassType for NSSpeechSynthesizer {
type Super = NSObject;
}
);
impl NSSpeechSynthesizer {
// Uses default voice
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
fn set_rate(&mut self, rate: f32) {
unsafe { msg_send![self, setRate: rate] }
}
fn set_volume(&mut self, volume: f32) {
unsafe { msg_send![self, setVolume: volume] }
}
fn start_speaking(&mut self, s: &NSString) {
unsafe { msg_send![self, startSpeakingString: s] }
}
pub fn speak(&mut self, utterance: &Utterance) {
// Convert to the range 90-720 that `NSSpeechSynthesizer` seems to
// support
//
// Note that you'd probably want a nonlinear conversion here to
// make it match `AVSpeechSynthesizer`.
self.set_rate(90.0 + (utterance.rate * (360.0 - 90.0)));
self.set_volume(utterance.volume);
self.start_speaking(&utterance.string);
}
pub fn is_speaking(&self) -> bool {
unsafe { msg_send![self, isSpeaking] }
}
}
// Shim to make NSSpeechSynthesizer work similar to AVSpeechSynthesizer
pub struct Utterance {
rate: f32,
volume: f32,
string: Id<NSString, Shared>,
}
impl Utterance {
pub fn new(string: &NSString) -> Self {
Self {
rate: 0.5,
volume: 1.0,
string: string.copy(),
}
}
pub fn set_rate(&mut self, rate: f32) {
self.rate = rate;
}
pub fn set_volume(&mut self, volume: f32) {
self.volume = volume;
}
}
}
#[cfg(all(feature = "apple", not(target_os = "macos")))]
mod avfaudio {
use super::*;
#[link(name = "AVFoundation", kind = "framework")]
extern "C" {}
extern_class!(
/// <https://developer.apple.com/documentation/avfaudio/avspeechsynthesizer?language=objc>
#[derive(Debug)]
pub struct AVSpeechSynthesizer;
unsafe impl ClassType for AVSpeechSynthesizer {
type Super = NSObject;
}
);
impl AVSpeechSynthesizer {
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn speak(&mut self, utterance: &AVSpeechUtterance) {
unsafe { msg_send![self, speakUtterance: utterance] }
}
pub fn is_speaking(&self) -> bool {
unsafe { msg_send![self, isSpeaking] }
}
}
extern_class!(
/// <https://developer.apple.com/documentation/avfaudio/avspeechutterance?language=objc>
#[derive(Debug)]
pub struct AVSpeechUtterance;
unsafe impl ClassType for AVSpeechUtterance {
type Super = NSObject;
}
);
impl AVSpeechUtterance {
pub fn new(string: &NSString) -> Id<Self, Owned> {
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithString: string] }
}
pub fn set_rate(&mut self, rate: f32) {
unsafe { msg_send![self, setRate: rate] }
}
pub fn set_volume(&mut self, volume: f32) {
unsafe { msg_send![self, setVolume: volume] }
}
}
}
#[cfg(all(feature = "apple", target_os = "macos"))]
use appkit::{NSSpeechSynthesizer as Synthesizer, Utterance};
#[cfg(all(feature = "apple", not(target_os = "macos")))]
use avfaudio::{AVSpeechSynthesizer as Synthesizer, AVSpeechUtterance as Utterance};
#[cfg(feature = "apple")]
fn main() {
let mut synthesizer = Synthesizer::new();
let mut utterance = Utterance::new(ns_string!("Hello from Rust!"));
utterance.set_rate(0.5);
utterance.set_volume(0.5);
synthesizer.speak(&utterance);
// Wait until speech has properly started up
thread::sleep(Duration::from_millis(1000));
// Wait until finished speaking
while synthesizer.is_speaking() {
thread::sleep(Duration::from_millis(100));
}
}
#[cfg(feature = "gnustep-1-7")]
fn main() {
panic!("this example is only available on Apple targets");
}

View file

@ -0,0 +1,705 @@
use crate::__sel_inner;
use crate::rc::{Allocated, Id, Ownership};
use crate::runtime::{Class, Object, Sel};
use crate::{Message, MessageArguments, MessageReceiver};
pub use crate::cache::CachedClass;
pub use crate::cache::CachedSel;
pub use core::borrow::{Borrow, BorrowMut};
pub use core::cell::UnsafeCell;
pub use core::convert::{AsMut, AsRef};
pub use core::mem::size_of;
pub use core::ops::{Deref, DerefMut};
pub use core::option::Option::{self, None, Some};
pub use core::primitive::{bool, str, u8};
pub use core::ptr::drop_in_place;
pub use core::{compile_error, concat, panic, stringify};
// TODO: Use `core::cell::LazyCell`
pub use std::sync::Once;
// Common selectors.
//
// These are put here to deduplicate the cached selector, and when using
// `unstable-static-sel`, the statics.
//
// Note that our assembly tests of `unstable-static-sel-inlined` output a GOT
// entry for such accesses, but that is just a limitation of our tests - the
// actual assembly is as one would expect.
#[inline]
pub fn alloc() -> Sel {
// SAFETY: Must have NUL byte
__sel_inner!("alloc\0", "alloc")
}
#[inline]
pub fn init() -> Sel {
// SAFETY: Must have NUL byte
__sel_inner!("init\0", "init")
}
#[inline]
pub fn new() -> Sel {
// SAFETY: Must have NUL byte
__sel_inner!("new\0", "new")
}
/// Helper for specifying the retain semantics for a given selector family.
///
/// Note that we can't actually check if a method is in a method family; only
/// whether the _selector_ is in a _selector_ family.
///
/// The slight difference here is:
/// - The method may be annotated with the `objc_method_family` attribute,
/// which would cause it to be in a different family. That this is not the
/// case is part of the `unsafe` contract of `msg_send_id!`.
/// - The method may not obey the added restrictions of the method family.
/// The added restrictions are:
/// - `new`, `alloc`, `copy` and `mutableCopy`: The method must return a
/// retainable object pointer type - we ensure this by making
/// `message_send_id` return `Id`.
/// - `init`: The method must be an instance method and must return an
/// Objective-C pointer type - We ensure this by taking `Id<T, O>`, which
/// means it can't be a class method!
///
/// While we're at it, we also limit a few other things to help the user out,
/// like only allowing `&Class` in `new` - this is not strictly required by
/// ARC though!
///
/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments>
pub struct RetainSemantics<
// `new` family
const NEW: bool,
// `alloc` family
const ALLOC: bool,
// `init` family
const INIT: bool,
// `copy` or `mutableCopy` family
const COPY_OR_MUT_COPY: bool,
> {}
type New = RetainSemantics<true, false, false, false>;
type Alloc = RetainSemantics<false, true, false, false>;
type Init = RetainSemantics<false, false, true, false>;
type CopyOrMutCopy = RetainSemantics<false, false, false, true>;
type Other = RetainSemantics<false, false, false, false>;
pub trait MsgSendId<T, U: ?Sized, O: Ownership> {
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<U, O>>(
obj: T,
sel: Sel,
args: A,
) -> R;
}
impl<T: ?Sized + Message, O: Ownership> MsgSendId<&'_ Class, T, O> for New {
#[inline]
#[track_caller]
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<T, O>>(
cls: &Class,
sel: Sel,
args: A,
) -> R {
// SAFETY: Checked by caller
let obj = unsafe { MessageReceiver::send_message(cls, sel, args) };
// SAFETY: The selector is `new`, so this has +1 retain count
let obj = unsafe { Id::new(obj) };
R::maybe_unwrap::<Self>(obj, (cls, sel))
}
}
impl<T: ?Sized + Message, O: Ownership> MsgSendId<&'_ Class, Allocated<T>, O> for Alloc {
#[inline]
#[track_caller]
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Allocated<T>, O>>(
cls: &Class,
sel: Sel,
args: A,
) -> R {
// SAFETY: Checked by caller
let obj = unsafe { MessageReceiver::send_message(cls, sel, args) };
// SAFETY: The selector is `alloc`, so this has +1 retain count
let obj = unsafe { Id::new_allocated(obj) };
R::maybe_unwrap::<Self>(obj, (cls, sel))
}
}
impl<T: ?Sized + Message, O: Ownership> MsgSendId<Option<Id<Allocated<T>, O>>, T, O> for Init {
#[inline]
#[track_caller]
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<T, O>>(
obj: Option<Id<Allocated<T>, O>>,
sel: Sel,
args: A,
) -> R {
let ptr = Id::option_into_ptr(obj.map(|obj| unsafe { Id::assume_init(obj) }));
// SAFETY: `ptr` may be null here, but that's fine since the return
// is `*mut T`, which is one of the few types where messages to nil is
// allowed.
//
// We do this for efficiency, to avoid having a branch after every
// `alloc`, that the user did not intend.
let obj = unsafe { MessageReceiver::send_message(ptr, sel, args) };
// SAFETY: The selector is `init`, so this has +1 retain count
let obj = unsafe { Id::new(obj) };
R::maybe_unwrap::<Self>(obj, (ptr.cast(), sel))
}
}
impl<T: MessageReceiver, U: ?Sized + Message, O: Ownership> MsgSendId<T, U, O> for CopyOrMutCopy {
#[inline]
#[track_caller]
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<U, O>>(
obj: T,
sel: Sel,
args: A,
) -> R {
// SAFETY: Checked by caller
let obj = unsafe { MessageReceiver::send_message(obj, sel, args) };
// SAFETY: The selector is `copy` or `mutableCopy`, so this has +1
// retain count
let obj = unsafe { Id::new(obj) };
R::maybe_unwrap::<Self>(obj, ())
}
}
impl<T: MessageReceiver, U: Message, O: Ownership> MsgSendId<T, U, O> for Other {
#[inline]
#[track_caller]
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<U, O>>(
obj: T,
sel: Sel,
args: A,
) -> R {
let ptr = obj.__as_raw_receiver();
// SAFETY: Checked by caller
let obj = unsafe { MessageReceiver::send_message(ptr, sel, args) };
// All code between the message send and the `retain_autoreleased`
// must be able to be optimized away for this to work.
// SAFETY: The selector is not `new`, `alloc`, `init`, `copy` nor
// `mutableCopy`, so the object must be manually retained.
let obj = unsafe { Id::retain_autoreleased(obj) };
// SAFETY: The object is still valid after a message send to a
// normal method - it would not be if the method was `init`.
R::maybe_unwrap::<Self>(obj, (unsafe { ptr.as_ref() }, sel))
}
}
pub trait MaybeUnwrap<T: ?Sized, O: Ownership> {
fn maybe_unwrap<'a, Failed: MsgSendIdFailed<'a>>(
obj: Option<Id<T, O>>,
args: Failed::Args,
) -> Self;
}
impl<T: ?Sized, O: Ownership> MaybeUnwrap<T, O> for Option<Id<T, O>> {
#[inline]
#[track_caller]
fn maybe_unwrap<'a, Failed: MsgSendIdFailed<'a>>(
obj: Option<Id<T, O>>,
_args: Failed::Args,
) -> Self {
obj
}
}
impl<T: ?Sized, O: Ownership> MaybeUnwrap<T, O> for Id<T, O> {
#[inline]
#[track_caller]
fn maybe_unwrap<'a, Failed: MsgSendIdFailed<'a>>(
obj: Option<Id<T, O>>,
args: Failed::Args,
) -> Self {
match obj {
Some(obj) => obj,
None => Failed::failed(args),
}
}
}
// Note: It would have been much easier to do this kind of thing using
// closures, but then `track_caller` doesn't work properly!
pub trait MsgSendIdFailed<'a> {
type Args;
fn failed(args: Self::Args) -> !;
}
impl<'a> MsgSendIdFailed<'a> for New {
type Args = (&'a Class, Sel);
#[cold]
#[track_caller]
fn failed((cls, sel): Self::Args) -> ! {
if sel == new() {
panic!("failed creating new instance of {:?}", cls)
} else {
panic!("failed creating new instance using +[{:?} {:?}]", cls, sel)
}
}
}
impl<'a> MsgSendIdFailed<'a> for Alloc {
type Args = (&'a Class, Sel);
#[cold]
#[track_caller]
fn failed((cls, sel): Self::Args) -> ! {
if sel == alloc() {
panic!("failed allocating {:?}", cls)
} else {
panic!("failed allocating with +[{:?} {:?}]", cls, sel)
}
}
}
impl MsgSendIdFailed<'_> for Init {
type Args = (*const Object, Sel);
#[cold]
#[track_caller]
fn failed((ptr, sel): Self::Args) -> ! {
if ptr.is_null() {
panic!("failed allocating object")
} else {
// We can't really display a more descriptive message here since the
// object is consumed by `init` and may not be valid any more.
if sel == init() {
panic!("failed initializing object")
} else {
panic!("failed initializing object with -{:?}", sel)
}
}
}
}
impl MsgSendIdFailed<'_> for CopyOrMutCopy {
type Args = ();
#[cold]
#[track_caller]
fn failed(_: Self::Args) -> ! {
panic!("failed copying object")
}
}
impl<'a> MsgSendIdFailed<'a> for Other {
type Args = (Option<&'a Object>, Sel);
#[cold]
#[track_caller]
fn failed((obj, sel): Self::Args) -> ! {
if let Some(obj) = obj {
let cls = obj.class();
panic!(
"unexpected NULL returned from {}[{:?} {:?}]",
if cls.is_metaclass() { "+" } else { "-" },
cls,
sel,
)
} else {
panic!("unexpected NULL {:?}; receiver was NULL", sel);
}
}
}
/// Checks whether a given selector is said to be in a given selector family.
///
/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families>
pub const fn in_selector_family(mut selector: &[u8], mut family: &[u8]) -> bool {
// Skip leading underscores from selector
loop {
selector = match selector {
[b'_', rest @ ..] => rest,
_ => break,
}
}
// Compare each character
loop {
(selector, family) = match (selector, family) {
// Remaining items
([s, selector @ ..], [f, family @ ..]) => {
if *s == *f {
// Next iteration
(selector, family)
} else {
// Family does not begin with selector
return false;
}
}
// Equal
([], []) => {
return true;
}
// Selector can't be part of familiy if smaller than it
([], _) => {
return false;
}
// Remaining items in selector
// -> ensure next character is not lowercase
([s, ..], []) => {
return !s.is_ascii_lowercase();
}
}
}
}
/// Helper struct for emitting the module info that macOS 32-bit requires.
///
/// <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCMac.cpp#L5211-L5234>
#[repr(C)]
pub struct ModuleInfo {
version: usize,
size: usize,
name: *const u8,
symtab: *const (),
}
// SAFETY: ModuleInfo is immutable.
unsafe impl Sync for ModuleInfo {}
impl ModuleInfo {
/// This is hardcoded in clang as 7.
const VERSION: usize = 7;
pub const fn new(name: *const u8) -> Self {
Self {
version: Self::VERSION,
size: core::mem::size_of::<Self>(),
name,
// We don't expose any symbols
symtab: core::ptr::null(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use core::ptr;
#[cfg(feature = "objc2-proc-macros")]
use crate::__hash_idents;
use crate::foundation::{NSDictionary, NSObject, NSString, NSValue, NSZone};
use crate::rc::{Owned, RcTestObject, Shared, ThreadTestData};
use crate::runtime::Object;
use crate::{class, msg_send_id, ns_string, ClassType};
#[test]
fn test_new() {
let _obj: Id<Object, Shared> = unsafe { msg_send_id![NSObject::class(), new] };
let _obj: Option<Id<Object, Shared>> = unsafe { msg_send_id![NSObject::class(), new] };
}
#[test]
// newScriptingObjectOfClass only available on macOS
#[cfg_attr(not(all(feature = "apple", target_os = "macos")), ignore)]
fn test_new_with_args() {
let mut expected = ThreadTestData::current();
let object_class = RcTestObject::class();
let key = ns_string!("");
let contents_value: *const Object = ptr::null();
let properties: Id<NSDictionary<NSString, Object>, _> =
NSDictionary::from_keys_and_objects::<NSString>(&[], vec![]);
let _obj: Option<Id<Object, Shared>> = unsafe {
msg_send_id![
NSObject::class(),
newScriptingObjectOfClass: object_class,
forValueForKey: key,
withContentsValue: contents_value,
properties: &*properties,
]
};
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
}
#[test]
fn test_macro_alloc() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let obj: Id<Allocated<RcTestObject>, Shared> = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_alloc_with_zone() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let zone: *const NSZone = ptr::null();
let _obj: Id<Allocated<RcTestObject>, Owned> =
unsafe { msg_send_id![cls, allocWithZone: zone] };
expected.alloc += 1;
expected.assert_current();
}
#[test]
fn test_macro_init() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let obj: Option<Id<Allocated<RcTestObject>, Shared>> = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
// Don't check allocation error
let _obj: Id<RcTestObject, Shared> = unsafe { msg_send_id![obj, init] };
expected.init += 1;
expected.assert_current();
let obj: Option<Id<Allocated<RcTestObject>, Shared>> = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
// Check allocation error before init
let obj = obj.unwrap();
let _obj: Id<RcTestObject, Shared> = unsafe { msg_send_id![Some(obj), init] };
expected.init += 1;
expected.assert_current();
}
#[test]
fn test_macro() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
crate::rc::autoreleasepool(|_| {
let _obj: Id<RcTestObject, Owned> = unsafe { msg_send_id![cls, new] };
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
let obj = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
expected.assert_current();
let obj: Id<RcTestObject, Owned> = unsafe { msg_send_id![obj, init] };
expected.init += 1;
expected.assert_current();
let _copy: Id<RcTestObject, Shared> = unsafe { msg_send_id![&obj, copy] };
expected.copy += 1;
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
let _mutable_copy: Id<RcTestObject, Shared> =
unsafe { msg_send_id![&obj, mutableCopy] };
expected.mutable_copy += 1;
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
let _self: Id<RcTestObject, Shared> = unsafe { msg_send_id![&obj, self] };
expected.retain += 1;
expected.assert_current();
let _desc: Option<Id<RcTestObject, Shared>> =
unsafe { msg_send_id![&obj, description] };
expected.assert_current();
});
expected.release += 5;
expected.dealloc += 4;
expected.assert_current();
}
#[test]
#[should_panic = "failed creating new instance of NSValue"]
// GNUStep instead returns an invalid instance that panics on accesses
#[cfg_attr(feature = "gnustep-1-7", ignore)]
fn new_nsvalue_fails() {
let _val: Id<NSValue, Shared> = unsafe { msg_send_id![NSValue::class(), new] };
}
#[test]
#[should_panic = "failed creating new instance using +[RcTestObject newReturningNull]"]
fn test_new_with_null() {
let _obj: Id<RcTestObject, Owned> =
unsafe { msg_send_id![RcTestObject::class(), newReturningNull] };
}
#[test]
#[should_panic = "failed allocating with +[RcTestObject allocReturningNull]"]
fn test_alloc_with_null() {
let _obj: Id<Allocated<RcTestObject>, Owned> =
unsafe { msg_send_id![RcTestObject::class(), allocReturningNull] };
}
#[test]
#[should_panic = "failed initializing object with -initReturningNull"]
fn test_init_with_null() {
let obj: Option<Id<Allocated<RcTestObject>, Owned>> =
unsafe { msg_send_id![RcTestObject::class(), alloc] };
let _obj: Id<RcTestObject, Owned> = unsafe { msg_send_id![obj, initReturningNull] };
}
#[test]
#[should_panic = "failed allocating object"]
#[cfg(not(feature = "verify_message"))] // Does NULL receiver checks
fn test_init_with_null_receiver() {
let obj: Option<Id<Allocated<RcTestObject>, Owned>> = None;
let _obj: Id<RcTestObject, Owned> = unsafe { msg_send_id![obj, init] };
}
#[test]
#[should_panic = "failed copying object"]
fn test_copy_with_null() {
let obj = Id::into_shared(RcTestObject::new());
let _obj: Id<RcTestObject, Shared> = unsafe { msg_send_id![&obj, copyReturningNull] };
}
#[test]
#[should_panic = "unexpected NULL returned from -[RcTestObject methodReturningNull]"]
fn test_normal_with_null() {
let obj = Id::into_shared(RcTestObject::new());
let _obj: Id<RcTestObject, Shared> = unsafe { msg_send_id![&obj, methodReturningNull] };
}
#[test]
#[should_panic = "unexpected NULL description; receiver was NULL"]
#[cfg(not(feature = "verify_message"))] // Does NULL receiver checks
fn test_normal_with_null_receiver() {
let obj: *const NSObject = ptr::null();
let _obj: Id<NSString, Shared> = unsafe { msg_send_id![obj, description] };
}
#[test]
fn test_in_selector_family() {
// Common cases
assert!(in_selector_family(b"alloc", b"alloc"));
assert!(in_selector_family(b"allocWithZone:", b"alloc"));
assert!(!in_selector_family(b"dealloc", b"alloc"));
assert!(!in_selector_family(b"initialize", b"init"));
assert!(!in_selector_family(b"decimalNumberWithDecimal:", b"init"));
assert!(in_selector_family(b"initWithCapacity:", b"init"));
assert!(in_selector_family(b"_initButPrivate:withParam:", b"init"));
assert!(!in_selector_family(b"description", b"init"));
assert!(!in_selector_family(b"inIT", b"init"));
assert!(!in_selector_family(b"init", b"copy"));
assert!(!in_selector_family(b"copyingStuff:", b"copy"));
assert!(in_selector_family(b"copyWithZone:", b"copy"));
assert!(!in_selector_family(b"initWithArray:copyItems:", b"copy"));
assert!(in_selector_family(b"copyItemAtURL:toURL:error:", b"copy"));
assert!(!in_selector_family(b"mutableCopying", b"mutableCopy"));
assert!(in_selector_family(b"mutableCopyWithZone:", b"mutableCopy"));
assert!(in_selector_family(b"mutableCopyWithZone:", b"mutableCopy"));
assert!(in_selector_family(
b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:",
b"new"
));
assert!(in_selector_family(
b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:",
b"new"
));
assert!(!in_selector_family(b"newsstandAssetDownload", b"new"));
// Trying to weed out edge-cases:
assert!(in_selector_family(b"__abcDef", b"abc"));
assert!(in_selector_family(b"_abcDef", b"abc"));
assert!(in_selector_family(b"abcDef", b"abc"));
assert!(in_selector_family(b"___a", b"a"));
assert!(in_selector_family(b"__a", b"a"));
assert!(in_selector_family(b"_a", b"a"));
assert!(in_selector_family(b"a", b"a"));
assert!(!in_selector_family(b"_abcdef", b"abc"));
assert!(!in_selector_family(b"_abcdef", b"def"));
assert!(!in_selector_family(b"_bcdef", b"abc"));
assert!(!in_selector_family(b"a_bc", b"abc"));
assert!(!in_selector_family(b"abcdef", b"abc"));
assert!(!in_selector_family(b"abcdef", b"def"));
assert!(!in_selector_family(b"abcdef", b"abb"));
assert!(!in_selector_family(b"___", b"a"));
assert!(!in_selector_family(b"_", b"a"));
assert!(!in_selector_family(b"", b"a"));
assert!(in_selector_family(b"copy", b"copy"));
assert!(in_selector_family(b"copy:", b"copy"));
assert!(in_selector_family(b"copyMe", b"copy"));
assert!(in_selector_family(b"_copy", b"copy"));
assert!(in_selector_family(b"_copy:", b"copy"));
assert!(in_selector_family(b"_copyMe", b"copy"));
assert!(!in_selector_family(b"copying", b"copy"));
assert!(!in_selector_family(b"copying:", b"copy"));
assert!(!in_selector_family(b"_copying", b"copy"));
assert!(!in_selector_family(b"Copy", b"copy"));
assert!(!in_selector_family(b"COPY", b"copy"));
// Empty family (not supported)
assert!(in_selector_family(b"___", b""));
assert!(in_selector_family(b"__", b""));
assert!(in_selector_family(b"_", b""));
assert!(in_selector_family(b"", b""));
assert!(!in_selector_family(b"_a", b""));
assert!(!in_selector_family(b"a", b""));
assert!(in_selector_family(b"_A", b""));
assert!(in_selector_family(b"A", b""));
}
mod test_trait_disambugated {
use super::*;
trait Abc {
fn send_message_id() {}
}
impl<T> Abc for T {}
#[test]
fn test_macro_still_works() {
let cls = class!(NSObject);
let _obj: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
}
}
#[test]
#[cfg(feature = "objc2-proc-macros")]
fn hash_idents_different() {
assert_ne!(__hash_idents!(abc), __hash_idents!(def));
}
#[test]
#[cfg(feature = "objc2-proc-macros")]
fn hash_idents_same_no_equal() {
assert_ne!(__hash_idents!(abc), __hash_idents!(abc));
assert_ne!(__hash_idents!(abc def ghi), __hash_idents!(abc def ghi));
}
#[test]
#[cfg(feature = "objc2-proc-macros")]
fn hash_idents_exact_same_ident() {
macro_rules! x {
($x:ident) => {
(__hash_idents!($x), __hash_idents!($x))
};
}
let (ident1, ident2) = x!(abc);
// This is a limitation of `__hash_idents`, ideally we'd like these
// to be different!
assert_eq!(ident1, ident2);
}
#[test]
#[cfg_attr(
not(all(feature = "apple", target_os = "macos", target_arch = "x86")),
ignore = "Only relevant on macOS 32-bit"
)]
fn ensure_size_of_module_info() {
assert_eq!(core::mem::size_of::<ModuleInfo>(), 16);
}
}

72
third-party/vendor/objc2/src/cache.rs vendored Normal file
View file

@ -0,0 +1,72 @@
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
use crate::ffi;
use crate::runtime::{Class, Sel};
/// Allows storing a [`Sel`] in a static and lazily loading it.
#[doc(hidden)]
pub struct CachedSel {
ptr: AtomicPtr<ffi::objc_selector>,
}
impl CachedSel {
/// Constructs a new [`CachedSel`].
pub const fn new() -> CachedSel {
CachedSel {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
/// Returns the cached selector. If no selector is yet cached, registers
/// one with the given name and stores it.
#[inline]
#[doc(hidden)]
pub unsafe fn get(&self, name: &str) -> Sel {
// `Relaxed` should be fine since `sel_registerName` is thread-safe.
let ptr = self.ptr.load(Ordering::Relaxed);
unsafe { Sel::from_ptr(ptr) }.unwrap_or_else(|| {
// The panic inside `Sel::register_unchecked` is unfortunate, but
// strict correctness is more important than speed
// SAFETY: Input is a non-null, NUL-terminated C-string pointer.
//
// We know this, because we construct it in `sel!` ourselves
let sel = unsafe { Sel::register_unchecked(name.as_ptr().cast()) };
self.ptr
.store(sel.as_ptr() as *mut ffi::objc_selector, Ordering::Relaxed);
sel
})
}
}
/// Allows storing a [`Class`] reference in a static and lazily loading it.
#[doc(hidden)]
pub struct CachedClass {
ptr: AtomicPtr<Class>,
}
impl CachedClass {
/// Constructs a new [`CachedClass`].
pub const fn new() -> CachedClass {
CachedClass {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
/// Returns the cached class. If no class is yet cached, gets one with
/// the given name and stores it.
#[inline]
#[doc(hidden)]
pub unsafe fn get(&self, name: &str) -> Option<&'static Class> {
// `Relaxed` should be fine since `objc_getClass` is thread-safe.
let ptr = self.ptr.load(Ordering::Relaxed);
if let Some(cls) = unsafe { ptr.as_ref() } {
Some(cls)
} else {
let ptr: *const Class = unsafe { ffi::objc_getClass(name.as_ptr().cast()) }.cast();
self.ptr.store(ptr as *mut Class, Ordering::Relaxed);
unsafe { ptr.as_ref() }
}
}
}

View file

@ -0,0 +1,94 @@
use crate::runtime::Class;
use crate::Message;
/// Marks types that represent specific classes.
///
/// Usually it is enough to generically know that a type is messageable, e.g.
/// [`rc::Id`][crate::rc::Id] works with any type that implements the
/// [`Message`] trait. But often, you have an object that you know represents
/// a specific Objective-C class - this trait allows you to communicate that
/// to the rest of the type-system.
///
/// This is implemented automatically by the
/// [`declare_class!`][crate::declare_class] and
/// [`extern_class!`][crate::extern_class] macros.
///
///
/// # Safety
///
/// The class returned by [`Self::class`] must be a subclass of the class that
/// [`Self::Super`] represents, and `as_super`/`as_super_mut` must be
/// implemented correctly. Finally [`Self::NAME`] must be correct.
///
/// In pseudocode:
/// ```ignore
/// Self::class().superclass() == <Self::Super as ClassType>::class()
/// Self::class().name() == Self::NAME
/// ```
///
///
/// # Examples
///
/// Use the trait to access the [`Class`] of different objects.
///
/// ```
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// use objc2::ClassType;
/// use objc2::foundation::NSObject;
/// // Get a class object representing `NSObject`
/// let cls = <NSObject as ClassType>::class(); // Or just `NSObject::class()`
/// ```
///
/// Use the [`extern_class!`][crate::extern_class] macro to implement this
/// trait for a type.
///
/// ```ignore
/// use objc2::{extern_class, ClassType};
///
/// extern_class!(
/// struct MyClass;
///
/// unsafe impl ClassType for MyClass {
/// type Super = NSObject;
/// }
/// );
///
/// let cls = MyClass::class();
/// ```
pub unsafe trait ClassType: Message {
/// The superclass of this class.
///
/// If you have implemented [`Deref`] for your type, it is highly
/// recommended that this is equal to [`Deref::Target`].
///
/// This may be [`runtime::Object`] if the class is a root class.
///
/// [`Deref`]: std::ops::Deref
/// [`Deref::Target`]: std::ops::Deref::Target
/// [`runtime::Object`]: crate::runtime::Object
type Super: Message;
/// The name of the Objective-C class that this type represents.
const NAME: &'static str;
/// Get a reference to the Objective-C class that this type represents.
///
/// May register the class with the runtime if it wasn't already.
///
///
/// # Panics
///
/// This may panic if something went wrong with getting or declaring the
/// class, e.g. if the program is not properly linked to the framework
/// that defines the class.
fn class() -> &'static Class;
/// Get an immutable reference to the superclass.
// Note: It'd be safe to provide a default impl using transmute here if
// we wanted to!
fn as_super(&self) -> &Self::Super;
/// Get a mutable reference to the superclass.
fn as_super_mut(&mut self) -> &mut Self::Super;
}

687
third-party/vendor/objc2/src/declare.rs vendored Normal file
View file

@ -0,0 +1,687 @@
//! Functionality for dynamically declaring Objective-C classes.
//!
//! Classes can be declared using the [`ClassBuilder`] struct. Instance
//! variables and methods can then be added before the class is ultimately
//! registered.
//!
//! **Note**: You likely don't need the dynamicism that this module provides!
//! Consider using the [`declare_class!`][crate::declare_class] macro instead.
//!
//!
//! # Example
//!
//! The following example demonstrates declaring a class named `MyNumber` that
//! has one ivar, a `u32` named `_number` and a few methods for constructor
//! methods and methods for interfacing with the number.
//!
//! ```
//! use objc2::declare::ClassBuilder;
//! use objc2::foundation::NSObject;
//! use objc2::rc::{Id, Owned};
//! use objc2::runtime::{Class, Object, Sel};
//! use objc2::{class, sel, msg_send, msg_send_id, ClassType};
//! # #[cfg(feature = "gnustep-1-7")]
//! # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
//!
//! fn register_class() -> &'static Class {
//! // Inherit from NSObject
//! let mut builder = ClassBuilder::new("MyNumber", NSObject::class())
//! .expect("a class with the name MyNumber likely already exists");
//!
//! // Add an instance variable of type `u32`
//! builder.add_ivar::<u32>("_number");
//!
//! // Add an Objective-C method for initializing an instance with a number
//! unsafe extern "C" fn init_with_number(
//! this: &mut Object,
//! _cmd: Sel,
//! number: u32,
//! ) -> Option<&mut Object> {
//! let this: Option<&mut Object> = msg_send![super(this, NSObject::class()), init];
//! this.map(|this| {
//! // SAFETY: The ivar is added with the same type above
//! this.set_ivar::<u32>("_number", number);
//! this
//! })
//! }
//! unsafe {
//! builder.add_method(
//! sel!(initWithNumber:),
//! init_with_number as unsafe extern "C" fn(_, _, _) -> _,
//! );
//! }
//!
//! // Add convenience method for getting a new instance with the number
//! extern "C" fn with_number(
//! cls: &Class,
//! _cmd: Sel,
//! number: u32,
//! ) -> *mut Object {
//! let obj: Option<Id<Object, Owned>> = unsafe {
//! msg_send_id![
//! msg_send_id![cls, alloc],
//! initWithNumber: number,
//! ]
//! };
//! obj.map(|obj| obj.autorelease_return()).unwrap_or(std::ptr::null_mut())
//! }
//! unsafe {
//! builder.add_class_method(
//! sel!(withNumber:),
//! with_number as extern "C" fn(_, _, _) -> _,
//! );
//! }
//!
//! // Add an Objective-C method for setting the number
//! extern "C" fn my_number_set(this: &mut Object, _cmd: Sel, number: u32) {
//! // SAFETY: The ivar is added with the same type above
//! unsafe { this.set_ivar::<u32>("_number", number) }
//! }
//! unsafe {
//! builder.add_method(sel!(setNumber:), my_number_set as extern "C" fn(_, _, _));
//! }
//!
//! // Add an Objective-C method for getting the number
//! extern "C" fn my_number_get(this: &Object, _cmd: Sel) -> u32 {
//! // SAFETY: The ivar is added with the same type above
//! unsafe { *this.ivar::<u32>("_number") }
//! }
//! unsafe {
//! builder.add_method(sel!(number), my_number_get as extern "C" fn(_, _) -> _);
//! }
//!
//! builder.register()
//! }
//!
//! // Usage
//!
//! // Note: you should only do class registration once! This can be ensure
//! // with `std::sync::Once` or the `once_cell` crate.
//! let cls = register_class();
//!
//! let obj: Id<Object, Owned> = unsafe {
//! msg_send_id![cls, withNumber: 42u32]
//! };
//!
//! let n: u32 = unsafe { msg_send![&obj, number] };
//! assert_eq!(n, 42);
//!
//! let _: () = unsafe { msg_send![&obj, setNumber: 12u32] };
//! let n: u32 = unsafe { msg_send![&obj, number] };
//! assert_eq!(n, 12);
//! ```
mod ivar;
mod ivar_drop;
mod ivar_forwarding_impls;
use alloc::format;
use alloc::string::ToString;
use core::mem;
use core::mem::ManuallyDrop;
use core::ptr;
use core::ptr::NonNull;
use std::ffi::CString;
use crate::encode::{Encode, EncodeArguments, Encoding, RefEncode};
use crate::ffi;
use crate::runtime::{Bool, Class, Imp, Object, Protocol, Sel};
use crate::sel;
use crate::Message;
pub use ivar::{InnerIvarType, Ivar, IvarType};
pub use ivar_drop::IvarDrop;
pub(crate) mod private {
pub trait Sealed {}
}
/// Types that can be used as the implementation of an Objective-C method.
///
/// This is a sealed trait that is implemented for a lot of `extern "C"`
/// function pointer types.
pub trait MethodImplementation: private::Sealed {
/// The callee type of the method.
type Callee: RefEncode + ?Sized;
/// The return type of the method.
type Ret: Encode;
/// The argument types of the method.
type Args: EncodeArguments;
#[doc(hidden)]
fn __imp(self) -> Imp;
}
macro_rules! method_decl_impl {
(@<$($l:lifetime),*> T, $r:ident, $f:ty, $($t:ident),*) => {
impl<$($l,)* T, $r, $($t),*> private::Sealed for $f
where
T: Message + ?Sized,
$r: Encode,
$($t: Encode,)*
{}
impl<$($l,)* T, $r, $($t),*> MethodImplementation for $f
where
T: Message + ?Sized,
$r: Encode,
$($t: Encode,)*
{
type Callee = T;
type Ret = $r;
type Args = ($($t,)*);
fn __imp(self) -> Imp {
unsafe { mem::transmute(self) }
}
}
};
(@<$($l:lifetime),*> Class, $r:ident, $f:ty, $($t:ident),*) => {
impl<$($l,)* $r, $($t),*> private::Sealed for $f
where
$r: Encode,
$($t: Encode,)*
{}
impl<$($l,)* $r, $($t),*> MethodImplementation for $f
where
$r: Encode,
$($t: Encode,)*
{
type Callee = Class;
type Ret = $r;
type Args = ($($t,)*);
fn __imp(self) -> Imp {
unsafe { mem::transmute(self) }
}
}
};
(# $abi:literal; $($t:ident),*) => {
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> T, R, unsafe extern $abi fn(*const T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> T, R, unsafe extern $abi fn(*mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, unsafe extern $abi fn(&'a T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, unsafe extern $abi fn(&'a mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> Class, R, extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> Class, R, unsafe extern $abi fn(*const Class, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> Class, R, unsafe extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
};
($($t:ident),*) => {
method_decl_impl!(# "C"; $($t),*);
#[cfg(feature = "unstable-c-unwind")]
method_decl_impl!(# "C-unwind"; $($t),*);
};
}
method_decl_impl!();
method_decl_impl!(A);
method_decl_impl!(A, B);
method_decl_impl!(A, B, C);
method_decl_impl!(A, B, C, D);
method_decl_impl!(A, B, C, D, E);
method_decl_impl!(A, B, C, D, E, F);
method_decl_impl!(A, B, C, D, E, F, G);
method_decl_impl!(A, B, C, D, E, F, G, H);
method_decl_impl!(A, B, C, D, E, F, G, H, I);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L);
fn count_args(sel: Sel) -> usize {
sel.name().chars().filter(|&c| c == ':').count()
}
fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
// First two arguments are always self and the selector
let mut types = format!("{}{}{}", ret, <*mut Object>::ENCODING, Sel::ENCODING);
for enc in args {
use core::fmt::Write;
write!(&mut types, "{}", enc).unwrap();
}
CString::new(types).unwrap()
}
fn log2_align_of<T>() -> u8 {
let align = mem::align_of::<T>();
// Alignments are required to be powers of 2
debug_assert!(align.count_ones() == 1);
// log2 of a power of 2 is the number of trailing zeros
align.trailing_zeros() as u8
}
/// A type for declaring a new class and adding new methods and ivars to it
/// before registering it.
#[derive(Debug)]
pub struct ClassBuilder {
// Note: Don't ever construct a &mut Class, since it is possible to get
// this pointer using `Class::classes`!
cls: NonNull<Class>,
}
#[doc(hidden)]
#[deprecated = "Use `ClassBuilder` instead."]
pub type ClassDecl = ClassBuilder;
// SAFETY: The stuff that touch global state does so using locks internally.
//
// Modifying the class itself can only be done through `&mut`, so Sync is
// safe (e.g. we can't accidentally call `add_ivar` at the same time from two
// different threads).
//
// (Though actually, that would be safe since the entire runtime is locked
// when doing so...).
//
// Finally, there are no requirements that the class must be registered on the
// same thread that allocated it (so Send is safe).
unsafe impl Send for ClassBuilder {}
unsafe impl Sync for ClassBuilder {}
impl ClassBuilder {
fn as_mut_ptr(&mut self) -> *mut ffi::objc_class {
self.cls.as_ptr().cast()
}
fn with_superclass(name: &str, superclass: Option<&Class>) -> Option<Self> {
let name = CString::new(name).unwrap();
let super_ptr = superclass.map_or(ptr::null(), |c| c).cast();
let cls = unsafe { ffi::objc_allocateClassPair(super_ptr, name.as_ptr(), 0) };
NonNull::new(cls.cast()).map(|cls| Self { cls })
}
/// Constructs a [`ClassBuilder`] with the given name and superclass.
///
/// Returns [`None`] if the class couldn't be allocated, or a class with
/// that name already exist.
pub fn new(name: &str, superclass: &Class) -> Option<Self> {
Self::with_superclass(name, Some(superclass))
}
/// Constructs a [`ClassBuilder`] declaring a new root class with the
/// given name.
///
/// Returns [`None`] if the class couldn't be allocated.
///
/// An implementation for `+initialize` must also be given; the runtime
/// calls this method for all classes, so it must be defined on root
/// classes.
///
/// Note that implementing a root class is not a simple endeavor!
/// For example, your class probably cannot be passed to Cocoa code unless
/// the entire `NSObject` protocol is implemented.
/// Functionality it expects, like implementations of `-retain` and
/// `-release` used by ARC, will not be present otherwise.
pub fn root<F>(name: &str, intitialize_fn: F) -> Option<Self>
where
F: MethodImplementation<Callee = Class, Args = (), Ret = ()>,
{
Self::with_superclass(name, None).map(|mut this| {
unsafe { this.add_class_method(sel!(initialize), intitialize_fn) };
this
})
}
/// Adds a method with the given name and implementation.
///
/// # Panics
///
/// Panics if the method wasn't sucessfully added or if the selector and
/// function take different numbers of arguments.
///
/// # Safety
///
/// The caller must ensure that the types match those that are expected
/// when the method is invoked from Objective-C.
pub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
where
T: Message + ?Sized,
F: MethodImplementation<Callee = T>,
{
let encs = F::Args::ENCODINGS;
let sel_args = count_args(sel);
assert_eq!(
sel_args,
encs.len(),
"Selector {:?} accepts {} arguments, but function accepts {}",
sel,
sel_args,
encs.len(),
);
let types = method_type_encoding(&F::Ret::ENCODING, encs);
let success = Bool::from_raw(unsafe {
ffi::class_addMethod(
self.as_mut_ptr(),
sel.as_ptr(),
Some(func.__imp()),
types.as_ptr(),
)
});
assert!(success.as_bool(), "Failed to add method {:?}", sel);
}
fn metaclass_mut(&mut self) -> *mut ffi::objc_class {
unsafe { ffi::object_getClass(self.as_mut_ptr().cast()) as *mut ffi::objc_class }
}
/// Adds a class method with the given name and implementation.
///
/// # Panics
///
/// Panics if the method wasn't sucessfully added or if the selector and
/// function take different numbers of arguments.
///
/// # Safety
///
/// The caller must ensure that the types match those that are expected
/// when the method is invoked from Objective-C.
pub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)
where
F: MethodImplementation<Callee = Class>,
{
let encs = F::Args::ENCODINGS;
let sel_args = count_args(sel);
assert_eq!(
sel_args,
encs.len(),
"Selector {:?} accepts {} arguments, but function accepts {}",
sel,
sel_args,
encs.len(),
);
let types = method_type_encoding(&F::Ret::ENCODING, encs);
let success = Bool::from_raw(unsafe {
ffi::class_addMethod(
self.metaclass_mut(),
sel.as_ptr(),
Some(func.__imp()),
types.as_ptr(),
)
});
assert!(success.as_bool(), "Failed to add class method {:?}", sel);
}
/// Adds an ivar with type `T` and the provided name.
///
///
/// # Panics
///
/// If the ivar wasn't successfully added for some reason - this usually
/// happens if there already was an ivar with that name.
pub fn add_ivar<T: Encode>(&mut self, name: &str) {
// SAFETY: The encoding is correct
unsafe { self.add_ivar_inner::<T>(name, &T::ENCODING) }
}
unsafe fn add_ivar_inner<T>(&mut self, name: &str, encoding: &Encoding) {
let c_name = CString::new(name).unwrap();
let encoding = CString::new(encoding.to_string()).unwrap();
let size = mem::size_of::<T>();
let align = log2_align_of::<T>();
let success = Bool::from_raw(unsafe {
ffi::class_addIvar(
self.as_mut_ptr(),
c_name.as_ptr(),
size,
align,
encoding.as_ptr(),
)
});
assert!(success.as_bool(), "Failed to add ivar {}", name);
}
/// Adds an instance variable from an [`IvarType`].
///
///
/// # Panics
///
/// Same as [`ClassBuilder::add_ivar`].
pub fn add_static_ivar<T: IvarType>(&mut self) {
// SAFETY: The encoding is correct
unsafe {
self.add_ivar_inner::<<T::Type as InnerIvarType>::__Inner>(
T::NAME,
&T::Type::__ENCODING,
)
}
}
/// Adds the given protocol to self.
///
/// # Panics
///
/// If the protocol wasn't successfully added.
pub fn add_protocol(&mut self, proto: &Protocol) {
let success = unsafe { ffi::class_addProtocol(self.as_mut_ptr(), proto.as_ptr()) };
let success = Bool::from_raw(success).as_bool();
assert!(success, "Failed to add protocol {:?}", proto);
}
// fn add_property(&self, name: &str, attributes: &[ffi::objc_property_attribute_t]);
/// Registers the [`ClassBuilder`], consuming it, and returns a reference
/// to the newly registered [`Class`].
pub fn register(self) -> &'static Class {
// Forget self, otherwise the class will be disposed in drop
let mut this = ManuallyDrop::new(self);
unsafe { ffi::objc_registerClassPair(this.as_mut_ptr()) };
unsafe { this.cls.as_ref() }
}
}
impl Drop for ClassBuilder {
fn drop(&mut self) {
unsafe { ffi::objc_disposeClassPair(self.as_mut_ptr()) }
}
}
/// A type for declaring a new protocol and adding new methods to it
/// before registering it.
#[derive(Debug)]
pub struct ProtocolBuilder {
proto: NonNull<Protocol>,
}
#[doc(hidden)]
#[deprecated = "Use `ProtocolBuilder` instead."]
pub type ProtocolDecl = ProtocolBuilder;
// SAFETY: Similar to ClassBuilder
unsafe impl Send for ProtocolBuilder {}
unsafe impl Sync for ProtocolBuilder {}
impl ProtocolBuilder {
fn as_mut_ptr(&mut self) -> *mut ffi::objc_protocol {
self.proto.as_ptr().cast()
}
/// Constructs a [`ProtocolBuilder`] with the given name.
///
/// Returns [`None`] if the protocol couldn't be allocated.
pub fn new(name: &str) -> Option<Self> {
let c_name = CString::new(name).unwrap();
let proto = unsafe { ffi::objc_allocateProtocol(c_name.as_ptr()) };
NonNull::new(proto.cast()).map(|proto| Self { proto })
}
fn add_method_description_common<Args, Ret>(
&mut self,
sel: Sel,
is_required: bool,
is_instance_method: bool,
) where
Args: EncodeArguments,
Ret: Encode,
{
let encs = Args::ENCODINGS;
let sel_args = count_args(sel);
assert_eq!(
sel_args,
encs.len(),
"Selector {:?} accepts {} arguments, but function accepts {}",
sel,
sel_args,
encs.len(),
);
let types = method_type_encoding(&Ret::ENCODING, encs);
unsafe {
ffi::protocol_addMethodDescription(
self.as_mut_ptr(),
sel.as_ptr(),
types.as_ptr(),
Bool::new(is_required).as_raw(),
Bool::new(is_instance_method).as_raw(),
);
}
}
/// Adds an instance method declaration with a given description.
pub fn add_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
where
Args: EncodeArguments,
Ret: Encode,
{
self.add_method_description_common::<Args, Ret>(sel, is_required, true)
}
/// Adds a class method declaration with a given description.
pub fn add_class_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
where
Args: EncodeArguments,
Ret: Encode,
{
self.add_method_description_common::<Args, Ret>(sel, is_required, false)
}
/// Adds a requirement on another protocol.
pub fn add_protocol(&mut self, proto: &Protocol) {
unsafe {
ffi::protocol_addProtocol(self.as_mut_ptr(), proto.as_ptr());
}
}
/// Registers the [`ProtocolBuilder`], consuming it and returning a reference
/// to the newly registered [`Protocol`].
pub fn register(mut self) -> &'static Protocol {
unsafe {
ffi::objc_registerProtocol(self.as_mut_ptr());
self.proto.as_ref()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::msg_send;
use crate::test_utils;
#[test]
fn test_classbuilder_duplicate() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicate", cls).unwrap();
let _ = builder.register();
assert!(ClassBuilder::new("TestClassBuilderDuplicate", cls).is_none());
}
#[test]
#[should_panic = "Failed to add ivar xyz"]
fn duplicate_ivar() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicateIvar", cls).unwrap();
// ManuallyDrop to work around GNUStep issue
let mut builder = ManuallyDrop::new(builder);
builder.add_ivar::<i32>("xyz");
// Should panic:
builder.add_ivar::<i32>("xyz");
}
#[test]
#[should_panic = "Failed to add method xyz"]
fn duplicate_method() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicateMethod", cls).unwrap();
let mut builder = ManuallyDrop::new(builder);
extern "C" fn xyz(_this: &Object, _cmd: Sel) {}
unsafe {
builder.add_method(sel!(xyz), xyz as extern "C" fn(_, _));
// Should panic:
builder.add_method(sel!(xyz), xyz as extern "C" fn(_, _));
}
}
#[test]
#[should_panic = "Failed to add protocol NSObject"]
fn duplicate_protocol() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicateProtocol", cls).unwrap();
let mut builder = ManuallyDrop::new(builder);
let protocol = Protocol::get("NSObject").unwrap();
builder.add_protocol(protocol);
// Should panic:
builder.add_protocol(protocol);
}
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Dropping ClassBuilder has weird threading side effects on GNUStep"
)]
fn test_classbuilder_drop() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDrop", cls).unwrap();
drop(builder);
// After we dropped the class, we can create a new one with the same name:
let _builder = ClassBuilder::new("TestClassBuilderDrop", cls).unwrap();
}
#[test]
fn test_custom_class() {
// Registering the custom class is in test_utils
let mut obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&mut obj, setFoo: 13u32] };
let result: u32 = unsafe { msg_send![&obj, foo] };
assert_eq!(result, 13);
}
#[test]
#[cfg(feature = "malloc")]
fn test_in_all_classes() {
fn is_present(cls: *const Class) -> bool {
// Check whether the class is present in Class::classes()
Class::classes()
.into_iter()
.find(|&item| ptr::eq(cls, *item))
.is_some()
}
let superclass = test_utils::custom_class();
let builder = ClassBuilder::new("TestFetchWhileCreatingClass", superclass).unwrap();
if cfg!(all(feature = "apple", target_arch = "x86_64")) {
// It is IMO a bug that it is present here!
assert!(is_present(builder.cls.as_ptr()));
} else {
assert!(!is_present(builder.cls.as_ptr()));
}
let cls = builder.register();
assert!(is_present(cls));
}
#[test]
fn test_class_method() {
let cls = test_utils::custom_class();
let result: u32 = unsafe { msg_send![cls, classFoo] };
assert_eq!(result, 7);
}
}

View file

@ -0,0 +1,422 @@
use core::fmt;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull};
use crate::encode::{EncodeConvert, Encoding};
use crate::runtime::{ivar_offset, Object};
pub(crate) mod private {
pub trait Sealed {}
}
/// Types that may be used in ivars.
///
/// This may be either:
/// - [`bool`].
/// - [`IvarDrop<T>`][super::IvarDrop].
/// - Something that implements [`Encode`][crate::Encode].
///
/// This is a sealed trait, and should not need to be implemented. Open an
/// issue if you know a use-case where this restrition should be lifted!
///
///
/// # Safety
///
/// You cannot rely on any safety guarantees from this.
pub unsafe trait InnerIvarType: private::Sealed {
#[doc(hidden)]
const __ENCODING: Encoding;
// SAFETY: It must be safe to transmute from `__Inner` to `Output`.
#[doc(hidden)]
type __Inner;
/// The type that an `Ivar` containing this will dereference to.
///
/// E.g. `Ivar<IvarDrop<Box<u8>>>` will deref to `Box<u8>`.
type Output;
// SAFETY: The __Inner type must be safe to drop even if zero-initialized.
#[doc(hidden)]
const __MAY_DROP: bool;
#[doc(hidden)]
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output;
#[doc(hidden)]
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output;
#[doc(hidden)]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output>;
}
impl<T: EncodeConvert> private::Sealed for T {}
unsafe impl<T: EncodeConvert> InnerIvarType for T {
const __ENCODING: Encoding = <Self as EncodeConvert>::__ENCODING;
type __Inner = Self;
type Output = Self;
// Note: We explicitly tell `Ivar` that it shouldn't do anything to drop,
// since if the object was deallocated before an `init` method was called,
// the ivar would not have been initialized properly!
//
// For example in the case of `NonNull<u8>`, it would be zero-initialized
// which is an invalid state for that.
const __MAY_DROP: bool = false;
#[inline]
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output {
inner
}
#[inline]
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output {
inner
}
#[inline]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
inner
}
}
/// Helper trait for defining instance variables.
///
/// This should be implemented for an empty marker type, which can then be
/// used within [`Ivar`] to refer to the instance variable.
///
///
/// # Safety
///
/// Really, [`Ivar`] should be marked as `unsafe`, but since we can't do that
/// we'll mark this trait as `unsafe` instead. See [`Ivar`] for safety
/// requirements.
///
///
/// # Examples
///
/// Create an instance variable `myCustomIvar` with type `i32`.
///
/// ```
/// use objc2::declare::IvarType;
///
/// // Helper type
/// struct MyCustomIvar;
///
/// unsafe impl IvarType for MyCustomIvar {
/// type Type = i32;
/// const NAME: &'static str = "myCustomIvar";
/// }
///
/// // `Ivar<MyCustomIvar>` can now be used
/// ```
pub unsafe trait IvarType {
/// The type of the instance variable.
type Type: InnerIvarType;
/// The name of the instance variable.
const NAME: &'static str;
#[doc(hidden)]
unsafe fn __offset(ptr: NonNull<Object>) -> isize {
let obj = unsafe { ptr.as_ref() };
ivar_offset(obj.class(), Self::NAME, &Self::Type::__ENCODING)
}
}
/// A wrapper type over a custom instance variable.
///
/// This type is not meant to be constructed by itself, it must reside within
/// another struct meant to represent an Objective-C object.
///
/// On [`Deref`] it then uses the [`IvarType::NAME`] string to access the ivar
/// of the containing object.
///
/// Note that this is not ([currently][zst-hack]) allowed by [stacked
/// borrows][sb], but due to [`Object`] being a zero-sized type such that we
/// don't have provenance over the ivars anyhow, this should be just as sound
/// as normal instance variable access.
///
/// [sb]: https://github.com/rust-lang/unsafe-code-guidelines/blob/e21202c60c7be03dd2ab016ada92fb5305d40438/wip/stacked-borrows.md
/// [zst-hack]: https://github.com/rust-lang/unsafe-code-guidelines/issues/305
///
///
/// # `bool` handling
///
/// This does _not_ perform a conversion step between [`bool`] and the
/// Objective-C `BOOL`; use [`runtime::Bool`][crate::runtime::Bool] when you
/// want your instance variable to be accessible from other Objective-C code.
///
///
/// # Safety
///
/// This must be used within a type that act as an Objective-C object. In
/// particular, this is never safe to have on the stack by itself.
///
/// Additionally, the instance variable described by `T` must be available on
/// the specific instance, and be of the exact same type. When declaring the
/// object yourself, you can ensure this using
/// [`ClassBuilder::add_static_ivar`].
///
/// Finally, two ivars with the same name must not be used on the same object.
///
/// [`ClassBuilder::add_static_ivar`]: crate::declare::ClassBuilder::add_static_ivar
///
///
/// # Examples
///
/// ```
/// use objc2::declare::{Ivar, IvarType};
/// use objc2::runtime::Object;
/// #
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// // Declare ivar with given type and name
/// struct MyCustomIvar;
/// unsafe impl IvarType for MyCustomIvar {
/// type Type = i32;
/// const NAME: &'static str = "myCustomIvar";
/// }
///
/// // Custom object
/// #[repr(C)]
/// pub struct MyObject {
/// inner: Object,
/// // SAFETY: The instance variable is used within an object, and it is
/// // properly declared below.
/// my_ivar: Ivar<MyCustomIvar>,
/// }
///
/// # use objc2::class;
/// # use objc2::declare::ClassBuilder;
/// # let mut builder = ClassBuilder::new("MyObject", class!(NSObject)).unwrap();
/// // Declare the class and add the instance variable to it
/// builder.add_static_ivar::<MyCustomIvar>();
/// # let _cls = builder.register();
///
/// let obj: MyObject;
/// // You can now access `obj.my_ivar`
/// ```
///
/// See also the `declare_ivar.rs` example.
#[repr(C)]
// Must not be `Copy` nor `Clone`!
pub struct Ivar<T: IvarType> {
/// Make this type allowed in `repr(C)`
inner: [u8; 0],
/// For proper variance and auto traits
item: PhantomData<<T::Type as InnerIvarType>::Output>,
}
impl<T: IvarType> Drop for Ivar<T> {
#[inline]
fn drop(&mut self) {
if <T::Type as InnerIvarType>::__MAY_DROP {
// SAFETY: We drop the inner type, which is guaranteed by
// `__MAY_DROP` to always be safe to drop.
unsafe { ptr::drop_in_place(self.as_inner_mut_ptr().as_ptr()) }
}
}
}
impl<T: IvarType> Ivar<T> {
/// Get a pointer to the instance variable.
///
/// Note that if the ivar has already been initialized, you can simply
/// use the `Deref` implementation to get a reference.
///
/// This is similar to [`MaybeUninit::as_ptr`], see that for usage
/// instructions.
pub fn as_ptr(this: &Self) -> *const <T::Type as InnerIvarType>::Output {
T::Type::__to_ptr(this.as_inner_ptr()).as_ptr()
}
fn as_inner_ptr(&self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
let ptr: NonNull<Object> = NonNull::from(self).cast();
// SAFETY: The user ensures that this is placed in a struct that can
// be reinterpreted as an `Object`. Since `Ivar` can never be
// constructed by itself (and is neither Copy nor Clone), we know that
// it is guaranteed to _stay_ in said struct.
//
// Even if the user were to do `mem::swap`, the `Ivar` has a unique
// type (and does not hold any data), so that wouldn't break anything.
//
// Note: We technically don't have provenance over the object, nor the
// ivar, but the object doesn't have provenance over the ivar either,
// so that is fine.
let offset = unsafe { T::__offset(ptr) };
// SAFETY: The offset is valid
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
}
/// Get a mutable pointer to the instance variable.
///
/// This is useful when you want to initialize the ivar inside an `init`
/// method (where it may otherwise not have been safely initialized yet).
///
/// Note that if the ivar has already been initialized, you can simply
/// use the `DerefMut` implementation to get a mutable reference.
///
/// This is similar to [`MaybeUninit::as_mut_ptr`], see that for usage
/// instructions.
pub fn as_mut_ptr(this: &mut Self) -> *mut <T::Type as InnerIvarType>::Output {
T::Type::__to_ptr(this.as_inner_mut_ptr()).as_ptr()
}
fn as_inner_mut_ptr(&mut self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
let ptr: NonNull<Object> = NonNull::from(self).cast();
// SAFETY: Same as `as_inner_ptr`
let offset = unsafe { T::__offset(ptr) };
// SAFETY: The offset is valid
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
}
/// Sets the value of the instance variable.
///
/// This is useful when you want to initialize the ivar inside an `init`
/// method (where it may otherwise not have been safely initialized yet).
///
/// This is similar to [`MaybeUninit::write`], see that for usage
/// instructions.
pub fn write(
this: &mut Self,
val: <T::Type as InnerIvarType>::Output,
) -> &mut <T::Type as InnerIvarType>::Output {
let ptr: *mut MaybeUninit<<T::Type as InnerIvarType>::Output> =
Self::as_mut_ptr(this).cast();
let ivar = unsafe { ptr.as_mut().unwrap_unchecked() };
ivar.write(val)
}
}
impl<T: IvarType> Deref for Ivar<T> {
type Target = <T::Type as InnerIvarType>::Output;
#[inline]
fn deref(&self) -> &Self::Target {
// SAFETY: User ensures that the `Ivar<T>` is only used when the ivar
// exists, has the correct type, and has been properly initialized.
//
// Since all accesses to a particular ivar only goes through one
// `Ivar`, if we have `&Ivar` we know that `&T` is safe.
unsafe { T::Type::__to_ref(self.as_inner_ptr().as_ref()) }
}
}
impl<T: IvarType> DerefMut for Ivar<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
// SAFETY: User ensures that the `Ivar<T>` is only used when the ivar
// exists, has the correct type, and has been properly initialized.
//
// Safe as mutable because there is only one access to a
// particular ivar at a time (since we have `&mut self`).
// Note: We're careful not to create `&mut Object` because the user
// might have two mutable references to different ivars, as such:
//
// ```
// #[repr(C)]
// struct X {
// inner: Object,
// ivar1: Ivar<Ivar1>,
// ivar2: Ivar<Ivar2>,
// }
//
// let mut x: X;
// let ivar1: &mut Ivar<Ivar1> = &mut x.ivar1;
// let ivar2: &mut Ivar<Ivar2> = &mut x.ivar2;
// ```
//
// And using `mut` would create aliasing mutable reference to the
// object.
unsafe { T::Type::__to_mut(self.as_inner_mut_ptr().as_mut()) }
}
}
/// Format as a pointer to the instance variable.
impl<T: IvarType> fmt::Pointer for Ivar<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&Self::as_ptr(self), f)
}
}
#[cfg(test)]
mod tests {
use core::mem;
use core::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::atomic::{AtomicBool, Ordering};
use super::*;
use crate::foundation::NSObject;
use crate::rc::{Id, Owned};
use crate::{declare_class, msg_send, msg_send_id, test_utils, ClassType, MessageReceiver};
struct TestIvar;
unsafe impl IvarType for TestIvar {
type Type = u32;
const NAME: &'static str = "_foo";
}
#[repr(C)]
struct IvarTestObject {
inner: Object,
foo: Ivar<TestIvar>,
}
#[test]
fn auto_traits() {
fn assert_auto_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized + Send + Sync>() {}
assert_auto_traits::<Ivar<TestIvar>>();
// Ensure that `Ivar` is zero-sized
assert_eq!(mem::size_of::<Ivar<TestIvar>>(), 0);
assert_eq!(mem::align_of::<Ivar<TestIvar>>(), 1);
}
#[test]
fn access_ivar() {
let mut obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&mut obj, setFoo: 42u32] };
let obj = unsafe {
obj.__as_raw_receiver()
.cast::<IvarTestObject>()
.as_ref()
.unwrap()
};
assert_eq!(*obj.foo, 42);
}
#[test]
fn ensure_custom_drop_is_possible() {
static HAS_RUN_DEALLOC: AtomicBool = AtomicBool::new(false);
declare_class!(
#[derive(Debug, PartialEq)]
struct CustomDrop {
ivar: u8,
}
unsafe impl ClassType for CustomDrop {
type Super = NSObject;
}
unsafe impl CustomDrop {
#[sel(dealloc)]
fn dealloc(&mut self) {
HAS_RUN_DEALLOC.store(true, Ordering::SeqCst);
unsafe { msg_send![super(self), dealloc] }
}
}
);
let _: Id<CustomDrop, Owned> = unsafe { msg_send_id![CustomDrop::class(), new] };
assert!(HAS_RUN_DEALLOC.load(Ordering::SeqCst));
}
}

View file

@ -0,0 +1,333 @@
use alloc::boxed::Box;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::ptr::NonNull;
use crate::encode::{EncodeConvert, Encoding};
use crate::rc::{Id, Ownership};
use crate::Message;
use super::InnerIvarType;
/// A helper type to allow putting certain types that may drop into ivars.
///
/// This is used to work around current limitations in the type system.
/// Consider this type "temporary" in the sense that one day it may just
/// become `type IvarDrop<T> = T`.
///
/// This currently works with the following types:
/// - `Box<T>`
/// - `Option<Box<T>>`
/// - `Id<T, O>`
/// - `Option<Id<T, O>>`
///
/// Further may be added when the standard library guarantees their layout.
///
/// See `examples/delegate.rs` for usage.
pub struct IvarDrop<T> {
/// For proper variance and auto traits.
p: PhantomData<T>,
}
impl<T: Sized> super::ivar::private::Sealed for IvarDrop<Box<T>> {}
// SAFETY: The memory layout of `Box<T: Sized>` is guaranteed to be a pointer:
// <https://doc.rust-lang.org/1.62.1/std/boxed/index.html#memory-layout>
//
// The user ensures that the Box has been initialized in an `init` method
// before being used.
unsafe impl<T: Sized> InnerIvarType for IvarDrop<Box<T>> {
// Note that we use `*const c_void` and not `*const T` to allow _any_
// type, not just types that can be encoded by Objective-C
const __ENCODING: Encoding = <*const c_void as EncodeConvert>::__ENCODING;
type __Inner = Option<Box<T>>;
type Output = Box<T>;
const __MAY_DROP: bool = true;
#[inline]
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output {
match inner {
Some(inner) => inner,
None => unsafe { box_unreachable() },
}
}
#[inline]
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output {
match inner {
Some(inner) => inner,
None => unsafe { box_unreachable() },
}
}
#[inline]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
inner.cast()
}
}
impl<T: Sized> super::ivar::private::Sealed for IvarDrop<Option<Box<T>>> {}
// SAFETY: `Option<Box<T>>` guarantees the null-pointer optimization, so for
// `T: Sized` the layout is just a pointer:
// <https://doc.rust-lang.org/1.62.1/std/option/index.html#representation>
//
// This is valid to initialize as all-zeroes, so the user doesn't have to do
// anything to initialize it.
unsafe impl<T: Sized> InnerIvarType for IvarDrop<Option<Box<T>>> {
const __ENCODING: Encoding = <*const c_void as EncodeConvert>::__ENCODING;
type __Inner = Option<Box<T>>;
type Output = Option<Box<T>>;
const __MAY_DROP: bool = true;
#[inline]
unsafe fn __to_ref(this: &Self::__Inner) -> &Self::Output {
this
}
#[inline]
unsafe fn __to_mut(this: &mut Self::__Inner) -> &mut Self::Output {
this
}
#[inline]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
inner.cast()
}
}
impl<T: Message, O: Ownership> super::ivar::private::Sealed for IvarDrop<Id<T, O>> {}
// SAFETY: `Id` is `NonNull<T>`, and hence safe to store as a pointer.
//
// The user ensures that the Id has been initialized in an `init` method
// before being used.
//
// Note: We could technically do `impl InnerIvarType for Ivar<Id<T, O>>`
// directly today, but since we can't do so for `Box` (because that is
// `#[fundamental]`), I think it makes sense to handle them similarly.
unsafe impl<T: Message, O: Ownership> InnerIvarType for IvarDrop<Id<T, O>> {
const __ENCODING: Encoding = <*const T as EncodeConvert>::__ENCODING;
type __Inner = Option<Id<T, O>>;
type Output = Id<T, O>;
const __MAY_DROP: bool = true;
#[inline]
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output {
match inner {
Some(inner) => inner,
None => unsafe { id_unreachable() },
}
}
#[inline]
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output {
match inner {
Some(inner) => inner,
None => unsafe { id_unreachable() },
}
}
#[inline]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
inner.cast()
}
}
impl<T: Message, O: Ownership> super::ivar::private::Sealed for IvarDrop<Option<Id<T, O>>> {}
// SAFETY: `Id<T, O>` guarantees the null-pointer optimization.
//
// This is valid to initialize as all-zeroes, so the user doesn't have to do
// anything to initialize it.
unsafe impl<T: Message, O: Ownership> InnerIvarType for IvarDrop<Option<Id<T, O>>> {
const __ENCODING: Encoding = <*const T as EncodeConvert>::__ENCODING;
type __Inner = Option<Id<T, O>>;
type Output = Option<Id<T, O>>;
const __MAY_DROP: bool = true;
#[inline]
unsafe fn __to_ref(this: &Self::__Inner) -> &Self::Output {
this
}
#[inline]
unsafe fn __to_mut(this: &mut Self::__Inner) -> &mut Self::Output {
this
}
#[inline]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
inner.cast()
}
}
// TODO: Allow the following once their layout is guaranteed by `std`:
// - Arc<T>
// - Option<Arc<T>>
// - sync::Weak<T>
// - Rc<T>
// - Option<Rc<T>>
// - rc::Weak<T>
// - Vec<T>
// - String
// TODO: Allow `WeakId` once we figure out how to allow it being initialized
// by default.
#[inline]
unsafe fn id_unreachable() -> ! {
#[cfg(debug_assertions)]
{
unreachable!("an Id in instance variables must always be initialized before use!")
}
// SAFETY: Checked by caller
#[cfg(not(debug_assertions))]
unsafe {
core::hint::unreachable_unchecked()
}
}
#[inline]
unsafe fn box_unreachable() -> ! {
#[cfg(debug_assertions)]
{
unreachable!("a Box in instance variables must always be initialized before use!")
}
// SAFETY: Checked by caller
#[cfg(not(debug_assertions))]
unsafe {
core::hint::unreachable_unchecked()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::declare::{Ivar, IvarType};
use crate::foundation::NSObject;
use crate::rc::{Allocated, Owned, RcTestObject, Shared, ThreadTestData};
use crate::runtime::Object;
use crate::{declare_class, msg_send, msg_send_id, ClassType};
struct TestIvar1;
unsafe impl IvarType for TestIvar1 {
type Type = IvarDrop<Box<u8>>;
const NAME: &'static str = "_abc";
}
struct TestIvar2;
unsafe impl IvarType for TestIvar2 {
type Type = IvarDrop<Option<Box<u8>>>;
const NAME: &'static str = "_abc";
}
struct TestIvar3;
unsafe impl IvarType for TestIvar3 {
type Type = IvarDrop<Id<Object, Shared>>;
const NAME: &'static str = "_abc";
}
struct TestIvar4;
unsafe impl IvarType for TestIvar4 {
type Type = IvarDrop<Option<Id<Object, Owned>>>;
const NAME: &'static str = "_abc";
}
declare_class!(
#[derive(Debug, PartialEq)]
struct IvarTester {
ivar1: IvarDrop<Id<RcTestObject, Shared>>,
ivar2: IvarDrop<Option<Id<RcTestObject, Owned>>>,
ivar3: IvarDrop<Box<Id<RcTestObject, Owned>>>,
ivar4: IvarDrop<Option<Box<Id<RcTestObject, Owned>>>>,
}
unsafe impl ClassType for IvarTester {
type Super = NSObject;
}
unsafe impl IvarTester {
#[sel(init)]
fn init(&mut self) -> Option<&mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
Ivar::write(&mut this.ivar1, Id::into_shared(RcTestObject::new()));
*this.ivar2 = Some(RcTestObject::new());
Ivar::write(&mut this.ivar3, Box::new(RcTestObject::new()));
*this.ivar4 = Some(Box::new(RcTestObject::new()));
this
})
}
#[sel(initInvalid)]
fn init_invalid(&mut self) -> Option<&mut Self> {
// Don't actually initialize anything here; this creates an
// invalid instance, where accessing the two ivars `ivar1`
// and `ivar3` is UB
unsafe { msg_send![super(self), init] }
}
}
);
#[test]
fn test_alloc_dealloc() {
let expected = ThreadTestData::current();
let obj: Id<Allocated<IvarTester>, Owned> =
unsafe { msg_send_id![IvarTester::class(), alloc] };
expected.assert_current();
drop(obj);
expected.assert_current();
}
#[test]
fn test_init_drop() {
let mut expected = ThreadTestData::current();
let mut obj: Id<IvarTester, Owned> = unsafe { msg_send_id![IvarTester::class(), new] };
expected.alloc += 4;
expected.init += 4;
expected.assert_current();
*obj.ivar1 = (*obj.ivar1).clone();
expected.retain += 1;
expected.release += 1;
expected.assert_current();
*obj.ivar2 = None;
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
drop(obj);
expected.release += 3;
expected.dealloc += 3;
expected.assert_current();
}
#[test]
#[cfg_attr(not(debug_assertions), ignore = "only panics in debug mode")]
#[should_panic = "an Id in instance variables must always be initialized before use"]
fn test_init_invalid_ref() {
let obj: Id<IvarTester, Owned> =
unsafe { msg_send_id![msg_send_id![IvarTester::class(), alloc], initInvalid] };
std::println!("{:?}", obj.ivar1);
}
#[test]
#[cfg_attr(not(debug_assertions), ignore = "only panics in debug mode")]
#[should_panic = "an Id in instance variables must always be initialized before use"]
fn test_init_invalid_mut() {
let mut obj: Id<IvarTester, Owned> =
unsafe { msg_send_id![msg_send_id![IvarTester::class(), alloc], initInvalid] };
*obj.ivar1 = RcTestObject::new().into();
}
}

View file

@ -0,0 +1,334 @@
//! Trivial forwarding impls on `Ivar`.
//!
//! Kept here to keep `ivar.rs` free from this boilerplate.
//!
//! `#[inline]` is used where the standard library `Box` uses it.
#![forbid(unsafe_code)]
// use alloc::borrow;
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::fmt;
use core::future::Future;
use core::hash;
use core::iter::FusedIterator;
use core::ops::{Deref, DerefMut};
use core::pin::Pin;
use core::task::{Context, Poll};
use std::error::Error;
use std::io;
use super::{Ivar, IvarType};
impl<T: IvarType> PartialEq for Ivar<T>
where
<Self as Deref>::Target: PartialEq,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
(**self).eq(&**other)
}
#[inline]
#[allow(clippy::partialeq_ne_impl)]
fn ne(&self, other: &Self) -> bool {
(**self).ne(&**other)
}
}
impl<T: IvarType> Eq for Ivar<T> where <Self as Deref>::Target: Eq {}
impl<T: IvarType> PartialOrd for Ivar<T>
where
<Self as Deref>::Target: PartialOrd,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(**self).partial_cmp(&**other)
}
#[inline]
fn lt(&self, other: &Self) -> bool {
(**self).lt(&**other)
}
#[inline]
fn le(&self, other: &Self) -> bool {
(**self).le(&**other)
}
#[inline]
fn ge(&self, other: &Self) -> bool {
(**self).ge(&**other)
}
#[inline]
fn gt(&self, other: &Self) -> bool {
(**self).gt(&**other)
}
}
impl<T: IvarType> Ord for Ivar<T>
where
<Self as Deref>::Target: Ord,
{
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(**self).cmp(&**other)
}
}
impl<T: IvarType> hash::Hash for Ivar<T>
where
<Self as Deref>::Target: hash::Hash,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(**self).hash(state)
}
}
impl<T: IvarType> hash::Hasher for Ivar<T>
where
<Self as Deref>::Target: hash::Hasher,
{
fn finish(&self) -> u64 {
(**self).finish()
}
fn write(&mut self, bytes: &[u8]) {
(**self).write(bytes)
}
fn write_u8(&mut self, i: u8) {
(**self).write_u8(i)
}
fn write_u16(&mut self, i: u16) {
(**self).write_u16(i)
}
fn write_u32(&mut self, i: u32) {
(**self).write_u32(i)
}
fn write_u64(&mut self, i: u64) {
(**self).write_u64(i)
}
fn write_u128(&mut self, i: u128) {
(**self).write_u128(i)
}
fn write_usize(&mut self, i: usize) {
(**self).write_usize(i)
}
fn write_i8(&mut self, i: i8) {
(**self).write_i8(i)
}
fn write_i16(&mut self, i: i16) {
(**self).write_i16(i)
}
fn write_i32(&mut self, i: i32) {
(**self).write_i32(i)
}
fn write_i64(&mut self, i: i64) {
(**self).write_i64(i)
}
fn write_i128(&mut self, i: i128) {
(**self).write_i128(i)
}
fn write_isize(&mut self, i: isize) {
(**self).write_isize(i)
}
}
impl<T: IvarType> fmt::Display for Ivar<T>
where
<Self as Deref>::Target: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: IvarType> fmt::Debug for Ivar<T>
where
<Self as Deref>::Target: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<I: IvarType> Iterator for Ivar<I>
where
<Self as Deref>::Target: Iterator,
{
type Item = <<Self as Deref>::Target as Iterator>::Item;
fn next(&mut self) -> Option<<<Self as Deref>::Target as Iterator>::Item> {
(**self).next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(**self).size_hint()
}
}
impl<I: IvarType> DoubleEndedIterator for Ivar<I>
where
<Self as Deref>::Target: DoubleEndedIterator,
{
fn next_back(&mut self) -> Option<<<Self as Deref>::Target as Iterator>::Item> {
(**self).next_back()
}
fn nth_back(&mut self, n: usize) -> Option<<<Self as Deref>::Target as Iterator>::Item> {
(**self).nth_back(n)
}
}
impl<I: IvarType> ExactSizeIterator for Ivar<I>
where
<Self as Deref>::Target: ExactSizeIterator,
{
fn len(&self) -> usize {
(**self).len()
}
}
impl<I: IvarType> FusedIterator for Ivar<I> where <Self as Deref>::Target: FusedIterator {}
// impl<T: IvarType> borrow::Borrow<<Self as Deref>::Target> for Ivar<T> {
// fn borrow(&self) -> &<Self as Deref>::Target {
// Deref::deref(self)
// }
// }
//
// impl<T: IvarType> borrow::BorrowMut<<Self as Deref>::Target> for Ivar<T> {
// fn borrow_mut(&mut self) -> &mut <Self as Deref>::Target {
// DerefMut::deref_mut(self)
// }
// }
impl<T: IvarType> AsRef<<Self as Deref>::Target> for Ivar<T> {
fn as_ref(&self) -> &<Self as Deref>::Target {
Deref::deref(self)
}
}
impl<T: IvarType> AsMut<<Self as Deref>::Target> for Ivar<T> {
fn as_mut(&mut self) -> &mut <Self as Deref>::Target {
DerefMut::deref_mut(self)
}
}
impl<T: IvarType> Error for Ivar<T>
where
<Self as Deref>::Target: Error,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
(**self).source()
}
}
impl<T: IvarType> io::Read for Ivar<T>
where
<Self as Deref>::Target: io::Read,
{
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(**self).read(buf)
}
#[inline]
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
(**self).read_vectored(bufs)
}
#[inline]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
(**self).read_to_end(buf)
}
#[inline]
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
(**self).read_to_string(buf)
}
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
(**self).read_exact(buf)
}
}
impl<T: IvarType> io::Write for Ivar<T>
where
<Self as Deref>::Target: io::Write,
{
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(**self).write(buf)
}
#[inline]
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
(**self).write_vectored(bufs)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
(**self).flush()
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
(**self).write_all(buf)
}
#[inline]
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
(**self).write_fmt(fmt)
}
}
impl<T: IvarType> io::Seek for Ivar<T>
where
<Self as Deref>::Target: io::Seek,
{
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
(**self).seek(pos)
}
#[inline]
fn stream_position(&mut self) -> io::Result<u64> {
(**self).stream_position()
}
}
impl<T: IvarType> io::BufRead for Ivar<T>
where
<Self as Deref>::Target: io::BufRead,
{
#[inline]
fn fill_buf(&mut self) -> io::Result<&[u8]> {
(**self).fill_buf()
}
#[inline]
fn consume(&mut self, amt: usize) {
(**self).consume(amt)
}
#[inline]
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> io::Result<usize> {
(**self).read_until(byte, buf)
}
#[inline]
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
(**self).read_line(buf)
}
}
impl<T: IvarType> Future for Ivar<T>
where
<Self as Deref>::Target: Future + Unpin,
{
type Output = <<Self as Deref>::Target as Future>::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
<<Self as Deref>::Target as Future>::poll(Pin::new(&mut *self), cx)
}
}
// TODO: impl Fn traits, CoerceUnsized, Stream and so on when stabilized

View file

@ -0,0 +1,307 @@
//! Objective-C's @throw and @try/@catch.
//!
//! By default, if the [`msg_send!`] macro causes an exception to be thrown,
//! this will unwind into Rust, resulting in undefined behavior. However, this
//! crate has an `"catch-all"` feature which, when enabled, wraps each
//! [`msg_send!`] in a `@catch` and panics if an exception is caught,
//! preventing Objective-C from unwinding into Rust.
//!
//! The `@try`/`@catch` functionality in this module is only available when
//! the `"exception"` feature is enabled.
//!
//! See the following links for more information:
//! - [Exception Programming Topics for Cocoa](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html)
//! - [The Objective-C Programming Language - Exception Handling](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocExceptionHandling.html)
//! - [Exception Handling in LLVM](https://llvm.org/docs/ExceptionHandling.html)
//!
//! [`msg_send!`]: crate::msg_send
// TODO: Test this with panic=abort, and ensure that the code-size is
// reasonable in that case.
#[cfg(feature = "exception")]
use core::ffi::c_void;
use core::fmt;
#[cfg(feature = "exception")]
use core::mem;
use core::ops::Deref;
use core::panic::RefUnwindSafe;
use core::panic::UnwindSafe;
#[cfg(feature = "exception")]
use core::ptr;
use objc2_encode::Encoding;
use objc2_encode::RefEncode;
use std::error::Error;
#[cfg(feature = "exception")]
use crate::ffi;
#[cfg(feature = "exception")]
use crate::rc::{Id, Shared};
use crate::runtime::Object;
use crate::Message;
/// An Objective-C exception.
///
/// While highly recommended that any exceptions you intend to throw are
/// subclasses of `NSException`, this is not required by the runtime (similar
/// to how Rust can panic with arbitary payloads using [`panic_any`]).
///
/// [`panic_any`]: std::panic::panic_any
#[repr(transparent)]
pub struct Exception(Object);
unsafe impl RefEncode for Exception {
const ENCODING_REF: Encoding = Encoding::Object;
}
unsafe impl Message for Exception {}
impl Deref for Exception {
type Target = Object;
#[inline]
fn deref(&self) -> &Object {
&self.0
}
}
impl AsRef<Object> for Exception {
#[inline]
fn as_ref(&self) -> &Object {
self
}
}
// Note: We can't implement `Send` nor `Sync` since the exception could be
// anything!
impl fmt::Debug for Exception {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "exception ")?;
// Attempt to present a somewhat usable error message if the
// `foundation` feature is enabled
#[cfg(feature = "foundation")]
if crate::foundation::NSException::is_nsexception(self) {
// SAFETY: Just checked that object is an NSException
let obj: *const Self = self;
let obj = unsafe {
obj.cast::<crate::foundation::NSException>()
.as_ref()
.unwrap()
};
return write!(f, "{:?}", obj);
}
// Fall back to `Object` Debug
write!(f, "{:?}", self.0)
}
}
impl fmt::Display for Exception {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "foundation")]
if crate::foundation::NSException::is_nsexception(self) {
// SAFETY: Just checked that object is an NSException
let obj: *const Self = self;
let obj = unsafe {
obj.cast::<crate::foundation::NSException>()
.as_ref()
.unwrap()
};
if let Some(reason) = obj.reason() {
return write!(f, "{}", reason);
}
}
write!(f, "unknown exception")
}
}
impl Error for Exception {}
impl UnwindSafe for Exception {}
impl RefUnwindSafe for Exception {}
/// Throws an Objective-C exception.
///
/// This is the Objective-C equivalent of Rust's [`panic!`].
///
///
/// # Safety
///
/// This unwinds from Objective-C, and the exception must be caught using an
/// Objective-C exception handler like [`catch`] (and specifically not
/// [`catch_unwind`]).
///
/// This also invokes undefined behaviour until `C-unwind` is stabilized, see
/// [RFC-2945] - you can try this out on nightly using the `unstable-c-unwind`
/// feature flag.
///
/// [`catch_unwind`]: std::panic::catch_unwind
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
#[inline]
#[cfg(feature = "exception")] // For consistency, not strictly required
pub unsafe fn throw(exception: Id<Exception, Shared>) -> ! {
let ptr = exception.0.as_ptr() as *mut ffi::objc_object;
// SAFETY: Object is valid and non-null (nil exceptions are not valid in
// the old runtime).
unsafe { ffi::objc_exception_throw(ptr) }
}
#[cfg(feature = "exception")]
unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Exception, Shared>>> {
#[cfg(not(feature = "unstable-c-unwind"))]
let f = {
extern "C" fn try_objc_execute_closure<F>(closure: &mut Option<F>)
where
F: FnOnce(),
{
// This is always passed Some, so it's safe to unwrap
let closure = closure.take().unwrap();
closure();
}
let f: extern "C" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C" fn(*mut c_void) = unsafe { mem::transmute(f) };
f
};
#[cfg(feature = "unstable-c-unwind")]
let f = {
extern "C-unwind" fn try_objc_execute_closure<F>(closure: &mut Option<F>)
where
F: FnOnce(),
{
// This is always passed Some, so it's safe to unwrap
let closure = closure.take().unwrap();
closure();
}
let f: extern "C-unwind" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C-unwind" fn(*mut c_void) = unsafe { mem::transmute(f) };
f
};
// Wrap the closure in an Option so it can be taken
let mut closure = Some(closure);
let context: *mut Option<F> = &mut closure;
let context = context.cast();
let mut exception = ptr::null_mut();
let success = unsafe { ffi::rust_objc_sys_0_2_try_catch_exception(f, context, &mut exception) };
if success == 0 {
Ok(())
} else {
// SAFETY:
// The exception is always a valid object or NULL.
//
// The ownership is safe as Shared; Objective-C code throwing an
// exception knows that they don't hold sole access to that exception
// instance any more, and Rust code is forbidden by requiring a Shared
// Id in `throw` (instead of just a shared reference, which could have
// come from an Owned Id).
Err(unsafe { Id::new(exception.cast()) })
}
}
/// Tries to execute the given closure and catches an Objective-C exception
/// if one is thrown.
///
/// This is the Objective-C equivalent of Rust's [`catch_unwind`].
/// Accordingly, if your Rust code is compiled with `panic=abort` this cannot
/// catch the exception.
///
/// Returns a `Result` that is either `Ok` if the closure succeeded without an
/// exception being thrown, or an `Err` with the exception. The exception is
/// automatically released.
///
/// The exception is `None` in the extremely exceptional case that the
/// exception object is `nil`. This should basically never happen, but is
/// technically possible on some systems with `@throw nil`.
///
/// [`catch_unwind`]: std::panic::catch_unwind
///
///
/// # Safety
///
/// The given closure must not panic (e.g. normal Rust unwinding into this
/// causes undefined behaviour).
///
/// Additionally, this unwinds through the closure from Objective-C, which is
/// undefined behaviour until `C-unwind` is stabilized, see [RFC-2945] - you
/// can try this out on nightly using the `unstable-c-unwind` feature flag.
///
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
#[cfg(feature = "exception")]
pub unsafe fn catch<R>(
closure: impl FnOnce() -> R + UnwindSafe,
) -> Result<R, Option<Id<Exception, Shared>>> {
let mut value = None;
let value_ref = &mut value;
let closure = move || {
*value_ref = Some(closure());
};
let result = unsafe { try_no_ret(closure) };
// If the try succeeded, this was set so it's safe to unwrap
result.map(|()| value.unwrap())
}
#[cfg(test)]
#[cfg(feature = "exception")]
mod tests {
use alloc::format;
use alloc::string::ToString;
use super::*;
use crate::{class, msg_send_id};
#[test]
fn test_catch() {
let mut s = "Hello".to_string();
let result = unsafe {
catch(move || {
s.push_str(", World!");
s
})
};
assert_eq!(result.unwrap(), "Hello, World!");
}
#[test]
#[cfg_attr(
all(feature = "apple", target_os = "macos", target_arch = "x86"),
ignore = "`NULL` exceptions are invalid on 32-bit / w. fragile runtime"
)]
fn test_catch_null() {
let s = "Hello".to_string();
let result = unsafe {
catch(move || {
if !s.is_empty() {
ffi::objc_exception_throw(ptr::null_mut())
}
s.len()
})
};
assert!(result.unwrap_err().is_none());
}
#[test]
fn test_throw_catch_object() {
let obj: Id<Exception, Shared> = unsafe { msg_send_id![class!(NSObject), new] };
// TODO: Investigate why this is required on GNUStep!
let _obj2 = obj.clone();
let ptr: *const Exception = &*obj;
let result = unsafe { catch(|| throw(obj)) };
let obj = result.unwrap_err().unwrap();
assert_eq!(
format!("{:?}", obj),
format!("exception <NSObject: {:p}>", ptr)
);
assert!(ptr::eq(&*obj, ptr));
}
}

View file

@ -0,0 +1,523 @@
//! Macro for making a static NSString.
//!
//! This basically does what clang does, see:
//! - Apple: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5057-L5249>
//! - GNUStep 2.0 (not yet supported): <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCGNU.cpp#L973-L1118>
//! - Other (not yet supported): <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCGNU.cpp#L2471-L2507>
//!
//! Note that this uses the `CFString` static, while `clang` has support for
//! generating a pure `NSString`. We don't support that yet (since I don't
//! know the use-case), but we definitely could!
//! See: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCMac.cpp#L2007-L2068>
use core::ffi::c_void;
use core::mem::ManuallyDrop;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
use crate::foundation::NSString;
use crate::rc::Id;
use crate::runtime::Class;
// This is defined in CoreFoundation, but we don't emit a link attribute
// here because it is already linked via Foundation.
//
// Although this is a "private" (underscored) symbol, it is directly
// referenced in Objective-C binaries. So it's safe for us to reference.
extern "C" {
pub static __CFConstantStringClassReference: Class;
}
/// Structure used to describe a constant `CFString`.
///
/// This struct is the same as [`CF_CONST_STRING`], which contains
/// [`CFRuntimeBase`]. While the documentation clearly says that the ABI of
/// `CFRuntimeBase` should not be relied on, we can rely on it as long as we
/// only do it with regards to `CFString` (because `clang` does this as well).
///
/// [`CFRuntimeBase`]: <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.h#L216-L228>
/// [`CF_CONST_STRING`]: <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFInternal.h#L332-L336>
#[repr(C)]
pub struct CFConstString {
isa: &'static Class,
// Important that we don't just use `usize` here, since that would be
// wrong on big-endian systems!
cfinfo: u32,
#[cfg(target_pointer_width = "64")]
_rc: u32,
data: *const c_void,
len: usize,
}
// Required to place in a `static`.
unsafe impl Sync for CFConstString {}
impl CFConstString {
// From `CFString.c`:
// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFString.c#L184-L212>
// > !!! Note: Constant CFStrings use the bit patterns:
// > C8 (11001000 = default allocator, not inline, not freed contents; 8-bit; has NULL byte; doesn't have length; is immutable)
// > D0 (11010000 = default allocator, not inline, not freed contents; Unicode; is immutable)
// > The bit usages should not be modified in a way that would effect these bit patterns.
//
// Hence CoreFoundation guarantees that these two are always valid.
//
// The `CFTypeID` of `CFStringRef` is guaranteed to always be 7:
// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.c#L982>
const FLAGS_ASCII: u32 = 0x07_C8;
const FLAGS_UTF16: u32 = 0x07_D0;
pub const unsafe fn new_ascii(isa: &'static Class, data: &'static [u8]) -> Self {
Self {
isa,
cfinfo: Self::FLAGS_ASCII,
#[cfg(target_pointer_width = "64")]
_rc: 0,
data: data.as_ptr().cast(),
// The length does not include the trailing NUL.
len: data.len() - 1,
}
}
pub const unsafe fn new_utf16(isa: &'static Class, data: &'static [u16]) -> Self {
Self {
isa,
cfinfo: Self::FLAGS_UTF16,
#[cfg(target_pointer_width = "64")]
_rc: 0,
data: data.as_ptr().cast(),
// The length does not include the trailing NUL.
len: data.len() - 1,
}
}
#[inline]
pub const fn as_nsstring_const(&self) -> &NSString {
let ptr: *const Self = self;
unsafe { &*ptr.cast::<NSString>() }
}
// This is deliberately not `const` to prevent the result from being used
// in other statics, since not all platforms support that (yet).
#[inline]
pub fn as_nsstring(&self) -> &NSString {
self.as_nsstring_const()
}
}
/// Returns `true` if `bytes` is entirely ASCII with no interior NULs.
pub const fn is_ascii_no_nul(bytes: &[u8]) -> bool {
let mut i = 0;
while i < bytes.len() {
let byte = bytes[i];
if !byte.is_ascii() || byte == b'\0' {
return false;
}
i += 1;
}
true
}
pub struct Utf16Char {
pub repr: [u16; 2],
pub len: usize,
}
impl Utf16Char {
const fn encode(ch: u32) -> Self {
if ch <= 0xffff {
Self {
repr: [ch as u16, 0],
len: 1,
}
} else {
let payload = ch - 0x10000;
let hi = (payload >> 10) | 0xd800;
let lo = (payload & 0x3ff) | 0xdc00;
Self {
repr: [hi as u16, lo as u16],
len: 2,
}
}
}
#[cfg(test)]
fn as_slice(&self) -> &[u16] {
&self.repr[..self.len]
}
}
pub struct EncodeUtf16Iter {
str: &'static [u8],
index: usize,
}
impl EncodeUtf16Iter {
pub const fn new(str: &'static [u8]) -> Self {
Self { str, index: 0 }
}
pub const fn next(self) -> Option<(Self, Utf16Char)> {
if self.index >= self.str.len() {
None
} else {
let (index, ch) = decode_utf8(self.str, self.index);
Some((Self { index, ..self }, Utf16Char::encode(ch)))
}
}
}
// (&str bytes, index) -> (new index, decoded char)
const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
let b0 = s[i];
match b0 {
// one-byte seq
0b0000_0000..=0b0111_1111 => {
let decoded = b0 as u32;
(i + 1, decoded)
}
// two-byte seq
0b1100_0000..=0b1101_1111 => {
let decoded = ((b0 as u32 & 0x1f) << 6) | (s[i + 1] as u32 & 0x3f);
(i + 2, decoded)
}
// 3 byte seq
0b1110_0000..=0b1110_1111 => {
let decoded = ((b0 as u32 & 0x0f) << 12)
| ((s[i + 1] as u32 & 0x3f) << 6)
| (s[i + 2] as u32 & 0x3f);
(i + 3, decoded)
}
// 4 byte seq
0b1111_0000..=0b1111_0111 => {
let decoded = ((b0 as u32 & 0x07) << 18)
| ((s[i + 1] as u32 & 0x3f) << 12)
| ((s[i + 2] as u32 & 0x3f) << 6)
| (s[i + 3] as u32 & 0x3f);
(i + 4, decoded)
}
// continuation bytes, or never-valid bytes.
0b1000_0000..=0b1011_1111 | 0b1111_1000..=0b1111_1111 => {
panic!("Encountered invalid bytes")
}
}
}
/// Allows storing a [`NSString`] in a static and lazily loading it.
#[doc(hidden)]
pub struct CachedNSString {
ptr: AtomicPtr<NSString>,
}
impl CachedNSString {
/// Constructs a new [`CachedNSString`].
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
/// Returns the cached NSString. If no string is yet cached, creates one
/// with the given name and stores it.
#[inline]
pub fn get(&self, s: &str) -> &'static NSString {
// TODO: Investigate if we can use weaker orderings.
let ptr = self.ptr.load(Ordering::SeqCst);
// SAFETY: The pointer is either NULL, or has been created below.
unsafe { ptr.as_ref() }.unwrap_or_else(|| {
// "Forget" about releasing the string, effectively promoting it
// to a static.
let s = ManuallyDrop::new(NSString::from_str(s));
let ptr = Id::as_ptr(&s);
self.ptr.store(ptr as *mut NSString, Ordering::SeqCst);
// SAFETY: The pointer is valid, and will always be valid, since
// we haven't released it.
unsafe { ptr.as_ref().unwrap_unchecked() }
})
}
}
/// Creates an [`NSString`][`crate::foundation::NSString`] from a static string.
///
///
/// # Examples
///
/// This macro takes a either a `"string"` literal or `const` string slice as
/// the argument, and produces a `&'static NSString`:
///
/// ```
/// use objc2::ns_string;
/// use objc2::foundation::NSString;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let hello: &'static NSString = ns_string!("hello");
/// assert_eq!(hello.to_string(), "hello");
///
/// const WORLD: &str = "world";
/// let world = ns_string!(WORLD);
/// assert_eq!(world.to_string(), WORLD);
/// ```
///
///
/// # Unicode Strings
///
/// An NSString can contain strings with many different encodings, including
/// ASCII, UTF-8, UTF-16, and so on. This macro automatically converts your
/// string to the most efficient encoding, you don't have to do anything!
///
/// ```
/// # use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let hello_ru = ns_string!("Привет");
/// assert_eq!(hello_ru.to_string(), "Привет");
/// ```
///
/// Note that because this is implemented with `const` evaluation, massive
/// strings can increase compile time, and may even hit the `const` evaluation
/// limit.
///
///
/// # NUL handling
///
/// Strings containing ASCII NUL is allowed, the NUL is preserved as one would
/// expect:
///
/// ```
/// # use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let example = ns_string!("example\0");
/// assert_eq!(example.to_string(), "example\0");
///
/// let example = ns_string!("exa\0mple");
/// assert_eq!(example.to_string(), "exa\0mple");
/// ```
///
///
/// # Runtime Cost
///
/// None.
///
/// The result is equivalent to `@"string"` syntax in Objective-C.
///
/// Because of that, this should be preferred over [`NSString::from_str`]
/// where possible.
///
/// [`NSString::from_str`]: crate::foundation::NSString::from_str
#[cfg(feature = "foundation")] // For auto_doc_cfg
#[macro_export]
macro_rules! ns_string {
($s:expr) => {{
// Immediately place in constant for better UI
const INPUT: &str = $s;
$crate::__ns_string_inner!(INPUT)
}};
}
#[doc(hidden)]
#[cfg(feature = "apple")]
#[macro_export]
macro_rules! __ns_string_inner {
($inp:ident) => {{
const X: &[u8] = $inp.as_bytes();
$crate::__ns_string_inner!(@inner X);
// Return &'static NSString
CFSTRING.as_nsstring()
}};
(@inner $inp:ident) => {
// Note: We create both the ASCII + NUL and the UTF-16 + NUL versions
// of the string, since we can't conditionally create a static.
//
// Since we don't add the `#[used]` attribute, Rust can fairly easily
// figure out that one of the variants are never used, and simply
// exclude it.
// Convert the input slice to a C-style string with a NUL byte.
//
// The section is the same as what clang sets, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192
#[link_section = "__TEXT,__cstring,cstring_literals"]
static ASCII: [u8; $inp.len() + 1] = {
// Zero-fill with $inp.len() + 1
let mut res: [u8; $inp.len() + 1] = [0; $inp.len() + 1];
let mut i = 0;
// Fill with data from $inp
while i < $inp.len() {
res[i] = $inp[i];
i += 1;
}
// Now contains $inp + '\0'
res
};
// The full UTF-16 contents along with the written length.
const UTF16_FULL: (&[u16; $inp.len()], usize) = {
let mut out = [0u16; $inp.len()];
let mut iter = $crate::foundation::__ns_string::EncodeUtf16Iter::new($inp);
let mut written = 0;
while let Some((state, chars)) = iter.next() {
iter = state;
out[written] = chars.repr[0];
written += 1;
if chars.len > 1 {
out[written] = chars.repr[1];
written += 1;
}
}
(&{ out }, written)
};
// Convert the slice to an UTF-16 array + a final NUL byte.
//
// The section is the same as what clang sets, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5193
#[link_section = "__TEXT,__ustring"]
static UTF16: [u16; UTF16_FULL.1 + 1] = {
// Zero-fill with UTF16_FULL.1 + 1
let mut res: [u16; UTF16_FULL.1 + 1] = [0; UTF16_FULL.1 + 1];
let mut i = 0;
// Fill with data from UTF16_FULL.0 up until UTF16_FULL.1
while i < UTF16_FULL.1 {
res[i] = UTF16_FULL.0[i];
i += 1;
}
// Now contains UTF16_FULL.1 + NUL
res
};
// Create the constant string structure, and store it in a static
// within a special section.
//
// The section is the same as what clang sets, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243
#[link_section = "__DATA,__cfstring"]
static CFSTRING: $crate::foundation::__ns_string::CFConstString = unsafe {
if $crate::foundation::__ns_string::is_ascii_no_nul($inp) {
// This is technically an optimization (UTF-16 strings are
// always valid), but it's a fairly important one!
$crate::foundation::__ns_string::CFConstString::new_ascii(
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
&ASCII,
)
} else {
$crate::foundation::__ns_string::CFConstString::new_utf16(
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
&UTF16,
)
}
};
};
}
#[doc(hidden)]
#[cfg(not(feature = "apple"))]
#[macro_export]
macro_rules! __ns_string_inner {
($inp:ident) => {{
use $crate::foundation::__ns_string::CachedNSString;
static CACHED_NSSTRING: CachedNSString = CachedNSString::new();
CACHED_NSSTRING.get($inp)
}};
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn test_is_ascii() {
assert!(is_ascii_no_nul(b"a"));
assert!(is_ascii_no_nul(b"abc"));
assert!(!is_ascii_no_nul(b"\xff"));
assert!(!is_ascii_no_nul(b"\0"));
assert!(!is_ascii_no_nul(b"a\0b"));
assert!(!is_ascii_no_nul(b"ab\0"));
assert!(!is_ascii_no_nul(b"a\0b\0"));
}
#[test]
fn test_decode_utf8() {
for c in '\u{0}'..=core::char::MAX {
let mut buf;
for off in 0..4 {
// Ensure we see garbage if we read outside bounds.
buf = [0xff; 8];
let len = c.encode_utf8(&mut buf[off..(off + 4)]).len();
let (end_idx, decoded) = decode_utf8(&buf, off);
assert_eq!(
(end_idx, decoded),
(off + len, c as u32),
"failed for U+{code:04X} ({ch:?}) encoded as {buf:#x?} over {range:?}",
code = c as u32,
ch = c,
buf = &buf[off..(off + len)],
range = off..(off + len),
);
}
}
}
#[test]
fn encode_utf16() {
for c in '\u{0}'..=core::char::MAX {
assert_eq!(
c.encode_utf16(&mut [0u16; 2]),
Utf16Char::encode(c as u32).as_slice(),
"failed for U+{:04X} ({:?})",
c as u32,
c
);
}
}
#[test]
fn ns_string() {
macro_rules! test {
($($s:expr,)+) => {$({
let s1 = ns_string!($s);
let s2 = NSString::from_str($s);
assert_eq!(s1, s1);
assert_eq!(s1, &*s2);
assert_eq!(s1.to_string(), $s);
assert_eq!(s2.to_string(), $s);
})+};
}
test! {
"",
"asdf",
"🦀",
"🏳️‍🌈",
"𝄞music",
"abcd【e】fg",
"abcd⒠fg",
"ääääh",
"lööps, bröther?",
"\u{fffd} \u{fffd} \u{fffd}",
"讓每個人都能打造出。",
"\0",
"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09",
// "\u{feff}", // TODO
include_str!("__ns_string.rs"),
}
}
#[test]
fn ns_string_in_unsafe() {
// Test that the `unused_unsafe` lint doesn't trigger
let s = unsafe {
let s: *const NSString = ns_string!("abc");
&*s
};
assert_eq!(s.to_string(), "abc");
}
}

View file

@ -0,0 +1,506 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut, Range};
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableArray, NSMutableCopying,
NSObject, NSRange,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::runtime::{Class, Object};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
/// An immutable ordered collection of objects.
///
/// This is the Objective-C equivalent of a "boxed slice" (`Box<[T]>`),
/// so effectively a `Vec<T>` where you can't change the number of
/// elements.
///
/// The type of the contained objects is described by the generic
/// parameter `T`, and the ownership of the objects is described with the
/// generic parameter `O`.
///
///
/// # Ownership
///
/// While `NSArray` _itself_ is immutable, i.e. the number of objects it
/// contains can't change, it is still possible to modify the contained
/// objects themselves, if you know you're the sole owner of them -
/// quite similar to how you can modify elements in `Box<[T]>`.
///
/// To mutate the contained objects the ownership must be `O = Owned`. A
/// summary of what the different "types" of arrays allow you to do can be
/// found below. `Array` refers to either `NSArray` or `NSMutableArray`.
/// - `Id<NSMutableArray<T, Owned>, Owned>`: Allows you to mutate the
/// objects, and the array itself.
/// - `Id<NSMutableArray<T, Shared>, Owned>`: Allows you to mutate the
/// array itself, but not it's contents.
/// - `Id<NSArray<T, Owned>, Owned>`: Allows you to mutate the objects,
/// but not the array itself.
/// - `Id<NSArray<T, Shared>, Owned>`: Effectively the same as the below.
/// - `Id<Array<T, Shared>, Shared>`: Allows you to copy the array, but
/// does not allow you to modify it in any way.
/// - `Id<Array<T, Owned>, Shared>`: Pretty useless compared to the
/// others, avoid this.
///
/// See [Apple's documentation][apple-doc].
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsarray?language=objc
// `T: PartialEq` bound correct because `NSArray` does deep (instead of
// shallow) equality comparisons.
#[derive(PartialEq, Eq, Hash)]
pub struct NSArray<T: Message, O: Ownership = Shared> {
item: PhantomData<Id<T, O>>,
notunwindsafe: PhantomData<&'static mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSArray<T, O> {
type Super = NSObject;
}
);
// SAFETY: Same as Id<T, O> (which is what NSArray effectively stores).
unsafe impl<T: Message + Sync + Send> Sync for NSArray<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSArray<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSArray<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSArray<T, Owned> {}
// Also same as Id<T, O>
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSArray<T, O> {}
impl<T: Message + RefUnwindSafe> UnwindSafe for NSArray<T, Shared> {}
impl<T: Message + UnwindSafe> UnwindSafe for NSArray<T, Owned> {}
#[track_caller]
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
cls: &Class,
objects: &[&T],
) -> Id<R, O> {
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithObjects: objects.as_ptr(),
count: objects.len(),
]
}
}
extern_methods!(
/// Generic creation methods.
unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
/// Get an empty array.
pub fn new() -> Id<Self, Shared> {
// SAFETY:
// - `new` may not create a new object, but instead return a shared
// instance. We remedy this by returning `Id<Self, Shared>`.
// - `O` don't actually matter here! E.g. `NSArray<T, Owned>` is
// perfectly legal, since the array doesn't have any elements, and
// hence the notion of ownership over the elements is void.
unsafe { msg_send_id![Self::class(), new] }
}
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
// SAFETY:
// `initWithObjects:` may choose to deduplicate arrays (I could
// imagine it having a special case for arrays with one `NSNumber`
// object), and returning mutable references to those would be
// unsound!
// However, when we know that we have ownership over the variables, we
// also know that there cannot be another array in existence with the
// same objects, so `Id<NSArray<T, Owned>, Owned>` is safe to return.
//
// In essence, we can choose between always returning `Id<T, Shared>`
// or `Id<T, O>`, and the latter is probably the most useful, as we
// would like to know when we're the only owner of the array, to
// allow mutation of the array's items.
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
}
}
/// Creation methods that produce shared arrays.
unsafe impl<T: Message> NSArray<T, Shared> {
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
// SAFETY: Taking `&T` would not be sound, since the `&T` could come
// from an `Id<T, Owned>` that would now no longer be owned!
//
// (Note that NSArray internally retains all the objects it is given,
// effectively making the safety requirements the same as
// `Id::retain`).
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
}
/// Generic accessor methods.
unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
#[doc(alias = "count")]
#[sel(count)]
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[sel(objectAtIndex:)]
unsafe fn get_unchecked(&self, index: usize) -> &T;
#[doc(alias = "objectAtIndex:")]
pub fn get(&self, index: usize) -> Option<&T> {
// TODO: Replace this check with catching the thrown NSRangeException
if index < self.len() {
// SAFETY: The index is checked to be in bounds.
Some(unsafe { self.get_unchecked(index) })
} else {
None
}
}
#[doc(alias = "firstObject")]
#[sel(firstObject)]
pub fn first(&self) -> Option<&T>;
#[doc(alias = "lastObject")]
#[sel(lastObject)]
pub fn last(&self) -> Option<&T>;
#[doc(alias = "objectEnumerator")]
pub fn iter(&self) -> NSEnumerator<'_, T> {
unsafe {
let result: *mut Object = msg_send![self, objectEnumerator];
NSEnumerator::from_ptr(result)
}
}
#[sel(getObjects:range:)]
unsafe fn get_objects(&self, ptr: *mut &T, range: NSRange);
pub fn objects_in_range(&self, range: Range<usize>) -> Vec<&T> {
let range = NSRange::from(range);
let mut vec = Vec::with_capacity(range.length);
unsafe {
self.get_objects(vec.as_mut_ptr(), range);
vec.set_len(range.length);
}
vec
}
pub fn to_vec(&self) -> Vec<&T> {
self.objects_in_range(0..self.len())
}
// TODO: Take Id<Self, Self::ItemOwnership> ?
pub fn into_vec(array: Id<Self, Owned>) -> Vec<Id<T, O>> {
array
.to_vec()
.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
/// Accessor methods that work on shared arrays.
unsafe impl<T: Message> NSArray<T, Shared> {
#[doc(alias = "objectAtIndex:")]
pub fn get_retained(&self, index: usize) -> Id<T, Shared> {
let obj = self.get(index).unwrap();
// SAFETY: The object is originally shared (see `where` bound).
unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
}
pub fn to_shared_vec(&self) -> Vec<Id<T, Shared>> {
self.to_vec()
.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
/// Accessor methods that work on owned arrays.
unsafe impl<T: Message> NSArray<T, Owned> {
#[doc(alias = "objectAtIndex:")]
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
// TODO: Replace this check with catching the thrown NSRangeException
if index < self.len() {
// SAFETY: The index is checked to be in bounds.
Some(unsafe { msg_send![self, objectAtIndex: index] })
} else {
None
}
}
#[doc(alias = "firstObject")]
#[sel(firstObject)]
pub fn first_mut(&mut self) -> Option<&mut T>;
#[doc(alias = "lastObject")]
#[sel(lastObject)]
pub fn last_mut(&mut self) -> Option<&mut T>;
}
);
/// This is implemented as a shallow copy.
///
/// As such, it is only possible when the array's contents are `Shared`.
unsafe impl<T: Message> NSCopying for NSArray<T, Shared> {
type Ownership = Shared;
type Output = NSArray<T, Shared>;
}
/// This is implemented as a shallow copy.
unsafe impl<T: Message> NSMutableCopying for NSArray<T, Shared> {
type Output = NSMutableArray<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSArray<T, Shared> {
type Owned = Id<NSArray<T, Shared>, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSArray<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSArray<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSArray<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message, O: Ownership> Index<usize> for NSArray<T, O> {
type Output = T;
fn index(&self, index: usize) -> &T {
self.get(index).unwrap()
}
}
impl<T: Message> IndexMut<usize> for NSArray<T, Owned> {
fn index_mut(&mut self, index: usize) -> &mut T {
self.get_mut(index).unwrap()
}
}
impl<T: Message, O: Ownership> DefaultId for NSArray<T, O> {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSArray<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter_fast()).finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec::Vec;
use super::*;
use crate::foundation::{NSNumber, NSString};
use crate::rc::{RcTestObject, ThreadTestData};
fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
let mut vec = Vec::with_capacity(len);
for _ in 0..len {
vec.push(NSObject::new());
}
NSArray::from_vec(vec)
}
fn sample_number_array(len: u8) -> Id<NSArray<NSNumber, Shared>, Shared> {
let mut vec = Vec::with_capacity(len as usize);
for i in 0..len {
vec.push(NSNumber::new_u8(i));
}
NSArray::from_vec(vec)
}
#[test]
fn test_two_empty() {
let _empty_array1 = NSArray::<NSObject>::new();
let _empty_array2 = NSArray::<NSObject>::new();
}
#[test]
fn test_len() {
let empty_array = NSArray::<NSObject>::new();
assert_eq!(empty_array.len(), 0);
let array = sample_array(4);
assert_eq!(array.len(), 4);
}
#[test]
fn test_equality() {
let array1 = sample_array(3);
let array2 = sample_array(3);
assert_ne!(array1, array2);
let array1 = sample_number_array(3);
let array2 = sample_number_array(3);
assert_eq!(array1, array2);
let array1 = sample_number_array(3);
let array2 = sample_number_array(4);
assert_ne!(array1, array2);
}
#[test]
fn test_debug() {
let obj = sample_number_array(0);
assert_eq!(format!("{:?}", obj), "[]");
let obj = sample_number_array(3);
assert_eq!(format!("{:?}", obj), "[0, 1, 2]");
}
#[test]
fn test_get() {
let array = sample_array(4);
assert_ne!(array.get(0), array.get(3));
assert_eq!(array.first(), array.get(0));
assert_eq!(array.last(), array.get(3));
let empty_array = <NSArray<NSObject>>::new();
assert!(empty_array.first().is_none());
assert!(empty_array.last().is_none());
}
#[test]
fn test_retains_stored() {
let obj = Id::into_shared(RcTestObject::new());
let mut expected = ThreadTestData::current();
let input = [obj.clone(), obj.clone()];
expected.retain += 2;
expected.assert_current();
let array = NSArray::from_slice(&input);
expected.retain += 2;
expected.assert_current();
let _obj = array.first().unwrap();
expected.assert_current();
drop(array);
expected.release += 2;
expected.assert_current();
let array = NSArray::from_vec(Vec::from(input));
expected.retain += 2;
expected.release += 2;
expected.assert_current();
let _obj = array.get(0).unwrap();
let _obj = array.get(1).unwrap();
assert!(array.get(2).is_none());
expected.assert_current();
drop(array);
expected.release += 2;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_nscopying_uses_retain() {
let obj = Id::into_shared(RcTestObject::new());
let array = NSArray::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let _copy = array.copy();
expected.assert_current();
let _copy = array.mutable_copy();
expected.retain += 1;
expected.assert_current();
}
#[test]
#[cfg_attr(
feature = "apple",
ignore = "this works differently on different framework versions"
)]
fn test_iter_no_retain() {
let obj = Id::into_shared(RcTestObject::new());
let array = NSArray::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let iter = array.iter();
expected.retain += 0;
expected.assert_current();
assert_eq!(iter.count(), 1);
expected.autorelease += 0;
expected.assert_current();
let iter = array.iter_fast();
assert_eq!(iter.count(), 1);
expected.assert_current();
}
#[test]
fn test_iter() {
let array = sample_array(4);
assert_eq!(array.iter().count(), 4);
assert!(array
.iter()
.enumerate()
.all(|(i, obj)| Some(obj) == array.get(i)));
}
#[test]
fn test_objects_in_range() {
let array = sample_array(4);
let middle_objs = array.objects_in_range(1..3);
assert_eq!(middle_objs.len(), 2);
assert_eq!(middle_objs[0], array.get(1).unwrap());
assert_eq!(middle_objs[1], array.get(2).unwrap());
let empty_objs = array.objects_in_range(1..1);
assert!(empty_objs.is_empty());
let all_objs = array.objects_in_range(0..4);
assert_eq!(all_objs.len(), 4);
}
#[test]
fn test_into_vec() {
let array = sample_array(4);
let vec = NSArray::into_vec(array);
assert_eq!(vec.len(), 4);
}
#[test]
fn test_generic_ownership_traits() {
fn assert_partialeq<T: PartialEq>() {}
assert_partialeq::<NSArray<NSString, Shared>>();
assert_partialeq::<NSArray<NSString, Owned>>();
fn test_ownership_implies_partialeq<O: Ownership>() {
assert_partialeq::<NSArray<NSString, O>>();
}
test_ownership_implies_partialeq::<Shared>();
test_ownership_implies_partialeq::<Owned>();
}
}

View file

@ -0,0 +1,212 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
NSCopying, NSDictionary, NSMutableAttributedString, NSMutableCopying, NSObject, NSString,
};
use crate::rc::{DefaultId, Id, Shared};
use crate::runtime::Object;
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A string that has associated attributes for portions of its text.
///
/// Examples of attributes could be: Visual style, hyperlinks, or
/// accessibility data.
///
/// Conceptually, each UTF-16 code unit in an attributed string has its
/// own collection of attributes - most often though
///
/// Only the most basic functionality is defined here, the `AppKit`
/// framework contains most of the extension methods.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsattributedstring?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSAttributedString;
unsafe impl ClassType for NSAttributedString {
type Super = NSObject;
}
);
// SAFETY: `NSAttributedString` is immutable and `NSMutableAttributedString`
// can only be mutated from `&mut` methods.
unsafe impl Sync for NSAttributedString {}
unsafe impl Send for NSAttributedString {}
// Same reasoning as `NSString`.
impl UnwindSafe for NSAttributedString {}
impl RefUnwindSafe for NSAttributedString {}
/// Attributes that you can apply to text in an attributed string.
pub type NSAttributedStringKey = NSString;
extern_methods!(
/// Creating attributed strings.
unsafe impl NSAttributedString {
/// Construct an empty attributed string.
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates a new attributed string from the given string and attributes.
///
/// The attributes are associated with every UTF-16 code unit in the
/// string.
#[doc(alias = "initWithString:")]
pub fn new_with_attributes(
string: &NSString,
// TODO: Mutability of the dictionary should be (Shared, Shared)
attributes: &NSDictionary<NSAttributedStringKey, Object>,
) -> Id<Self, Shared> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string, attributes: attributes]
}
}
/// Creates a new attributed string without any attributes.
#[doc(alias = "initWithString:")]
pub fn from_nsstring(string: &NSString) -> Id<Self, Shared> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string]
}
}
}
/// Querying.
unsafe impl NSAttributedString {
// TODO: Lifetimes?
pub fn string(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, string] }
}
/// Alias for `self.string().len_utf16()`.
#[doc(alias = "length")]
#[sel(length)]
pub fn len_utf16(&self) -> usize;
// /// TODO
// ///
// /// See [Apple's documentation on Effective and Maximal Ranges][doc].
// ///
// /// [doc]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/AccessingAttrs.html#//apple_ref/doc/uid/20000161-SW2
// #[doc(alias = "attributesAtIndex:effectiveRange:")]
// pub fn attributes_in_effective_range(
// &self,
// index: usize,
// range: Range<usize>,
// ) -> Id<Self, Shared> {
// let range = NSRange::from(range);
// todo!()
// }
//
// attributesAtIndex:longestEffectiveRange:inRange:
// TODO: attributedSubstringFromRange:
// TODO: enumerateAttributesInRange:options:usingBlock:
}
);
impl DefaultId for NSAttributedString {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSAttributedString {
type Ownership = Shared;
type Output = NSAttributedString;
}
unsafe impl NSMutableCopying for NSAttributedString {
type Output = NSMutableAttributedString;
}
impl alloc::borrow::ToOwned for NSAttributedString {
type Owned = Id<NSAttributedString, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Debug for NSAttributedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Use -[NSAttributedString description] since it is pretty good
let obj: &NSObject = self;
fmt::Debug::fmt(obj, f)
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::{format, vec};
use super::*;
use crate::rc::{autoreleasepool, Owned};
#[test]
fn test_new() {
let s = NSAttributedString::new();
assert_eq!(&s.string().to_string(), "");
}
#[test]
fn test_string_bound_to_attributed() {
let attr_s = {
let source = NSString::from_str("Hello world!");
NSAttributedString::from_nsstring(&source)
};
let s = autoreleasepool(|_| attr_s.string());
assert_eq!(s.len(), 12);
}
#[test]
fn test_from_nsstring() {
let s = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
assert_eq!(&s.string().to_string(), "abc");
}
#[test]
fn test_copy() {
let s1 = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
let s2 = s1.copy();
// NSAttributedString performs this optimization in GNUStep's runtime,
// but not in Apple's; so we don't test for it!
// assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
assert!(s2.is_kind_of::<NSAttributedString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
assert!(s3.is_kind_of::<NSMutableAttributedString>());
}
#[test]
fn test_debug() {
let s = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
let expected = if cfg!(feature = "gnustep-1-7") {
"abc{}"
} else {
"abc{\n}"
};
assert_eq!(format!("{:?}", s), expected);
let obj: Id<Object, Owned> = unsafe { Id::cast(NSObject::new()) };
let ptr: *const Object = &*obj;
let s = NSAttributedString::new_with_attributes(
&NSString::from_str("abc"),
&NSDictionary::from_keys_and_objects(&[&*NSString::from_str("test")], vec![obj]),
);
let expected = if cfg!(feature = "gnustep-1-7") {
format!("abc{{test = \"<NSObject: {:?}>\"; }}", ptr)
} else {
format!("abc{{\n test = \"<NSObject: {:?}>\";\n}}", ptr)
};
assert_eq!(format!("{:?}", s), expected);
}
}

View file

@ -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<Self, Shared> {
unsafe { msg_send_id![Self::class(), mainBundle] }
}
pub fn info(&self) -> Id<NSDictionary<NSString, NSObject>, Shared> {
unsafe { msg_send_id![self, infoDictionary] }
}
pub fn name(&self) -> Option<Id<NSString, Shared>> {
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);
}
}

View file

@ -0,0 +1,54 @@
use core::cmp::Ordering;
use crate::{Encode, Encoding, RefEncode};
/// Constants that indicate sort order.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nscomparisonresult?language=objc).
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NSComparisonResult {
/// The left operand is smaller than the right operand.
Ascending = -1,
/// The two operands are equal.
Same = 0,
/// The left operand is greater than the right operand.
Descending = 1,
}
impl Default for NSComparisonResult {
#[inline]
fn default() -> Self {
Self::Same
}
}
unsafe impl Encode for NSComparisonResult {
const ENCODING: Encoding = isize::ENCODING;
}
unsafe impl RefEncode for NSComparisonResult {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl From<Ordering> for NSComparisonResult {
#[inline]
fn from(order: Ordering) -> Self {
match order {
Ordering::Less => Self::Ascending,
Ordering::Equal => Self::Same,
Ordering::Greater => Self::Descending,
}
}
}
impl From<NSComparisonResult> for Ordering {
#[inline]
fn from(comparison_result: NSComparisonResult) -> Self {
match comparison_result {
NSComparisonResult::Ascending => Self::Less,
NSComparisonResult::Same => Self::Equal,
NSComparisonResult::Descending => Self::Greater,
}
}
}

View file

@ -0,0 +1,48 @@
use crate::rc::{Id, Owned, Ownership};
use crate::{msg_send_id, Message};
pub unsafe trait NSCopying: Message {
/// Indicates whether the type is mutable or immutable.
///
/// This can be [`Owned`] if and only if `copy` creates a new instance,
/// see the following example:
///
/// ```ignore
/// let x: Id<MyObject, _> = MyObject::new();
/// // This is valid only if `y` is a new instance. Otherwise `x` and `y`
/// // would be able to create aliasing mutable references!
/// let y: Id<MyObject, Owned> = x.copy();
/// ```
///
/// Note that for the same reason, you should be careful when defining
/// `new` methods on your object; e.g. immutable types like [`NSString`]
/// don't return `Id<NSString, Owned>`, because that would allow this
/// trait to create an aliasing `Id<NSString, Shared>` (since sending the
/// `copy` message (and others) does not create a new instance, but
/// instead just retains the instance).
///
/// [`NSString`]: crate::foundation::NSString
type Ownership: Ownership;
/// The output type.
///
/// This is usually `Self`, but e.g. `NSMutableString` returns `NSString`.
/// TODO: Verify???
type Output: Message;
fn copy(&self) -> Id<Self::Output, Self::Ownership> {
unsafe { msg_send_id![self, copy] }
}
}
/// TODO
///
/// Note that the `mutableCopy` selector must return an owned object!
pub unsafe trait NSMutableCopying: Message {
/// TODO
type Output: Message;
fn mutable_copy(&self) -> Id<Self::Output, Owned> {
unsafe { msg_send_id![self, mutableCopy] }
}
}

View file

@ -0,0 +1,228 @@
#[cfg(feature = "block")]
use alloc::vec::Vec;
use core::ffi::c_void;
use core::fmt;
use core::ops::Index;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::slice::{self, SliceIndex};
use super::{NSCopying, NSMutableCopying, NSMutableData, NSObject};
use crate::rc::{DefaultId, Id, Shared};
use crate::runtime::{Class, Object};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A static byte buffer in memory.
///
/// This is similar to a [`slice`][`prim@slice`] of [`u8`].
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsdata?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSData;
unsafe impl ClassType for NSData {
type Super = NSObject;
}
);
// SAFETY: `NSData` is immutable and `NSMutableData` can only be mutated from
// `&mut` methods.
unsafe impl Sync for NSData {}
unsafe impl Send for NSData {}
impl UnwindSafe for NSData {}
impl RefUnwindSafe for NSData {}
extern_methods!(
/// Creation methods.
unsafe impl NSData {
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn with_bytes(bytes: &[u8]) -> Id<Self, Shared> {
unsafe { Id::cast(with_slice(Self::class(), bytes)) }
}
#[cfg(feature = "block")]
pub fn from_vec(bytes: Vec<u8>) -> Id<Self, Shared> {
// GNUStep's NSData `initWithBytesNoCopy:length:deallocator:` has a
// bug; it forgets to assign the input buffer and length to the
// instance before it swizzles to NSDataWithDeallocatorBlock.
// See https://github.com/gnustep/libs-base/pull/213
// So we just use NSDataWithDeallocatorBlock directly.
//
// NSMutableData does not have this problem.
#[cfg(feature = "gnustep-1-7")]
let cls = crate::class!(NSDataWithDeallocatorBlock);
#[cfg(not(feature = "gnustep-1-7"))]
let cls = Self::class();
unsafe { Id::cast(with_vec(cls, bytes)) }
}
}
/// Accessor methods.
unsafe impl NSData {
#[sel(length)]
#[doc(alias = "length")]
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[sel(bytes)]
fn bytes_raw(&self) -> *const c_void;
pub fn bytes(&self) -> &[u8] {
let ptr = self.bytes_raw();
let ptr: *const u8 = ptr.cast();
// The bytes pointer may be null for length zero
if ptr.is_null() {
&[]
} else {
unsafe { slice::from_raw_parts(ptr, self.len()) }
}
}
}
);
unsafe impl NSCopying for NSData {
type Ownership = Shared;
type Output = NSData;
}
unsafe impl NSMutableCopying for NSData {
type Output = NSMutableData;
}
impl alloc::borrow::ToOwned for NSData {
type Owned = Id<NSData, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl AsRef<[u8]> for NSData {
fn as_ref(&self) -> &[u8] {
self.bytes()
}
}
// Note: We don't implement `Borrow<[u8]>` since we can't guarantee that `Eq`,
// `Ord` and `Hash` are equal for `NSData` vs. `[u8]`!
impl<I: SliceIndex<[u8]>> Index<I> for NSData {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
// Replaces the need for getBytes:range:
Index::index(self.bytes(), index)
}
}
impl DefaultId for NSData {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl fmt::Debug for NSData {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// -[NSData description] is quite unreadable
fmt::Debug::fmt(self.bytes(), f)
}
}
impl<'a> IntoIterator for &'a NSData {
type Item = &'a u8;
type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.bytes().iter()
}
}
pub(crate) unsafe fn with_slice(cls: &Class, bytes: &[u8]) -> Id<Object, Shared> {
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithBytes: bytes_ptr,
length: bytes.len(),
]
}
}
#[cfg(feature = "block")]
pub(crate) unsafe fn with_vec(cls: &Class, bytes: Vec<u8>) -> Id<Object, Shared> {
use core::mem::ManuallyDrop;
use block2::{Block, ConcreteBlock};
let capacity = bytes.capacity();
let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe {
// Recreate the Vec and let it drop
let _ = Vec::<u8>::from_raw_parts(bytes.cast(), len, capacity);
});
let dealloc = dealloc.copy();
let dealloc: &Block<(*mut c_void, usize), ()> = &dealloc;
let mut bytes = ManuallyDrop::new(bytes);
let bytes_ptr: *mut c_void = bytes.as_mut_ptr().cast();
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithBytesNoCopy: bytes_ptr,
length: bytes.len(),
deallocator: dealloc,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[cfg(feature = "block")]
use alloc::vec;
#[test]
fn test_bytes() {
let bytes = [3, 7, 16, 52, 112, 19];
let data = NSData::with_bytes(&bytes);
assert_eq!(data.len(), bytes.len());
assert_eq!(data.bytes(), bytes);
}
#[test]
fn test_no_bytes() {
let data = NSData::new();
assert!(Some(data.bytes()).is_some());
}
#[cfg(feature = "block")]
#[test]
fn test_from_vec() {
let bytes = vec![3, 7, 16];
let bytes_ptr = bytes.as_ptr();
let data = NSData::from_vec(bytes);
assert_eq!(data.bytes().as_ptr(), bytes_ptr);
}
#[test]
fn test_debug() {
let bytes = [3, 7, 16, 52, 112, 19];
let data = NSData::with_bytes(&bytes);
assert_eq!(format!("{:?}", data), "[3, 7, 16, 52, 112, 19]");
}
}

View file

@ -0,0 +1,264 @@
use alloc::vec::Vec;
use core::cmp::min;
use core::fmt;
use core::marker::PhantomData;
use core::ops::Index;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr;
use super::{NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSObject};
use crate::rc::{DefaultId, Id, Owned, Shared, SliceId};
use crate::{ClassType, __inner_extern_class, extern_methods, msg_send, msg_send_id, Message};
__inner_extern_class!(
#[derive(PartialEq, Eq, Hash)]
pub struct NSDictionary<K: Message, V: Message> {
key: PhantomData<Id<K, Shared>>,
obj: PhantomData<Id<V, Owned>>,
}
unsafe impl<K: Message, V: Message> ClassType for NSDictionary<K, V> {
type Super = NSObject;
}
);
// TODO: SAFETY
// Approximately same as `NSArray<T, Shared>`
unsafe impl<K: Message + Sync + Send, V: Message + Sync> Sync for NSDictionary<K, V> {}
unsafe impl<K: Message + Sync + Send, V: Message + Send> Send for NSDictionary<K, V> {}
// Approximately same as `NSArray<T, Shared>`
impl<K: Message + UnwindSafe, V: Message + UnwindSafe> UnwindSafe for NSDictionary<K, V> {}
impl<K: Message + RefUnwindSafe, V: Message + RefUnwindSafe> RefUnwindSafe for NSDictionary<K, V> {}
extern_methods!(
unsafe impl<K: Message, V: Message> NSDictionary<K, V> {
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
#[doc(alias = "count")]
#[sel(count)]
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "objectForKey:")]
#[sel(objectForKey:)]
pub fn get(&self, key: &K) -> Option<&V>;
#[sel(getObjects:andKeys:)]
unsafe fn get_objects_and_keys(&self, objects: *mut &V, keys: *mut &K);
#[doc(alias = "getObjects:andKeys:")]
pub fn keys(&self) -> Vec<&K> {
let len = self.len();
let mut keys = Vec::with_capacity(len);
unsafe {
self.get_objects_and_keys(ptr::null_mut(), keys.as_mut_ptr());
keys.set_len(len);
}
keys
}
#[doc(alias = "getObjects:andKeys:")]
pub fn values(&self) -> Vec<&V> {
let len = self.len();
let mut vals = Vec::with_capacity(len);
unsafe {
self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut());
vals.set_len(len);
}
vals
}
#[doc(alias = "getObjects:andKeys:")]
pub fn keys_and_objects(&self) -> (Vec<&K>, Vec<&V>) {
let len = self.len();
let mut keys = Vec::with_capacity(len);
let mut objs = Vec::with_capacity(len);
unsafe {
self.get_objects_and_keys(objs.as_mut_ptr(), keys.as_mut_ptr());
keys.set_len(len);
objs.set_len(len);
}
(keys, objs)
}
#[doc(alias = "keyEnumerator")]
pub fn iter_keys(&self) -> NSEnumerator<'_, K> {
unsafe {
let result = msg_send![self, keyEnumerator];
NSEnumerator::from_ptr(result)
}
}
#[doc(alias = "objectEnumerator")]
pub fn iter_values(&self) -> NSEnumerator<'_, V> {
unsafe {
let result = msg_send![self, objectEnumerator];
NSEnumerator::from_ptr(result)
}
}
pub fn keys_array(&self) -> Id<NSArray<K, Shared>, Shared> {
unsafe { msg_send_id![self, allKeys] }
}
pub fn from_keys_and_objects<T>(keys: &[&T], vals: Vec<Id<V, Owned>>) -> Id<Self, Shared>
where
T: NSCopying<Output = K>,
{
let vals = vals.as_slice_ref();
let cls = Self::class();
let count = min(keys.len(), vals.len());
let obj = unsafe { msg_send_id![cls, alloc] };
unsafe {
msg_send_id![
obj,
initWithObjects: vals.as_ptr(),
forKeys: keys.as_ptr(),
count: count,
]
}
}
pub fn into_values_array(dict: Id<Self, Owned>) -> Id<NSArray<V, Owned>, Shared> {
unsafe { msg_send_id![&dict, allValues] }
}
}
);
impl<K: Message, V: Message> DefaultId for NSDictionary<K, V> {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl<K: Message, V: Message> NSFastEnumeration for NSDictionary<K, V> {
type Item = K;
}
impl<'a, K: Message, V: Message> Index<&'a K> for NSDictionary<K, V> {
type Output = V;
fn index<'s>(&'s self, index: &'a K) -> &'s V {
self.get(index).unwrap()
}
}
impl<K: fmt::Debug + Message, V: fmt::Debug + Message> fmt::Debug for NSDictionary<K, V> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let iter = self.iter_keys().zip(self.iter_values());
f.debug_map().entries(iter).finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec;
use super::*;
use crate::foundation::NSString;
use crate::rc::autoreleasepool;
fn sample_dict(key: &str) -> Id<NSDictionary<NSString, NSObject>, Shared> {
let string = NSString::from_str(key);
let obj = NSObject::new();
NSDictionary::from_keys_and_objects(&[&*string], vec![obj])
}
#[test]
fn test_len() {
let dict = sample_dict("abcd");
assert_eq!(dict.len(), 1);
}
#[test]
fn test_get() {
let dict = sample_dict("abcd");
let string = NSString::from_str("abcd");
assert!(dict.get(&string).is_some());
let string = NSString::from_str("abcde");
assert!(dict.get(&string).is_none());
}
#[test]
fn test_keys() {
let dict = sample_dict("abcd");
let keys = dict.keys();
assert_eq!(keys.len(), 1);
autoreleasepool(|pool| {
assert_eq!(keys[0].as_str(pool), "abcd");
});
}
#[test]
fn test_values() {
let dict = sample_dict("abcd");
let vals = dict.values();
assert_eq!(vals.len(), 1);
}
#[test]
fn test_keys_and_objects() {
let dict = sample_dict("abcd");
let (keys, objs) = dict.keys_and_objects();
assert_eq!(keys.len(), 1);
assert_eq!(objs.len(), 1);
autoreleasepool(|pool| {
assert_eq!(keys[0].as_str(pool), "abcd");
});
assert_eq!(objs[0], dict.get(keys[0]).unwrap());
}
#[test]
fn test_iter_keys() {
let dict = sample_dict("abcd");
assert_eq!(dict.iter_keys().count(), 1);
autoreleasepool(|pool| {
assert_eq!(dict.iter_keys().next().unwrap().as_str(pool), "abcd");
});
}
#[test]
fn test_iter_values() {
let dict = sample_dict("abcd");
assert_eq!(dict.iter_values().count(), 1);
}
#[test]
fn test_arrays() {
let dict = sample_dict("abcd");
let keys = dict.keys_array();
assert_eq!(keys.len(), 1);
autoreleasepool(|pool| {
assert_eq!(keys[0].as_str(pool), "abcd");
});
// let objs = NSDictionary::into_values_array(dict);
// assert_eq!(objs.len(), 1);
}
#[test]
fn test_debug() {
let key = NSString::from_str("a");
// TODO: Fix this
let val = unsafe { Id::from_shared(NSString::from_str("b")) };
let dict = NSDictionary::from_keys_and_objects(&[&*key], vec![val]);
assert_eq!(format!("{:?}", dict), r#"{"a": "b"}"#);
}
}

View file

@ -0,0 +1,196 @@
use core::marker::PhantomData;
use core::mem;
use core::ptr;
use core::slice;
use std::os::raw::c_ulong;
use crate::rc::{Id, Owned};
use crate::runtime::Object;
use crate::{msg_send, Encode, Encoding, Message, RefEncode};
// TODO: https://doc.rust-lang.org/stable/reference/trait-bounds.html#lifetime-bounds
pub struct NSEnumerator<'a, T: Message> {
id: Id<Object, Owned>,
item: PhantomData<&'a T>,
}
impl<'a, T: Message> NSEnumerator<'a, T> {
/// TODO
///
/// # Safety
///
/// The object pointer must be a valid `NSEnumerator` with `Owned`
/// ownership.
pub unsafe fn from_ptr(ptr: *mut Object) -> Self {
Self {
id: unsafe { Id::retain_autoreleased(ptr) }.unwrap(),
item: PhantomData,
}
}
}
impl<'a, T: Message> Iterator for NSEnumerator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> {
unsafe { msg_send![&mut self.id, nextObject] }
}
}
pub unsafe trait NSFastEnumeration: Message {
type Item: Message;
fn iter_fast(&self) -> NSFastEnumerator<'_, Self> {
NSFastEnumerator::new(self)
}
}
#[repr(C)]
struct NSFastEnumerationState<T: Message> {
state: c_ulong, // TODO: Verify this is actually always 64 bit
items_ptr: *const *const T,
mutations_ptr: *mut c_ulong,
extra: [c_ulong; 5],
}
unsafe impl<T: Message> Encode for NSFastEnumerationState<T> {
const ENCODING: Encoding = Encoding::Struct(
"?",
&[
Encoding::C_ULONG,
Encoding::Pointer(&Encoding::Object), // <*const *const T>::ENCODING
Encoding::Pointer(&Encoding::C_ULONG),
Encoding::Array(5, &Encoding::C_ULONG),
],
);
}
unsafe impl<T: Message> RefEncode for NSFastEnumerationState<T> {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
fn enumerate<'a, 'b: 'a, C: NSFastEnumeration + ?Sized>(
object: &'b C,
state: &mut NSFastEnumerationState<C::Item>,
buf: &'a mut [*const C::Item],
) -> Option<&'a [*const C::Item]> {
let count: usize = unsafe {
// Reborrow state so that we don't move it
let state = &mut *state;
msg_send![
object,
countByEnumeratingWithState: state,
objects: buf.as_mut_ptr(),
count: buf.len(),
]
};
if count > 0 {
unsafe { Some(slice::from_raw_parts(state.items_ptr, count)) }
} else {
None
}
}
const FAST_ENUM_BUF_SIZE: usize = 16;
pub struct NSFastEnumerator<'a, C: 'a + NSFastEnumeration + ?Sized> {
object: &'a C,
ptr: *const *const C::Item,
end: *const *const C::Item,
state: NSFastEnumerationState<C::Item>,
buf: [*const C::Item; FAST_ENUM_BUF_SIZE],
}
impl<'a, C: NSFastEnumeration + ?Sized> NSFastEnumerator<'a, C> {
fn new(object: &'a C) -> Self {
Self {
object,
ptr: ptr::null(),
end: ptr::null(),
state: unsafe { mem::zeroed() },
buf: [ptr::null(); FAST_ENUM_BUF_SIZE],
}
}
fn update_buf(&mut self) -> bool {
// If this isn't our first time enumerating, record the previous value
// from the mutations pointer.
let mutations = if !self.ptr.is_null() {
Some(unsafe { *self.state.mutations_ptr })
} else {
None
};
let next_buf = enumerate(self.object, &mut self.state, &mut self.buf);
if let Some(buf) = next_buf {
// Check if the collection was mutated
if let Some(mutations) = mutations {
assert_eq!(
mutations,
unsafe { *self.state.mutations_ptr },
"Mutation detected during enumeration of object {:p}",
self.object
);
}
self.ptr = buf.as_ptr();
self.end = unsafe { self.ptr.add(buf.len()) };
true
} else {
self.ptr = ptr::null();
self.end = ptr::null();
false
}
}
}
impl<'a, C: NSFastEnumeration + ?Sized> Iterator for NSFastEnumerator<'a, C> {
type Item = &'a C::Item;
fn next(&mut self) -> Option<&'a C::Item> {
if self.ptr == self.end && !self.update_buf() {
None
} else {
unsafe {
let obj = *self.ptr;
self.ptr = self.ptr.offset(1);
Some(obj.as_ref().unwrap_unchecked())
}
}
}
}
#[cfg(test)]
mod tests {
use super::NSFastEnumeration;
use crate::foundation::{NSArray, NSNumber};
#[test]
fn test_enumerator() {
let vec = (0..4).map(NSNumber::new_usize).collect();
let array = NSArray::from_vec(vec);
let enumerator = array.iter();
assert_eq!(enumerator.count(), 4);
let enumerator = array.iter();
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
}
#[test]
fn test_fast_enumerator() {
let vec = (0..4).map(NSNumber::new_usize).collect();
let array = NSArray::from_vec(vec);
let enumerator = array.iter_fast();
assert_eq!(enumerator.count(), 4);
let enumerator = array.iter_fast();
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
}
}

View file

@ -0,0 +1,165 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::ffi::NSInteger;
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// Information about an error condition including a domain, a
/// domain-specific error code, and application-specific information.
///
/// See also Apple's [documentation on error handling][err], and their
/// NSError [API reference][api].
///
/// [err]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorHandling/ErrorHandling.html#//apple_ref/doc/uid/TP40001806-CH201-SW1
/// [api]: https://developer.apple.com/documentation/foundation/nserror?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSError;
unsafe impl ClassType for NSError {
type Super = NSObject;
}
);
// SAFETY: Error objects are immutable data containers.
unsafe impl Sync for NSError {}
unsafe impl Send for NSError {}
impl UnwindSafe for NSError {}
impl RefUnwindSafe for NSError {}
pub type NSErrorUserInfoKey = NSString;
pub type NSErrorDomain = NSString;
extern_methods!(
/// Creation methods.
unsafe impl NSError {
/// Construct a new [`NSError`] with the given code in the given domain.
pub fn new(code: NSInteger, domain: &NSString) -> Id<Self, Shared> {
unsafe { Self::with_user_info(code, domain, None) }
}
// TODO: Figure out safety of `user_info` dict!
unsafe fn with_user_info(
code: NSInteger,
domain: &NSString,
user_info: Option<&NSDictionary<NSErrorUserInfoKey, NSObject>>,
) -> Id<Self, Shared> {
// SAFETY: `domain` and `user_info` are copied to the error object, so
// even if the `&NSString` came from a `&mut NSMutableString`, we're
// still good!
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithDomain: domain,
code: code,
userInfo: user_info,
]
}
}
}
/// Accessor methods.
unsafe impl NSError {
pub fn domain(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, domain] }
}
#[sel(code)]
pub fn code(&self) -> NSInteger;
pub fn user_info(&self) -> Option<Id<NSDictionary<NSErrorUserInfoKey, NSObject>, Shared>> {
unsafe { msg_send_id![self, userInfo] }
}
pub fn localized_description(&self) -> Id<NSString, Shared> {
// TODO: For some reason this leaks a lot?
let obj: Option<_> = unsafe { msg_send_id![self, localizedDescription] };
obj.expect(
"unexpected NULL localized description; a default should have been generated!",
)
}
// TODO: localizedRecoveryOptions
// TODO: localizedRecoverySuggestion
// TODO: localizedFailureReason
// TODO: helpAnchor
// TODO: +setUserInfoValueProviderForDomain:provider:
// TODO: +userInfoValueProviderForDomain:
// TODO: recoveryAttempter
// TODO: attemptRecoveryFromError:...
// TODO: Figure out if this is a good design, or if we should do something
// differently (like a Rusty name for the function, or putting a bunch of
// statics in a module instead)?
#[allow(non_snake_case)]
pub fn NSLocalizedDescriptionKey() -> &'static NSErrorUserInfoKey {
extern "C" {
#[link_name = "NSLocalizedDescriptionKey"]
static VALUE: &'static NSErrorUserInfoKey;
}
unsafe { VALUE }
}
// TODO: Other NSErrorUserInfoKey values
// TODO: NSErrorDomain values
}
);
impl fmt::Debug for NSError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NSError")
.field("domain", &self.domain())
.field("code", &self.code())
.field("user_info", &self.user_info())
.finish()
}
}
impl fmt::Display for NSError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.localized_description())
}
}
impl std::error::Error for NSError {}
unsafe impl NSCopying for NSError {
type Ownership = Shared;
type Output = Self;
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use crate::ns_string;
#[test]
fn custom_domain() {
let error = NSError::new(42, ns_string!("MyDomain"));
assert_eq!(error.code(), 42);
assert_eq!(&*error.domain(), ns_string!("MyDomain"));
let expected = if cfg!(feature = "apple") {
"The operation couldnt be completed. (MyDomain error 42.)"
} else {
"MyDomain 42"
};
assert_eq!(format!("{}", error), expected);
}
#[test]
fn basic() {
let error = NSError::new(-999, ns_string!("NSURLErrorDomain"));
let expected = if cfg!(feature = "apple") {
"The operation couldnt be completed. (NSURLErrorDomain error -999.)"
} else {
"NSURLErrorDomain -999"
};
assert_eq!(format!("{}", error), expected);
}
}

View file

@ -0,0 +1,200 @@
use core::fmt;
use core::hint::unreachable_unchecked;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::exception::Exception;
use crate::rc::{Id, Shared};
use crate::runtime::Object;
use crate::{extern_class, extern_methods, msg_send_id, sel, ClassType};
extern_class!(
/// A special condition that interrupts the normal flow of program
/// execution.
///
/// Exceptions can be thrown and caught using the `objc2::exception`
/// module.
///
/// See also [Apple's documentation][doc].
///
/// [doc]: https://developer.apple.com/documentation/foundation/nsexception?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSException;
unsafe impl ClassType for NSException {
type Super = NSObject;
}
);
// SAFETY: Exception objects are immutable data containers, and documented as
// thread safe.
unsafe impl Sync for NSException {}
unsafe impl Send for NSException {}
impl UnwindSafe for NSException {}
impl RefUnwindSafe for NSException {}
type NSExceptionName = NSString;
extern_methods!(
unsafe impl NSException {
/// Create a new [`NSException`] object.
///
/// Returns `None` if the exception couldn't be created (example: If the
/// process is out of memory).
pub fn new(
name: &NSExceptionName,
reason: Option<&NSString>,
user_info: Option<&NSDictionary<Object, Object>>,
) -> Option<Id<Self, Shared>> {
let obj = unsafe { msg_send_id![Self::class(), alloc] };
unsafe { msg_send_id![obj, initWithName: name, reason: reason, userInfo: user_info] }
}
#[sel(raise)]
unsafe fn raise_raw(&self);
/// Raises the exception, causing program flow to jump to the local
/// exception handler.
///
/// This is equivalent to using `objc2::exception::throw`.
///
///
/// # Safety
///
/// Same as `objc2::exception::throw`.
pub unsafe fn raise(&self) -> ! {
// SAFETY: We only create `Shared` NSExceptions, so it is safe to give
// to the place where `@catch` receives it.
unsafe { self.raise_raw() };
// SAFETY: `raise` will throw an exception, or abort if something
// unexpected happened.
unsafe { unreachable_unchecked() }
}
/// A that uniquely identifies the type of exception.
///
/// See [Apple's documentation][doc] for some of the different values this
/// can take.
///
/// [doc]: https://developer.apple.com/documentation/foundation/nsexceptionname?language=objc
pub fn name(&self) -> Id<NSExceptionName, Shared> {
// Nullability not documented, but a name is expected in most places.
unsafe { msg_send_id![self, name] }
}
/// A human-readable message summarizing the reason for the exception.
pub fn reason(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, reason] }
}
/// Application-specific data pertaining to the exception.
pub fn user_info(&self) -> Option<Id<NSDictionary<Object, Object>, Shared>> {
unsafe { msg_send_id![self, userInfo] }
}
/// Convert this into an [`Exception`] object.
pub fn into_exception(this: Id<Self, Shared>) -> Id<Exception, Shared> {
// SAFETY: Downcasting to "subclass"
unsafe { Id::cast(this) }
}
pub(crate) fn is_nsexception(obj: &Exception) -> bool {
if obj.class().responds_to(sel!(isKindOfClass:)) {
// SAFETY: We only use `isKindOfClass:` on NSObject
let obj: *const Exception = obj;
let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
obj.is_kind_of::<Self>()
} else {
false
}
}
/// Create this from an [`Exception`] object.
///
/// This should be considered a hint; it may return `Err` in very, very
/// few cases where the object is actually an instance of `NSException`.
pub fn from_exception(
obj: Id<Exception, Shared>,
) -> Result<Id<Self, Shared>, Id<Exception, Shared>> {
if Self::is_nsexception(&obj) {
// SAFETY: Just checked the object is an NSException
Ok(unsafe { Id::cast::<Self>(obj) })
} else {
Err(obj)
}
}
}
);
unsafe impl NSCopying for NSException {
type Ownership = Shared;
type Output = NSException;
}
impl alloc::borrow::ToOwned for NSException {
type Owned = Id<NSException, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Debug for NSException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let obj: &Object = self.as_ref();
write!(f, "{:?} '{}'", obj, self.name())?;
if let Some(reason) = self.reason() {
write!(f, " reason:{}", reason)?;
} else {
write!(f, " reason:(NULL)")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn create_and_query() {
let exc = NSException::new(
&NSString::from_str("abc"),
Some(&NSString::from_str("def")),
None,
)
.unwrap();
assert_eq!(exc.name(), NSString::from_str("abc"));
assert_eq!(exc.reason().unwrap(), NSString::from_str("def"));
assert!(exc.user_info().is_none());
let debug = format!("<NSException: {:p}> 'abc' reason:def", exc);
assert_eq!(format!("{:?}", exc), debug);
let description = if cfg!(feature = "gnustep-1-7") {
format!("<NSException: {:p}> NAME:abc REASON:def", exc)
} else {
"def".into()
};
let exc: &NSObject = &exc;
assert_eq!(format!("{:?}", exc), description);
}
#[test]
#[should_panic = "'abc' reason:def"]
fn unwrap() {
let exc = NSException::new(
&NSString::from_str("abc"),
Some(&NSString::from_str("def")),
None,
)
.unwrap();
let _: () = Err(exc).unwrap();
}
// Further tests in `tests::exception`
}

View file

@ -0,0 +1,446 @@
use crate::{Encode, Encoding, RefEncode};
#[cfg(target_pointer_width = "64")]
type InnerFloat = f64;
#[cfg(not(target_pointer_width = "64"))]
type InnerFloat = f32;
/// The basic type for all floating-point values.
///
/// This is [`f32`] on 32-bit platforms and [`f64`] on 64-bit platforms.
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/coregraphics/cgfloat?language=objc).
// Defined in CoreGraphics/CGBase.h
// TODO: Use a newtype here?
pub type CGFloat = InnerFloat;
// NSGeometry types are just aliases to CGGeometry types on iOS, tvOS, watchOS
// and macOS 64bit (and hence their Objective-C encodings are different).
//
// TODO: Adjust `objc2-encode` so that this is handled there, and so that we
// can effectively just forget about it and use `NS` and `CG` types equally.
#[cfg(all(
feature = "apple",
not(all(target_os = "macos", target_pointer_width = "32"))
))]
mod names {
pub(super) const POINT: &str = "CGPoint";
pub(super) const SIZE: &str = "CGSize";
pub(super) const RECT: &str = "CGRect";
}
#[cfg(any(
feature = "gnustep-1-7",
all(target_os = "macos", target_pointer_width = "32")
))]
mod names {
pub(super) const POINT: &str = "_NSPoint";
pub(super) const SIZE: &str = "_NSSize";
pub(super) const RECT: &str = "_NSRect";
}
/// A point in a two-dimensional coordinate system.
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgpoint?language=objc).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CGPoint {
/// The x-coordinate of the point.
pub x: CGFloat,
/// The y-coordinate of the point.
pub y: CGFloat,
}
unsafe impl Encode for CGPoint {
const ENCODING: Encoding =
Encoding::Struct(names::POINT, &[CGFloat::ENCODING, CGFloat::ENCODING]);
}
unsafe impl RefEncode for CGPoint {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl CGPoint {
/// Create a new point with the given coordinates.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGPoint;
/// assert_eq!(CGPoint::new(10.0, -2.3), CGPoint { x: 10.0, y: -2.3 });
/// ```
#[inline]
#[doc(alias = "NSMakePoint")]
#[doc(alias = "CGPointMake")]
pub const fn new(x: CGFloat, y: CGFloat) -> Self {
Self { x, y }
}
/// A point with both coordinates set to `0.0`.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGPoint;
/// assert_eq!(CGPoint::ZERO, CGPoint { x: 0.0, y: 0.0 });
/// ```
#[doc(alias = "NSZeroPoint")]
#[doc(alias = "CGPointZero")]
#[doc(alias = "ORIGIN")]
pub const ZERO: Self = Self::new(0.0, 0.0);
}
/// A two-dimensional size.
///
/// As this is sometimes used to represent a distance vector, rather than a
/// physical size, the width and height are _not_ guaranteed to be
/// non-negative! Methods that expect that must use one of [`CGSize::abs`] or
/// [`CGRect::standardize`].
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgsize?language=objc).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CGSize {
/// The dimensions along the x-axis.
pub width: CGFloat,
/// The dimensions along the y-axis.
pub height: CGFloat,
}
unsafe impl Encode for CGSize {
const ENCODING: Encoding =
Encoding::Struct(names::SIZE, &[CGFloat::ENCODING, CGFloat::ENCODING]);
}
unsafe impl RefEncode for CGSize {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl CGSize {
/// Create a new size with the given dimensions.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGSize;
/// let size = CGSize::new(10.0, 2.3);
/// assert_eq!(size.width, 10.0);
/// assert_eq!(size.height, 2.3);
/// ```
///
/// Negative values are allowed (though often undesired).
///
/// ```
/// use objc2::foundation::CGSize;
/// let size = CGSize::new(-1.0, 0.0);
/// assert_eq!(size.width, -1.0);
/// ```
#[inline]
#[doc(alias = "NSMakeSize")]
#[doc(alias = "CGSizeMake")]
pub const fn new(width: CGFloat, height: CGFloat) -> Self {
// The documentation for NSSize explicitly says:
// > If the value of width or height is negative, however, the
// > behavior of some methods may be undefined.
//
// But since this type can come from FFI, we'll leave it up to the
// user to ensure that it is used safely.
Self { width, height }
}
/// Convert the size to a non-negative size.
///
/// This can be used to convert the size to a safe value.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGSize;
/// assert_eq!(CGSize::new(-1.0, 1.0).abs(), CGSize::new(1.0, 1.0));
/// ```
#[inline]
pub fn abs(self) -> Self {
Self::new(self.width.abs(), self.height.abs())
}
/// A size that is 0.0 in both dimensions.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGSize;
/// assert_eq!(CGSize::ZERO, CGSize { width: 0.0, height: 0.0 });
/// ```
#[doc(alias = "NSZeroSize")]
#[doc(alias = "CGSizeZero")]
pub const ZERO: Self = Self::new(0.0, 0.0);
}
/// The location and dimensions of a rectangle.
///
/// In the default Core Graphics coordinate space (macOS), the origin is
/// located in the lower-left corner of the rectangle and the rectangle
/// extends towards the upper-right corner.
///
/// If the context has a flipped coordinate space (iOS, tvOS, watchOS) the
/// origin is in the upper-left corner and the rectangle extends towards the
/// lower-right corner.
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgrect?language=objc).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CGRect {
/// The coordinates of the rectangles origin.
pub origin: CGPoint,
/// The dimensions of the rectangle.
pub size: CGSize,
}
unsafe impl Encode for CGRect {
const ENCODING: Encoding =
Encoding::Struct(names::RECT, &[CGPoint::ENCODING, CGSize::ENCODING]);
}
unsafe impl RefEncode for CGRect {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl CGRect {
/// Create a new rectangle with the given origin and dimensions.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
/// let origin = CGPoint::new(10.0, -2.3);
/// let size = CGSize::new(5.0, 0.0);
/// let rect = CGRect::new(origin, size);
/// ```
#[inline]
#[doc(alias = "NSMakeRect")]
#[doc(alias = "CGRectMake")]
pub const fn new(origin: CGPoint, size: CGSize) -> Self {
Self { origin, size }
}
/// A rectangle with origin (0.0, 0.0) and zero width and height.
#[doc(alias = "NSZeroRect")]
#[doc(alias = "CGRectZero")]
pub const ZERO: Self = Self::new(CGPoint::ZERO, CGSize::ZERO);
/// Returns a rectangle with a positive width and height.
///
/// This is often useful
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
/// let origin = CGPoint::new(1.0, 1.0);
/// let size = CGSize::new(-5.0, -2.0);
/// let rect = CGRect::new(origin, size);
/// assert_eq!(rect.standardize().size, CGSize::new(5.0, 2.0));
/// ```
#[inline]
#[doc(alias = "CGRectStandardize")]
pub fn standardize(self) -> Self {
Self::new(self.origin, self.size.abs())
}
/// The smallest coordinate of the rectangle.
#[inline]
#[doc(alias = "CGRectGetMinX")]
#[doc(alias = "CGRectGetMinY")]
#[doc(alias = "NSMinX")]
#[doc(alias = "NSMinY")]
pub fn min(self) -> CGPoint {
self.origin
}
/// The center point of the rectangle.
#[inline]
#[doc(alias = "CGRectGetMidX")]
#[doc(alias = "CGRectGetMidY")]
#[doc(alias = "NSMidX")]
#[doc(alias = "NSMidY")]
pub fn mid(self) -> CGPoint {
CGPoint::new(
self.origin.x + (self.size.width * 0.5),
self.origin.y + (self.size.height * 0.5),
)
}
/// The largest coordinate of the rectangle.
#[inline]
#[doc(alias = "CGRectGetMaxX")]
#[doc(alias = "CGRectGetMaxY")]
#[doc(alias = "NSMaxX")]
#[doc(alias = "NSMaxY")]
pub fn max(self) -> CGPoint {
CGPoint::new(
self.origin.x + self.size.width,
self.origin.y + self.size.height,
)
}
/// Returns whether a rectangle has zero width or height.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
/// assert!(CGRect::ZERO.is_empty());
/// let point = CGPoint::new(1.0, 2.0);
/// assert!(CGRect::new(point, CGSize::ZERO).is_empty());
/// assert!(!CGRect::new(point, CGSize::new(1.0, 1.0)).is_empty());
/// ```
#[inline]
#[doc(alias = "CGRectIsEmpty")]
pub fn is_empty(self) -> bool {
!(self.size.width > 0.0 && self.size.height > 0.0)
// TODO: NaN handling?
// self.size.width <= 0.0 || self.size.height <= 0.0
}
// TODO: NSContainsRect / CGRectContainsRect
// TODO: NSDivideRect / CGRectDivide
// TODO: NSInsetRect / CGRectInset
// TODO: NSIntegralRect / CGRectIntegral
// TODO: NSIntersectionRect / CGRectIntersection
// TODO: NSUnionRect / CGRectUnion
// TODO: NSIntersectsRect / CGRectIntersectsRect
// TODO: NSMouseInRect
// TODO: NSMouseInRect
// TODO: NSPointInRect / CGRectContainsPoint
// TODO: NSOffsetRect / CGRectOffset
// TODO: CGRectIsNull
// TODO: CGRectIsInfinite
// TODO: CGRectInfinite
// TODO: CGRectNull
// TODO: NSHeight / CGRectGetHeight (standardized)
// TODO: NSWidth / CGRectGetWidth (standardized)
}
/// A point in a Cartesian coordinate system.
///
/// This is just a convenience alias for [`CGPoint`]. For ease of use, it is
/// available on all platforms, though in practice it is only useful on macOS.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nspoint?language=objc).
pub type NSPoint = CGPoint;
/// A two-dimensional size.
///
/// This is just a convenience alias for [`CGSize`]. For ease of use, it is
/// available on all platforms, though in practice it is only useful on macOS.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nssize?language=objc).
pub type NSSize = CGSize;
/// A rectangle.
///
/// This is just a convenience alias for [`CGRect`]. For ease of use, it is
/// available on all platforms, though in practice it is only useful on macOS.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrect?language=objc).
pub type NSRect = CGRect;
// TODO: struct NSEdgeInsets
// TODO: enum NSRectEdge
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cgsize_new() {
CGSize::new(1.0, 1.0);
CGSize::new(0.0, -0.0);
CGSize::new(-0.0, 0.0);
CGSize::new(-0.0, -0.0);
CGSize::new(-1.0, -1.0);
CGSize::new(-1.0, 1.0);
CGSize::new(1.0, -1.0);
}
// We know the Rust implementation handles NaN, infinite, negative zero
// and so on properly, so let's ensure that NSEqualXXX handles these as
// well (so that we're confident that the implementations are equivalent).
#[test]
#[cfg(any(all(feature = "apple", target_os = "macos"), feature = "gnustep-1-7"))] // or macabi
fn test_partial_eq() {
use crate::runtime::Bool;
// Note: No need to use "C-unwind"
extern "C" {
fn NSEqualPoints(a: NSPoint, b: NSPoint) -> Bool;
fn NSEqualSizes(a: NSSize, b: NSSize) -> Bool;
fn NSEqualRects(a: NSRect, b: NSRect) -> Bool;
}
// We assume that comparisons handle e.g. `x` and `y` in the same way,
// therefore we just set the coordinates / dimensions to the same.
let cases: &[(CGFloat, CGFloat)] = &[
(0.0, 0.0),
(-0.0, -0.0),
(0.0, -0.0),
(1.0, 1.0 + CGFloat::EPSILON),
(0.0, CGFloat::MIN_POSITIVE),
(0.0, CGFloat::EPSILON),
(1.0, 1.0),
(1.0, -1.0),
// Infinity
(CGFloat::INFINITY, CGFloat::INFINITY),
(CGFloat::INFINITY, CGFloat::NEG_INFINITY),
(CGFloat::NEG_INFINITY, CGFloat::NEG_INFINITY),
// NaN
(CGFloat::NAN, 0.0),
(CGFloat::NAN, 1.0),
(CGFloat::NAN, CGFloat::NAN),
(CGFloat::NAN, -CGFloat::NAN),
(-CGFloat::NAN, -CGFloat::NAN),
(CGFloat::NAN, CGFloat::INFINITY),
];
for case in cases {
let point_a = NSPoint::new(case.0, case.1);
let point_b = NSPoint::new(case.0, case.1);
let actual = unsafe { NSEqualPoints(point_a, point_b).as_bool() };
assert_eq!(point_a == point_b, actual);
if case.0 >= 0.0 && case.1 >= 0.0 {
let size_a = NSSize::new(case.0, case.1);
let size_b = NSSize::new(case.0, case.1);
let actual = unsafe { NSEqualSizes(size_a, size_b).as_bool() };
assert_eq!(size_a == size_b, actual);
let rect_a = NSRect::new(point_a, size_a);
let rect_b = NSRect::new(point_b, size_b);
let actual = unsafe { NSEqualRects(rect_a, rect_b).as_bool() };
assert_eq!(rect_a == rect_b, actual);
}
}
}
}

View file

@ -0,0 +1,215 @@
//! Bindings to the `Foundation` framework.
//!
//! This is the [`std`] equivalent for Objective-C, containing essential data
//! types, collections, and operating-system services.
//!
//! See [Apple's documentation](https://developer.apple.com/documentation/foundation?language=objc).
//!
//!
//! ## Philosophy
//!
//! The `Foundation` framework is _huge_! If we aspired to map every API it
//! exposes (a lot of it is just helper methods to make Objective-C more
//! ergonomic), this library would never be finished. Instead, our focus lies
//! on conversion methods, to allow easily using them from Rust.
//!
//! If you find some API that an object doesn't expose (but should), we gladly
//! accept [pull requests]. If it is something that is out of scope, these
//! objects implement the [`Message`] trait, so you can always just manually
//! call a method on them using the [`msg_send!`] family of macros.
//!
//! [pull requests]: https://github.com/madsmtm/objc2/pulls
//! [`Message`]: crate::Message
//! [`msg_send!`]: crate::msg_send
//!
//!
//! # Use of `Deref`
//!
//! `objc2::foundation` uses the [`Deref`] trait in a bit special way: All
//! objects deref to their superclasses. For example, `NSMutableArray` derefs
//! to `NSArray`, which in turn derefs to `NSObject`.
//!
//! Note that this is explicitly recommended against in [the
//! documentation][`Deref`] and [the Rust Design patterns
//! book][anti-pattern-deref] (see those links for details).
//!
//! Due to Objective-C objects only ever being accessible behind pointers in
//! the first place, the problems stated there are less severe, and having the
//! implementation just means that everything is much nicer when you actually
//! want to use the objects!
//!
//! All objects also implement [`AsRef`] and [`AsMut`] to their superclass,
//! and can be used in [`Id::into_super`], so if you favour explicit
//! conversion, that is a possibility too.
//!
//! [`Deref`]: std::ops::Deref
//! [`ClassType`]: crate::ClassType
//! [anti-pattern-deref]: https://rust-unofficial.github.io/patterns/anti_patterns/deref.html
//! [`Id::into_super`]: crate::rc::Id::into_super
// TODO: Remove these
#![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;
pub use self::dictionary::NSDictionary;
pub use self::enumerator::{NSEnumerator, NSFastEnumeration, NSFastEnumerator};
pub use self::error::{NSError, NSErrorDomain, NSErrorUserInfoKey};
pub use self::exception::NSException;
pub use self::geometry::{CGFloat, CGPoint, CGRect, CGSize, NSPoint, NSRect, NSSize};
pub use self::mutable_array::NSMutableArray;
pub use self::mutable_attributed_string::NSMutableAttributedString;
pub use self::mutable_data::NSMutableData;
pub use self::mutable_dictionary::NSMutableDictionary;
pub use self::mutable_set::NSMutableSet;
pub use self::mutable_string::NSMutableString;
pub use self::number::NSNumber;
pub use self::object::NSObject;
pub use self::process_info::NSProcessInfo;
pub use self::range::NSRange;
pub use self::set::NSSet;
pub use self::string::NSString;
pub use self::thread::{is_main_thread, is_multi_threaded, MainThreadMarker, NSThread};
#[cfg(not(macos_10_7))] // Temporary
pub use self::uuid::NSUUID;
pub use self::value::NSValue;
pub use self::zone::NSZone;
// Available under Foundation, so makes sense here as well:
// https://developer.apple.com/documentation/foundation/numbers_data_and_basic_values?language=objc
#[doc(no_inline)]
pub use crate::ffi::{NSInteger, NSUInteger};
/// A value indicating that a requested item couldnt be found or doesnt 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" {}
#[cfg(feature = "gnustep-1-7")]
#[link(name = "gnustep-base", kind = "dylib")]
extern "C" {}
#[doc(hidden)]
pub mod __ns_string;
mod array;
mod attributed_string;
mod bundle;
mod comparison_result;
mod copying;
mod data;
mod dictionary;
mod enumerator;
mod error;
mod exception;
mod geometry;
mod mutable_array;
mod mutable_attributed_string;
mod mutable_data;
mod mutable_dictionary;
mod mutable_set;
mod mutable_string;
mod number;
mod object;
mod process_info;
mod range;
mod set;
mod string;
mod thread;
// Temporarily disable testing UUID on macOS 10.7 until
#[cfg(not(macos_10_7))]
mod uuid;
mod value;
mod zone;
#[cfg(test)]
mod tests {
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
use crate::rc::{Id, Owned, Shared};
// We expect most Foundation types to be UnwindSafe and RefUnwindSafe,
// since they follow Rust's usual mutability rules (&T = immutable).
//
// A _lot_ of Objective-C code out there would be subtly broken if e.g.
// `NSString` wasn't exception safe!
// As an example: -[NSArray objectAtIndex:] can throw, but it is still
// perfectly valid to access the array after that!
//
// Note that e.g. `&mut NSMutableString` is still not exception safe, but
// that is the entire idea of `UnwindSafe` (that if the object could have
// been mutated, it is not exception safe).
//
// Also note that this is still just a speed bump, not actually part of
// any unsafe contract; we really protect against it if something is not
// exception safe, since `UnwindSafe` is a safe trait.
fn assert_unwindsafe<T: UnwindSafe + RefUnwindSafe>() {}
fn assert_auto_traits<T: Send + Sync + UnwindSafe + RefUnwindSafe>() {
assert_unwindsafe::<T>();
}
#[test]
fn send_sync_unwindsafe() {
assert_auto_traits::<NSArray<NSString, Shared>>();
assert_auto_traits::<NSArray<NSString, Owned>>();
assert_auto_traits::<Id<NSArray<NSString, Shared>, Shared>>();
assert_auto_traits::<Id<NSArray<NSString, Owned>, Shared>>();
assert_auto_traits::<Id<NSArray<NSString, Shared>, Owned>>();
assert_auto_traits::<Id<NSArray<NSString, Owned>, Owned>>();
assert_auto_traits::<NSAttributedString>();
assert_auto_traits::<NSComparisonResult>();
assert_auto_traits::<NSData>();
assert_auto_traits::<NSDictionary<NSString, NSString>>();
assert_auto_traits::<NSSet<NSString, Shared>>();
assert_auto_traits::<NSSet<NSString, Owned>>();
assert_auto_traits::<Id<NSSet<NSString, Shared>, Shared>>();
assert_auto_traits::<Id<NSSet<NSString, Owned>, Shared>>();
assert_auto_traits::<Id<NSSet<NSString, Shared>, Owned>>();
assert_auto_traits::<Id<NSSet<NSString, Owned>, Owned>>();
// TODO: Figure out if Send + Sync is safe?
// assert_auto_traits::<NSEnumerator<NSString>>();
// assert_auto_traits::<NSFastEnumerator<NSArray<NSString, Shared>>>();
assert_auto_traits::<NSError>();
assert_auto_traits::<NSException>();
assert_auto_traits::<CGFloat>();
assert_auto_traits::<NSPoint>();
assert_auto_traits::<NSRect>();
assert_auto_traits::<NSSize>();
assert_auto_traits::<NSMutableArray<NSString, Shared>>();
assert_auto_traits::<NSMutableAttributedString>();
assert_auto_traits::<NSMutableData>();
assert_auto_traits::<NSMutableDictionary<NSString, NSString>>();
assert_auto_traits::<NSMutableSet<NSString, Shared>>();
assert_auto_traits::<NSMutableString>();
assert_auto_traits::<NSNumber>();
// assert_auto_traits::<NSObject>(); // Intentional
assert_auto_traits::<NSProcessInfo>();
assert_auto_traits::<NSRange>();
assert_auto_traits::<NSString>();
assert_unwindsafe::<MainThreadMarker>(); // Intentional
assert_auto_traits::<NSThread>();
#[cfg(not(macos_10_7))]
assert_auto_traits::<NSUUID>();
// assert_auto_traits::<NSValue>(); // Intentional
assert_unwindsafe::<NSZone>(); // Intentional
}
}

View file

@ -0,0 +1,326 @@
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::ffi::c_void;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};
use super::array::with_objects;
use super::{
NSArray, NSComparisonResult, NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying,
NSObject,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
/// A growable ordered collection of objects.
///
/// See the documentation for [`NSArray`] and/or [Apple's
/// documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutablearray?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableArray<T: Message, O: Ownership = Owned> {
p: PhantomData<*mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSMutableArray<T, O> {
#[inherits(NSObject)]
type Super = NSArray<T, O>;
}
);
// SAFETY: Same as NSArray<T, O>
//
// Put here because rustdoc doesn't show these otherwise
unsafe impl<T: Message + Sync + Send> Sync for NSMutableArray<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSMutableArray<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSMutableArray<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSMutableArray<T, Owned> {}
extern_methods!(
/// Generic creation methods.
unsafe impl<T: Message, O: Ownership> NSMutableArray<T, O> {
pub fn new() -> Id<Self, Owned> {
// SAFETY: Same as `NSArray::new`, except mutable arrays are always
// unique.
unsafe { msg_send_id![Self::class(), new] }
}
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, Owned> {
// SAFETY: Same as `NSArray::from_vec`, except mutable arrays are
// always unique.
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
}
}
/// Creation methods that produce shared arrays.
unsafe impl<T: Message> NSMutableArray<T, Shared> {
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Owned> {
// SAFETY: Same as `NSArray::from_slice`, except mutable arrays are
// always unique.
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
}
/// Generic accessor methods.
unsafe impl<T: Message, O: Ownership> NSMutableArray<T, O> {
#[doc(alias = "addObject:")]
pub fn push(&mut self, obj: Id<T, O>) {
// SAFETY: The object is not nil
unsafe { msg_send![self, addObject: &*obj] }
}
#[doc(alias = "insertObject:atIndex:")]
pub fn insert(&mut self, index: usize, obj: Id<T, O>) {
// TODO: Replace this check with catching the thrown NSRangeException
let len = self.len();
if index < len {
// SAFETY: The object is not nil and the index is checked to be in
// bounds.
unsafe { msg_send![self, insertObject: &*obj, atIndex: index] }
} else {
panic!(
"insertion index (is {}) should be <= len (is {})",
index, len
);
}
}
#[doc(alias = "replaceObjectAtIndex:withObject:")]
pub fn replace(&mut self, index: usize, obj: Id<T, O>) -> Id<T, O> {
let old_obj = unsafe {
let obj = self.get(index).unwrap();
Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked()
};
unsafe {
let _: () = msg_send![
self,
replaceObjectAtIndex: index,
withObject: &*obj,
];
}
old_obj
}
#[sel(removeObjectAtIndex:)]
unsafe fn remove_at(&mut self, index: usize);
#[doc(alias = "removeObjectAtIndex:")]
pub fn remove(&mut self, index: usize) -> Id<T, O> {
let obj = if let Some(obj) = self.get(index) {
unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
} else {
panic!("removal index should be < len");
};
unsafe { self.remove_at(index) };
obj
}
#[sel(removeLastObject)]
unsafe fn remove_last(&mut self);
#[doc(alias = "removeLastObject")]
pub fn pop(&mut self) -> Option<Id<T, O>> {
self.last()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.map(|obj| {
// SAFETY: `Self::last` just checked that there is an object
unsafe { self.remove_last() };
obj
})
}
#[doc(alias = "removeAllObjects")]
#[sel(removeAllObjects)]
pub fn clear(&mut self);
#[doc(alias = "sortUsingFunction:context:")]
pub fn sort_by<F: FnMut(&T, &T) -> Ordering>(&mut self, compare: F) {
// TODO: "C-unwind"
extern "C" fn compare_with_closure<U, F: FnMut(&U, &U) -> Ordering>(
obj1: &U,
obj2: &U,
context: *mut c_void,
) -> NSComparisonResult {
// Bring back a reference to the closure.
// Guaranteed to be unique, we gave `sortUsingFunction` unique is
// ownership, and that method only runs one function at a time.
let closure: &mut F = unsafe { context.cast::<F>().as_mut().unwrap_unchecked() };
NSComparisonResult::from((*closure)(obj1, obj2))
}
// We can't name the actual lifetimes in use here, so use `_`.
// See also https://github.com/rust-lang/rust/issues/56105
let f: extern "C" fn(_, _, *mut c_void) -> NSComparisonResult =
compare_with_closure::<T, F>;
// Grab a type-erased pointer to the closure (a pointer to stack).
let mut closure = compare;
let context: *mut F = &mut closure;
let context: *mut c_void = context.cast();
unsafe {
let _: () = msg_send![self, sortUsingFunction: f, context: context];
}
// Keep the closure alive until the function has run.
drop(closure);
}
}
);
// Copying only possible when ItemOwnership = Shared
/// This is implemented as a shallow copy.
unsafe impl<T: Message> NSCopying for NSMutableArray<T, Shared> {
type Ownership = Shared;
type Output = NSArray<T, Shared>;
}
/// This is implemented as a shallow copy.
unsafe impl<T: Message> NSMutableCopying for NSMutableArray<T, Shared> {
type Output = NSMutableArray<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSMutableArray<T, Shared> {
type Owned = Id<NSMutableArray<T, Shared>, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSMutableArray<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSMutableArray<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSMutableArray<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message, O: Ownership> Extend<Id<T, O>> for NSMutableArray<T, O> {
fn extend<I: IntoIterator<Item = Id<T, O>>>(&mut self, iter: I) {
let iterator = iter.into_iter();
iterator.for_each(move |item| self.push(item));
}
}
impl<T: Message, O: Ownership> Index<usize> for NSMutableArray<T, O> {
type Output = T;
fn index(&self, index: usize) -> &T {
self.get(index).unwrap()
}
}
impl<T: Message> IndexMut<usize> for NSMutableArray<T, Owned> {
fn index_mut(&mut self, index: usize) -> &mut T {
self.get_mut(index).unwrap()
}
}
impl<T: Message, O: Ownership> DefaultId for NSMutableArray<T, O> {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSMutableArray<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::foundation::NSString;
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
#[test]
fn test_adding() {
let mut array = NSMutableArray::new();
let obj1 = RcTestObject::new();
let obj2 = RcTestObject::new();
let mut expected = ThreadTestData::current();
array.push(obj1);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(array.len(), 1);
assert_eq!(array.get(0), array.get(0));
array.insert(0, obj2);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(array.len(), 2);
}
#[test]
fn test_replace() {
let mut array = NSMutableArray::new();
let obj1 = RcTestObject::new();
let obj2 = RcTestObject::new();
array.push(obj1);
let mut expected = ThreadTestData::current();
let old_obj = array.replace(0, obj2);
expected.retain += 2;
expected.release += 2;
expected.assert_current();
assert_ne!(&*old_obj, array.get(0).unwrap());
}
#[test]
fn test_remove() {
let mut array = NSMutableArray::new();
for _ in 0..4 {
array.push(RcTestObject::new());
}
let mut expected = ThreadTestData::current();
let _obj = array.remove(1);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(array.len(), 3);
let _obj = array.pop();
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(array.len(), 2);
array.clear();
expected.release += 2;
expected.dealloc += 2;
expected.assert_current();
assert_eq!(array.len(), 0);
}
#[test]
fn test_sort() {
let strings = vec![NSString::from_str("hello"), NSString::from_str("hi")];
let mut strings = NSMutableArray::from_vec(strings);
autoreleasepool(|pool| {
strings.sort_by(|s1, s2| s1.as_str(pool).len().cmp(&s2.as_str(pool).len()));
assert_eq!(strings[0].as_str(pool), "hi");
assert_eq!(strings[1].as_str(pool), "hello");
});
}
}

View file

@ -0,0 +1,115 @@
use core::fmt;
use super::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A mutable string that has associated attributes.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutableattributedstring?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableAttributedString;
unsafe impl ClassType for NSMutableAttributedString {
#[inherits(NSObject)]
type Super = NSAttributedString;
}
);
extern_methods!(
/// Creating mutable attributed strings.
unsafe impl NSMutableAttributedString {
/// Construct an empty mutable attributed string.
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
// TODO: new_with_attributes
#[doc(alias = "initWithString:")]
pub fn from_nsstring(string: &NSString) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string]
}
}
#[doc(alias = "initWithAttributedString:")]
pub fn from_attributed_nsstring(attributed_string: &NSAttributedString) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithAttributedString: attributed_string]
}
}
}
/// Modifying the attributed string.
unsafe impl NSMutableAttributedString {
// TODO
// - mutableString
// - replaceCharactersInRange:withString:
// - setAttributes:range:
/// Replaces the entire attributed string.
#[doc(alias = "setAttributedString:")]
#[sel(setAttributedString:)]
pub fn replace(&mut self, attributed_string: &NSAttributedString);
}
);
impl DefaultId for NSMutableAttributedString {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSMutableAttributedString {
type Ownership = Shared;
type Output = NSAttributedString;
}
unsafe impl NSMutableCopying for NSMutableAttributedString {
type Output = NSMutableAttributedString;
}
impl alloc::borrow::ToOwned for NSMutableAttributedString {
type Owned = Id<NSMutableAttributedString, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
impl fmt::Debug for NSMutableAttributedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn test_new() {
let s = NSAttributedString::new();
assert_eq!(&s.string().to_string(), "");
}
#[test]
fn test_copy() {
let s1 = NSMutableAttributedString::from_nsstring(&NSString::from_str("abc"));
let s2 = s1.copy();
assert_ne!(Id::as_ptr(&s1).cast(), Id::as_ptr(&s2));
assert!(s2.is_kind_of::<NSAttributedString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
assert!(s3.is_kind_of::<NSMutableAttributedString>());
}
}

View file

@ -0,0 +1,361 @@
#[cfg(feature = "block")]
use alloc::vec::Vec;
use core::ffi::c_void;
use core::fmt;
use core::ops::{Index, IndexMut, Range};
use core::slice::{self, SliceIndex};
use std::io;
use super::data::with_slice;
use super::{NSCopying, NSData, NSMutableCopying, NSObject, NSRange};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A dynamic byte buffer in memory.
///
/// This is the Objective-C equivalent of a [`Vec`] containing [`u8`].
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutabledata?language=objc).
///
/// [`Vec`]: std::vec::Vec
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableData;
unsafe impl ClassType for NSMutableData {
#[inherits(NSObject)]
type Super = NSData;
}
);
extern_methods!(
/// Creation methods
unsafe impl NSMutableData {
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn with_bytes(bytes: &[u8]) -> Id<Self, Owned> {
unsafe { Id::from_shared(Id::cast(with_slice(Self::class(), bytes))) }
}
#[cfg(feature = "block")]
pub fn from_vec(bytes: Vec<u8>) -> Id<Self, Owned> {
unsafe { Id::from_shared(Id::cast(super::data::with_vec(Self::class(), bytes))) }
}
// TODO: Use malloc_buf/mbox and `initWithBytesNoCopy:...`?
#[doc(alias = "initWithData:")]
pub fn from_data(data: &NSData) -> Id<Self, Owned> {
// Not provided on NSData, one should just use NSData::copy or similar
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithData: data]
}
}
#[doc(alias = "initWithCapacity:")]
pub fn with_capacity(capacity: usize) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithCapacity: capacity]
}
}
}
/// Mutation methods
unsafe impl NSMutableData {
/// Expands with zeroes, or truncates the buffer.
#[doc(alias = "setLength:")]
#[sel(setLength:)]
pub fn set_len(&mut self, len: usize);
#[sel(mutableBytes)]
fn bytes_mut_raw(&mut self) -> *mut c_void;
#[doc(alias = "mutableBytes")]
pub fn bytes_mut(&mut self) -> &mut [u8] {
let ptr = self.bytes_mut_raw();
let ptr: *mut u8 = ptr.cast();
// The bytes pointer may be null for length zero
if ptr.is_null() {
&mut []
} else {
unsafe { slice::from_raw_parts_mut(ptr, self.len()) }
}
}
#[sel(appendBytes:length:)]
unsafe fn append_raw(&mut self, ptr: *const c_void, len: usize);
#[doc(alias = "appendBytes:length:")]
pub fn extend_from_slice(&mut self, bytes: &[u8]) {
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
unsafe { self.append_raw(bytes_ptr, bytes.len()) }
}
pub fn push(&mut self, byte: u8) {
self.extend_from_slice(&[byte])
}
#[sel(replaceBytesInRange:withBytes:length:)]
unsafe fn replace_raw(&mut self, range: NSRange, ptr: *const c_void, len: usize);
#[doc(alias = "replaceBytesInRange:withBytes:length:")]
pub fn replace_range(&mut self, range: Range<usize>, bytes: &[u8]) {
let range = NSRange::from(range);
// No need to verify the length of the range here,
// `replaceBytesInRange:` just zero-fills if out of bounds.
let ptr: *const c_void = bytes.as_ptr().cast();
unsafe { self.replace_raw(range, ptr, bytes.len()) }
}
pub fn set_bytes(&mut self, bytes: &[u8]) {
let len = self.len();
self.replace_range(0..len, bytes);
}
}
);
unsafe impl NSCopying for NSMutableData {
type Ownership = Shared;
type Output = NSData;
}
unsafe impl NSMutableCopying for NSMutableData {
type Output = NSMutableData;
}
impl alloc::borrow::ToOwned for NSMutableData {
type Owned = Id<NSMutableData, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
impl AsRef<[u8]> for NSMutableData {
fn as_ref(&self) -> &[u8] {
self.bytes()
}
}
impl AsMut<[u8]> for NSMutableData {
fn as_mut(&mut self) -> &mut [u8] {
self.bytes_mut()
}
}
impl<I: SliceIndex<[u8]>> Index<I> for NSMutableData {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
Index::index(self.bytes(), index)
}
}
impl<I: SliceIndex<[u8]>> IndexMut<I> for NSMutableData {
#[inline]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
IndexMut::index_mut(self.bytes_mut(), index)
}
}
// impl FromIterator<u8> for Id<NSMutableData, Owned> {
// fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
// let iter = iter.into_iter();
// let (lower, _) = iter.size_hint();
// let data = Self::with_capacity(lower);
// for item in iter {
// data.push(item);
// }
// data
// }
// }
impl Extend<u8> for NSMutableData {
/// You should use [`extend_from_slice`] whenever possible, it is more
/// performant.
///
/// [`extend_from_slice`]: Self::extend_from_slice
fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
let iterator = iter.into_iter();
iterator.for_each(move |item| self.push(item));
}
}
// Vec also has this impl
impl<'a> Extend<&'a u8> for NSMutableData {
fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
let iterator = iter.into_iter();
iterator.for_each(move |item| self.push(*item));
}
}
impl io::Write for NSMutableData {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.extend_from_slice(buf);
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl DefaultId for NSMutableData {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl fmt::Debug for NSMutableData {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<'a> IntoIterator for &'a NSMutableData {
type Item = &'a u8;
type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.bytes().iter()
}
}
impl<'a> IntoIterator for &'a mut NSMutableData {
type Item = &'a mut u8;
type IntoIter = core::slice::IterMut<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.bytes_mut().iter_mut()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::Object;
#[test]
fn test_bytes_mut() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.bytes_mut()[0] = 3;
assert_eq!(data.bytes(), [3, 16]);
}
#[test]
fn test_set_len() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.set_len(4);
assert_eq!(data.len(), 4);
assert_eq!(data.bytes(), [7, 16, 0, 0]);
data.set_len(1);
assert_eq!(data.len(), 1);
assert_eq!(data.bytes(), [7]);
}
#[test]
fn test_append() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.extend_from_slice(&[3, 52]);
assert_eq!(data.len(), 4);
assert_eq!(data.bytes(), [7, 16, 3, 52]);
}
#[test]
fn test_replace() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.replace_range(0..0, &[3]);
assert_eq!(data.bytes(), [3, 7, 16]);
data.replace_range(1..2, &[52, 13]);
assert_eq!(data.bytes(), [3, 52, 13, 16]);
data.replace_range(2..4, &[6]);
assert_eq!(data.bytes(), [3, 52, 6]);
data.set_bytes(&[8, 17]);
assert_eq!(data.bytes(), [8, 17]);
}
#[test]
fn test_from_data() {
let data = NSData::with_bytes(&[1, 2]);
let mut_data = NSMutableData::from_data(&data);
assert_eq!(&*data, &**mut_data);
}
#[test]
fn test_with_capacity() {
let mut data = NSMutableData::with_capacity(5);
assert_eq!(data.bytes(), &[]);
data.extend_from_slice(&[1, 2, 3, 4, 5]);
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5]);
data.extend_from_slice(&[6, 7]);
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_extend() {
let mut data = NSMutableData::with_bytes(&[1, 2]);
data.extend(3..=5);
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5]);
data.extend(&*NSData::with_bytes(&[6, 7]));
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_as_ref_borrow() {
use core::borrow::{Borrow, BorrowMut};
fn impls_borrow<T: AsRef<U> + Borrow<U> + ?Sized, U: ?Sized>(_: &T) {}
fn impls_borrow_mut<T: AsMut<U> + BorrowMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
let mut obj = NSMutableData::new();
impls_borrow::<Id<NSMutableData, Owned>, NSMutableData>(&obj);
impls_borrow_mut::<Id<NSMutableData, Owned>, NSMutableData>(&mut obj);
impls_borrow::<NSMutableData, NSMutableData>(&obj);
impls_borrow_mut::<NSMutableData, NSMutableData>(&mut obj);
impls_borrow::<NSMutableData, NSData>(&obj);
impls_borrow_mut::<NSMutableData, NSData>(&mut obj);
impls_borrow::<NSMutableData, NSObject>(&obj);
impls_borrow_mut::<NSMutableData, NSObject>(&mut obj);
impls_borrow::<NSMutableData, Object>(&obj);
impls_borrow_mut::<NSMutableData, Object>(&mut obj);
impls_borrow::<NSData, NSData>(&obj);
impls_borrow_mut::<NSData, NSData>(&mut obj);
impls_borrow::<NSData, NSObject>(&obj);
impls_borrow_mut::<NSData, NSObject>(&mut obj);
impls_borrow::<NSData, Object>(&obj);
impls_borrow_mut::<NSData, Object>(&mut obj);
fn impls_as_ref<T: AsRef<U> + ?Sized, U: ?Sized>(_: &T) {}
fn impls_as_mut<T: AsMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
impls_as_ref::<NSMutableData, [u8]>(&obj);
impls_as_mut::<NSMutableData, [u8]>(&mut obj);
impls_as_ref::<NSData, [u8]>(&obj);
let obj: &mut NSMutableData = &mut obj;
let _: &[u8] = obj.as_ref();
let _: &mut [u8] = obj.as_mut();
let obj: &mut NSData = obj;
let _: &[u8] = obj.as_ref();
}
}

View file

@ -0,0 +1,399 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr;
use super::{NSArray, NSCopying, NSDictionary, NSFastEnumeration, NSObject};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{ClassType, __inner_extern_class, extern_methods, msg_send_id, Message};
__inner_extern_class!(
/// A mutable collection of objects associated with unique keys.
///
/// See the documentation for [`NSDictionary`] and/or [Apple's
/// documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutabledictionary?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableDictionary<K: Message, V: Message> {
key: PhantomData<Id<K, Shared>>,
obj: PhantomData<Id<V, Owned>>,
}
unsafe impl<K: Message, V: Message> ClassType for NSMutableDictionary<K, V> {
#[inherits(NSObject)]
type Super = NSDictionary<K, V>;
}
);
// Same as `NSDictionary<K, V>`
unsafe impl<K: Message + Sync + Send, V: Message + Sync> Sync for NSMutableDictionary<K, V> {}
unsafe impl<K: Message + Sync + Send, V: Message + Send> Send for NSMutableDictionary<K, V> {}
// Same as `NSDictionary<K, V>`
impl<K: Message + UnwindSafe, V: Message + UnwindSafe> UnwindSafe for NSMutableDictionary<K, V> {}
impl<K: Message + RefUnwindSafe, V: Message + RefUnwindSafe> RefUnwindSafe
for NSMutableDictionary<K, V>
{
}
extern_methods!(
unsafe impl<K: Message, V: Message> NSMutableDictionary<K, V> {
/// Creates an empty [`NSMutableDictionary`].
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let dict = NSMutableDictionary::<NSString, NSObject>::new();
/// ```
pub fn new() -> Id<Self, Owned> {
// SAFETY:
// Mutable dictionaries are always unique, so it's safe to return
// `Id<Self, Owned>`
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(setDictionary:)]
fn set_dictionary(&mut self, dict: &NSDictionary<K, V>);
/// Creates an [`NSMutableDictionary`] from a slice of keys and a
/// vector of values.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSNumber, NSObject};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let dict = NSMutableDictionary::from_keys_and_objects(
/// &[
/// &*NSNumber::new_i32(1),
/// &*NSNumber::new_i32(2),
/// &*NSNumber::new_i32(3),
/// ],
/// vec![NSObject::new(), NSObject::new(), NSObject::new()],
/// );
/// ```
pub fn from_keys_and_objects<T>(keys: &[&T], vals: Vec<Id<V, Owned>>) -> Id<Self, Owned>
where
T: NSCopying<Output = K>,
{
let mut dict = NSMutableDictionary::new();
dict.set_dictionary(&*NSDictionary::from_keys_and_objects(keys, vals));
dict
}
/// Returns a mutable reference to the value corresponding to the key.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// println!("{:?}", dict.get_mut(ns_string!("one")));
/// ```
#[doc(alias = "objectForKey:")]
#[sel(objectForKey:)]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V>;
#[sel(getObjects:andKeys:)]
unsafe fn get_objects_and_keys(&self, objects: *mut &mut V, keys: *mut &K);
/// Returns a vector of mutable references to the values in the dictionary.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// for val in dict.values_mut() {
/// println!("{:?}", val);
/// }
/// ```
#[doc(alias = "getObjects:andKeys:")]
pub fn values_mut(&mut self) -> Vec<&mut V> {
let len = self.len();
let mut vals: Vec<&mut V> = Vec::with_capacity(len);
// SAFETY: `vals` is not null
unsafe {
self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut());
vals.set_len(len);
}
vals
}
#[sel(setObject:forKey:)]
fn set_object_for_key(&mut self, object: &V, key: &K);
/// Inserts a key-value pair into the dictionary.
///
/// If the dictionary did not have this key present, None is returned.
/// If the dictionary did have this key present, the value is updated,
/// and the old value is returned.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// ```
#[doc(alias = "setObject:forKey:")]
pub fn insert(&mut self, key: Id<K, Shared>, value: Id<V, Owned>) -> Option<Id<V, Owned>> {
// SAFETY:
// `obj` is a reference to a value in the dictionary so it's safe
// to cast it to a pointer and pass it to `Id::retain_autoreleased`
let obj = self.get(&*key).map(|obj| unsafe {
Id::retain_autoreleased(obj as *const V as *mut V).unwrap_unchecked()
});
self.set_object_for_key(&*value, &*key);
obj
}
#[sel(removeObjectForKey:)]
fn remove_object_for_key(&mut self, key: &K);
/// Removes a key from the dictionary, returning the value at the key
/// if the key was previously in the dictionary.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// dict.remove(ns_string!("one"));
/// assert!(dict.is_empty());
/// ```
#[doc(alias = "removeObjectForKey:")]
pub fn remove(&mut self, key: &K) -> Option<Id<V, Owned>> {
// SAFETY:
// `obj` is a reference to a value in the dictionary so it's safe
// to cast it to a pointer and pass it to `Id::retain_autoreleased`
let obj = self.get(key).map(|obj| unsafe {
Id::retain_autoreleased(obj as *const V as *mut V).unwrap_unchecked()
});
self.remove_object_for_key(key);
obj
}
/// Clears the dictionary, removing all key-value pairs.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// dict.clear();
/// assert!(dict.is_empty());
/// ```
#[doc(alias = "removeAllObjects")]
#[sel(removeAllObjects)]
pub fn clear(&mut self);
/// Returns an [`NSArray`] containing the dictionary's values,
/// consuming the dictionary.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// let array = NSMutableDictionary::into_values_array(dict);
/// println!("{:?}", array);
/// ```
pub fn into_values_array(dict: Id<Self, Owned>) -> Id<NSArray<V, Owned>, Shared> {
unsafe { msg_send_id![&dict, allValues] }
}
}
);
unsafe impl<K: Message, V: Message> NSFastEnumeration for NSMutableDictionary<K, V> {
type Item = K;
}
impl<'a, K: Message, V: Message> Index<&'a K> for NSMutableDictionary<K, V> {
type Output = V;
fn index<'s>(&'s self, index: &'a K) -> &'s V {
self.get(index).unwrap()
}
}
impl<'a, K: Message, V: Message> IndexMut<&'a K> for NSMutableDictionary<K, V> {
fn index_mut<'s>(&'s mut self, index: &'a K) -> &'s mut V {
self.get_mut(index).unwrap()
}
}
impl<K: Message, V: Message> DefaultId for NSMutableDictionary<K, V> {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<K: fmt::Debug + Message, V: fmt::Debug + Message> fmt::Debug for NSMutableDictionary<K, V> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use crate::{
foundation::{NSNumber, NSString},
rc::{RcTestObject, ThreadTestData},
};
fn sample_dict() -> Id<NSMutableDictionary<NSNumber, NSObject>, Owned> {
NSMutableDictionary::from_keys_and_objects(
&[
&*NSNumber::new_i32(1),
&*NSNumber::new_i32(2),
&*NSNumber::new_i32(3),
],
vec![NSObject::new(), NSObject::new(), NSObject::new()],
)
}
#[test]
fn test_new() {
let dict = NSMutableDictionary::<NSString, NSObject>::new();
assert!(dict.is_empty());
}
#[test]
fn test_get_mut() {
let mut dict = sample_dict();
assert!(dict.get_mut(&NSNumber::new_i32(1)).is_some());
assert!(dict.get_mut(&NSNumber::new_i32(2)).is_some());
assert!(dict.get_mut(&NSNumber::new_i32(4)).is_none());
}
#[test]
fn test_values_mut() {
let mut dict = sample_dict();
let vec = dict.values_mut();
assert_eq!(vec.len(), 3);
}
#[test]
fn test_insert() {
let mut dict = NSMutableDictionary::new();
assert!(dict.insert(NSNumber::new_i32(1), NSObject::new()).is_none());
assert!(dict.insert(NSNumber::new_i32(2), NSObject::new()).is_none());
assert!(dict.insert(NSNumber::new_i32(3), NSObject::new()).is_none());
assert!(dict.insert(NSNumber::new_i32(1), NSObject::new()).is_some());
assert_eq!(dict.len(), 3);
}
#[test]
fn test_insert_retain_release() {
let mut dict = NSMutableDictionary::new();
dict.insert(NSNumber::new_i32(1), RcTestObject::new());
let mut expected = ThreadTestData::current();
let old = dict.insert(NSNumber::new_i32(1), RcTestObject::new());
expected.alloc += 1;
expected.init += 1;
expected.retain += 2;
expected.release += 2;
expected.assert_current();
drop(old);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_remove() {
let mut dict = sample_dict();
assert_eq!(dict.len(), 3);
assert!(dict.remove(&NSNumber::new_i32(1)).is_some());
assert!(dict.remove(&NSNumber::new_i32(2)).is_some());
assert!(dict.remove(&NSNumber::new_i32(1)).is_none());
assert!(dict.remove(&NSNumber::new_i32(4)).is_none());
assert_eq!(dict.len(), 1);
}
#[test]
fn test_clear() {
let mut dict = sample_dict();
assert_eq!(dict.len(), 3);
dict.clear();
assert!(dict.is_empty());
}
#[test]
fn test_remove_clear_release_dealloc() {
let mut dict = NSMutableDictionary::new();
for i in 0..4 {
dict.insert(NSNumber::new_i32(i), RcTestObject::new());
}
let mut expected = ThreadTestData::current();
let _obj = dict.remove(&NSNumber::new_i32(1));
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(dict.len(), 3);
let _obj = dict.remove(&NSNumber::new_i32(2));
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(dict.len(), 2);
dict.clear();
expected.release += 2;
expected.dealloc += 2;
expected.assert_current();
assert_eq!(dict.len(), 0);
}
#[test]
fn test_into_values_array() {
let dict = sample_dict();
let array = NSMutableDictionary::into_values_array(dict);
assert_eq!(array.len(), 3);
}
}

View file

@ -0,0 +1,368 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use super::set::with_objects;
use super::{NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying, NSObject, NSSet};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send_id};
__inner_extern_class!(
/// A growable unordered collection of unique objects.
///
/// See the documentation for [`NSSet`] and/or [Apple's
/// documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutableset?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableSet<T: Message, O: Ownership = Owned> {
p: PhantomData<*mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSMutableSet<T, O> {
#[inherits(NSObject)]
type Super = NSSet<T, O>;
}
);
// SAFETY: Same as NSSet<T, O>
unsafe impl<T: Message + Sync + Send> Sync for NSMutableSet<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSMutableSet<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSMutableSet<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSMutableSet<T, Owned> {}
extern_methods!(
unsafe impl<T: Message, O: Ownership> NSMutableSet<T, O> {
/// Creates an empty [`NSMutableSet`].
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set = NSMutableSet::<NSString>::new();
/// ```
pub fn new() -> Id<Self, Owned> {
// SAFETY:
// Same as `NSSet::new`, except mutable sets are always unique.
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates an [`NSMutableSet`] from a vector.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec();
/// let set = NSMutableSet::from_vec(strs);
/// ```
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, Owned> {
// SAFETY:
// We always return `Id<NSMutableSet<T, O>, Owned>` because mutable
// sets are always unique.
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
}
/// Clears the set, removing all values.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut set = NSMutableSet::new();
/// set.insert(NSString::from_str("one"));
/// set.clear();
/// assert!(set.is_empty());
/// ```
#[doc(alias = "removeAllObjects")]
#[sel(removeAllObjects)]
pub fn clear(&mut self);
/// Returns a [`Vec`] containing the set's elements, consuming the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSMutableString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = vec![
/// NSMutableString::from_str("one"),
/// NSMutableString::from_str("two"),
/// NSMutableString::from_str("three"),
/// ];
/// let set = NSMutableSet::from_vec(strs);
/// let vec = NSMutableSet::into_vec(set);
/// assert_eq!(vec.len(), 3);
/// ```
pub fn into_vec(set: Id<Self, Owned>) -> Vec<Id<T, O>> {
set.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
unsafe impl<T: Message> NSMutableSet<T, Shared> {
/// Creates an [`NSMutableSet`] from a slice.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSMutableSet::from_slice(&strs);
/// ```
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Owned> {
// SAFETY:
// Taking `&T` would not be sound, since the `&T` could come from
// an `Id<T, Owned>` that would now no longer be owned!
//
// We always return `Id<NSMutableSet<T, Shared>, Owned>` because
// the elements are shared and mutable sets are always unique.
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
}
// We're explicit about `T` being `PartialEq` for these methods because the
// set compares the input value with elements in the set
// For comparison: Rust's HashSet requires similar methods to be `Hash` + `Eq`
unsafe impl<T: Message + PartialEq, O: Ownership> NSMutableSet<T, O> {
#[sel(addObject:)]
fn add_object(&mut self, value: &T);
/// Adds a value to the set. Returns whether the value was
/// newly inserted.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut set = NSMutableSet::new();
///
/// assert_eq!(set.insert(NSString::from_str("one")), true);
/// assert_eq!(set.insert(NSString::from_str("one")), false);
/// assert_eq!(set.len(), 1);
/// ```
#[doc(alias = "addObject:")]
pub fn insert(&mut self, value: Id<T, O>) -> bool {
// SAFETY:
// We take `Id<T, O>` instead of `&T` because `&T` could be a
// reference to an owned object which would cause us to have a copy
// of an owned object in our set. By taking `Id<T, O>`, we force the
// caller to transfer ownership of the value to us, making it safe
// to insert the owned object into the set.
let contains_value = self.contains(&value);
self.add_object(&*value);
!contains_value
}
#[sel(removeObject:)]
fn remove_object(&mut self, value: &T);
/// Removes a value from the set. Returns whether the value was present
/// in the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut set = NSMutableSet::new();
///
/// set.insert(NSString::from_str("one"));
/// assert_eq!(set.remove(ns_string!("one")), true);
/// assert_eq!(set.remove(ns_string!("one")), false);
/// ```
#[doc(alias = "removeObject:")]
pub fn remove(&mut self, value: &T) -> bool {
let contains_value = self.contains(value);
self.remove_object(value);
contains_value
}
}
);
unsafe impl<T: Message> NSCopying for NSMutableSet<T, Shared> {
type Ownership = Shared;
type Output = NSSet<T, Shared>;
}
unsafe impl<T: Message> NSMutableCopying for NSMutableSet<T, Shared> {
type Output = NSMutableSet<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSMutableSet<T, Shared> {
type Owned = Id<NSMutableSet<T, Shared>, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSMutableSet<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSMutableSet<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSMutableSet<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message + PartialEq, O: Ownership> Extend<Id<T, O>> for NSMutableSet<T, O> {
fn extend<I: IntoIterator<Item = Id<T, O>>>(&mut self, iter: I) {
for item in iter {
self.insert(item);
}
}
}
impl<T: Message, O: Ownership> DefaultId for NSMutableSet<T, O> {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSMutableSet<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::foundation::{NSMutableString, NSString};
use crate::ns_string;
use crate::rc::{RcTestObject, ThreadTestData};
#[test]
fn test_insert() {
let mut set = NSMutableSet::new();
assert!(set.is_empty());
assert!(set.insert(NSString::from_str("one")));
assert!(!set.insert(NSString::from_str("one")));
assert!(set.insert(NSString::from_str("two")));
}
#[test]
fn test_remove() {
let strs = ["one", "two", "three"].map(NSString::from_str);
let mut set = NSMutableSet::from_slice(&strs);
assert!(set.remove(ns_string!("one")));
assert!(!set.remove(ns_string!("one")));
}
#[test]
fn test_clear() {
let strs = ["one", "two", "three"].map(NSString::from_str);
let mut set = NSMutableSet::from_slice(&strs);
assert_eq!(set.len(), 3);
set.clear();
assert!(set.is_empty());
}
#[test]
fn test_into_vec() {
let strs = vec![
NSMutableString::from_str("one"),
NSMutableString::from_str("two"),
NSMutableString::from_str("three"),
];
let set = NSMutableSet::from_vec(strs);
let mut vec = NSMutableSet::into_vec(set);
for str in vec.iter_mut() {
str.push_nsstring(ns_string!(" times zero is zero"));
}
assert_eq!(vec.len(), 3);
let suffix = ns_string!("zero");
assert!(vec.iter().all(|str| str.has_suffix(suffix)));
}
#[test]
fn test_extend() {
let mut set = NSMutableSet::new();
assert!(set.is_empty());
set.extend(["one", "two", "three"].map(NSString::from_str));
assert_eq!(set.len(), 3);
}
#[test]
fn test_mutable_copy() {
let set1 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
let mut set2 = set1.mutable_copy();
set2.insert(NSString::from_str("four"));
assert!(set1.is_subset(&set2));
assert_ne!(set1.mutable_copy(), set2);
}
#[test]
fn test_insert_retain_release() {
let mut set = NSMutableSet::new();
let obj1 = RcTestObject::new();
let obj2 = RcTestObject::new();
let mut expected = ThreadTestData::current();
set.insert(obj1);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(set.len(), 1);
assert_eq!(set.get_any(), set.get_any());
set.insert(obj2);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(set.len(), 2);
}
#[test]
fn test_clear_release_dealloc() {
let mut set = NSMutableSet::new();
for _ in 0..4 {
set.insert(RcTestObject::new());
}
let mut expected = ThreadTestData::current();
set.clear();
expected.release += 4;
expected.dealloc += 4;
expected.assert_current();
assert_eq!(set.len(), 0);
}
}

View file

@ -0,0 +1,232 @@
use core::cmp;
use core::fmt;
use core::ops::AddAssign;
use core::str;
use super::{NSCopying, NSMutableCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A dynamic plain-text Unicode string object.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutablestring?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableString;
unsafe impl ClassType for NSMutableString {
#[inherits(NSObject)]
type Super = NSString;
}
);
extern_methods!(
/// Creating mutable strings.
unsafe impl NSMutableString {
/// Construct an empty [`NSMutableString`].
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates a new [`NSMutableString`] by copying the given string slice.
#[doc(alias = "initWithBytes:length:encoding:")]
#[allow(clippy::should_implement_trait)] // Not really sure of a better name
pub fn from_str(string: &str) -> Id<Self, Owned> {
unsafe {
let obj = super::string::from_str(Self::class(), string);
Id::new(obj.cast()).unwrap()
}
}
/// Creates a new [`NSMutableString`] from the given [`NSString`].
#[doc(alias = "initWithString:")]
pub fn from_nsstring(string: &NSString) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string]
}
}
#[doc(alias = "initWithCapacity:")]
pub fn with_capacity(capacity: usize) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithCapacity: capacity]
}
}
}
/// Mutating strings.
unsafe impl NSMutableString {
/// Appends the given [`NSString`] onto the end of this.
#[doc(alias = "appendString:")]
// SAFETY: The string is not nil
#[sel(appendString:)]
pub fn push_nsstring(&mut self, nsstring: &NSString);
/// Replaces the entire string.
#[doc(alias = "setString:")]
// SAFETY: The string is not nil
#[sel(setString:)]
pub fn replace(&mut self, nsstring: &NSString);
// TODO:
// - deleteCharactersInRange:
// - replaceCharactersInRange:withString:
// - insertString:atIndex:
// Figure out how these work on character boundaries
}
);
impl DefaultId for NSMutableString {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSMutableString {
type Ownership = Shared;
type Output = NSString;
}
unsafe impl NSMutableCopying for NSMutableString {
type Output = NSMutableString;
}
impl alloc::borrow::ToOwned for NSMutableString {
type Owned = Id<NSMutableString, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
impl AddAssign<&NSString> for NSMutableString {
#[inline]
fn add_assign(&mut self, other: &NSString) {
self.push_nsstring(other)
}
}
impl PartialEq<NSString> for NSMutableString {
#[inline]
fn eq(&self, other: &NSString) -> bool {
PartialEq::eq(&**self, other)
}
}
impl PartialEq<NSMutableString> for NSString {
#[inline]
fn eq(&self, other: &NSMutableString) -> bool {
PartialEq::eq(self, &**other)
}
}
impl PartialOrd for NSMutableString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
PartialOrd::partial_cmp(&**self, &**other)
}
}
impl PartialOrd<NSString> for NSMutableString {
#[inline]
fn partial_cmp(&self, other: &NSString) -> Option<cmp::Ordering> {
PartialOrd::partial_cmp(&**self, other)
}
}
impl PartialOrd<NSMutableString> for NSString {
#[inline]
fn partial_cmp(&self, other: &NSMutableString) -> Option<cmp::Ordering> {
PartialOrd::partial_cmp(self, &**other)
}
}
impl Ord for NSMutableString {
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
Ord::cmp(&**self, &**other)
}
}
impl fmt::Write for NSMutableString {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let nsstring = NSString::from_str(s);
self.push_nsstring(&nsstring);
Ok(())
}
}
impl fmt::Display for NSMutableString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl fmt::Debug for NSMutableString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use super::*;
#[test]
fn display_debug() {
let s = NSMutableString::from_str("test\"123");
assert_eq!(format!("{}", s), "test\"123");
assert_eq!(format!("{:?}", s), r#""test\"123""#);
}
#[test]
fn test_from_nsstring() {
let s = NSString::from_str("abc");
let s = NSMutableString::from_nsstring(&s);
assert_eq!(&s.to_string(), "abc");
}
#[test]
fn test_push_nsstring() {
let mut s = NSMutableString::from_str("abc");
s.push_nsstring(&NSString::from_str("def"));
*s += &NSString::from_str("ghi");
assert_eq!(&s.to_string(), "abcdefghi");
}
#[test]
fn test_replace() {
let mut s = NSMutableString::from_str("abc");
s.replace(&NSString::from_str("def"));
assert_eq!(&s.to_string(), "def");
}
#[test]
fn test_with_capacity() {
let mut s = NSMutableString::with_capacity(3);
*s += &NSString::from_str("abc");
*s += &NSString::from_str("def");
assert_eq!(&s.to_string(), "abcdef");
}
#[test]
fn test_copy() {
let s1 = NSMutableString::from_str("abc");
let s2 = s1.copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s2).cast());
assert!(s2.is_kind_of::<NSString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
assert!(s3.is_kind_of::<NSMutableString>());
}
}

View file

@ -0,0 +1,414 @@
use core::cmp::Ordering;
use core::fmt;
use core::hash;
use core::panic::{RefUnwindSafe, UnwindSafe};
use std::os::raw::{
c_char, c_double, c_float, c_int, c_longlong, c_short, c_uchar, c_uint, c_ulonglong, c_ushort,
};
use super::{
CGFloat, NSComparisonResult, NSCopying, NSInteger, NSObject, NSString, NSUInteger, NSValue,
};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encoding};
extern_class!(
/// An object wrapper for primitive scalars.
///
/// This is the Objective-C equivalant of a Rust enum containing the
/// common scalar types `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`,
/// `u64`, `f32`, `f64` and the two C types `c_long` and `c_ulong`.
///
/// All accessor methods are safe, though they may return unexpected
/// results if the number was not created from said type. Consult [Apple's
/// documentation][apple-doc] for details.
///
/// Note that due to limitations in Objective-C type encodings, it is not
/// possible to distinguish between an `NSNumber` created from [`bool`],
/// and one created from an [`i8`]/[`u8`]. You should use the getter
/// methods that fit your use-case instead!
///
/// This does not implement [`Eq`] nor [`Ord`], since it may contain a
/// floating point value. Beware that the implementation of [`PartialEq`]
/// and [`PartialOrd`] does not properly handle NaNs either. Compare
/// [`NSNumber::encoding`] with [`Encoding::Float`] or
/// [`Encoding::Double`], and use [`NSNumber::as_f32`] or
/// [`NSNumber::as_f64`] to get the desired floating point value directly.
///
/// See [Apple's documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsnumber?language=objc
pub struct NSNumber;
unsafe impl ClassType for NSNumber {
#[inherits(NSObject)]
type Super = NSValue;
}
);
// SAFETY: `NSNumber` is just a wrapper around an integer/float/bool, and it
// is immutable.
unsafe impl Sync for NSNumber {}
unsafe impl Send for NSNumber {}
impl UnwindSafe for NSNumber {}
impl RefUnwindSafe for NSNumber {}
macro_rules! def_new_fn {
{$(
$(#[$($m:meta)*])*
($fn_name:ident($fn_inp:ty); $method_name:ident: $method_inp:ty),
)*} => {$(
$(#[$($m)*])*
pub fn $fn_name(val: $fn_inp) -> Id<Self, Shared> {
let val = val as $method_inp;
unsafe {
msg_send_id![Self::class(), $method_name: val]
}
}
)*}
}
/// Creation methods.
impl NSNumber {
def_new_fn! {
(new_bool(bool); numberWithBool: bool),
(new_i8(i8); numberWithChar: c_char),
(new_u8(u8); numberWithUnsignedChar: c_uchar),
(new_i16(i16); numberWithShort: c_short),
(new_u16(u16); numberWithUnsignedShort: c_ushort),
(new_i32(i32); numberWithInt: c_int),
(new_u32(u32); numberWithUnsignedInt: c_uint),
(new_i64(i64); numberWithLongLong: c_longlong),
(new_u64(u64); numberWithUnsignedLongLong: c_ulonglong),
(new_isize(isize); numberWithInteger: NSInteger),
(new_usize(usize); numberWithUnsignedInteger: NSUInteger),
(new_f32(f32); numberWithFloat: c_float),
(new_f64(f64); numberWithDouble: c_double),
}
#[inline]
pub fn new_cgfloat(val: CGFloat) -> Id<Self, Shared> {
#[cfg(target_pointer_width = "64")]
{
Self::new_f64(val)
}
#[cfg(not(target_pointer_width = "64"))]
{
Self::new_f32(val)
}
}
}
macro_rules! def_get_fn {
{$(
$(#[$($m:meta)*])*
($fn_name:ident -> $fn_ret:ty; $method_name:ident -> $method_ret:ty),
)*} => {$(
$(#[$($m)*])*
pub fn $fn_name(&self) -> $fn_ret {
let ret: $method_ret = unsafe { msg_send![self, $method_name] };
ret as $fn_ret
}
)*}
}
/// Getter methods.
impl NSNumber {
def_get_fn! {
(as_bool -> bool; boolValue -> bool),
(as_i8 -> i8; charValue -> c_char),
(as_u8 -> u8; unsignedCharValue -> c_uchar),
(as_i16 -> i16; shortValue -> c_short),
(as_u16 -> u16; unsignedShortValue -> c_ushort),
(as_i32 -> i32; intValue -> c_int),
(as_u32 -> u32; unsignedIntValue -> c_uint),
// TODO: Getter methods for `long` and `unsigned long`
(as_i64 -> i64; longLongValue -> c_longlong),
(as_u64 -> u64; unsignedLongLongValue -> c_ulonglong),
(as_isize -> isize; integerValue -> NSInteger),
(as_usize -> usize; unsignedIntegerValue -> NSUInteger),
(as_f32 -> f32; floatValue -> c_float),
(as_f64 -> f64; doubleValue -> c_double),
}
#[inline]
pub fn as_cgfloat(&self) -> CGFloat {
#[cfg(target_pointer_width = "64")]
{
self.as_f64()
}
#[cfg(not(target_pointer_width = "64"))]
{
self.as_f32()
}
}
/// The Objective-C encoding of this `NSNumber`.
///
/// This is guaranteed to return one of:
/// - [`Encoding::Char`]
/// - [`Encoding::UChar`]
/// - [`Encoding::Short`]
/// - [`Encoding::UShort`]
/// - [`Encoding::Int`]
/// - [`Encoding::UInt`]
/// - [`Encoding::Long`]
/// - [`Encoding::ULong`]
/// - [`Encoding::LongLong`]
/// - [`Encoding::ULongLong`]
/// - [`Encoding::Float`]
/// - [`Encoding::Double`]
///
///
/// # Examples
///
/// Convert an `NSNumber` to/from an enumeration describing the different
/// number properties.
///
/// ```
/// use objc2::Encoding;
/// use objc2::foundation::NSNumber;
/// use objc2::rc::{Id, Shared};
///
/// // Note: `bool` would convert to either `Signed` or `Unsigned`,
/// // depending on platform
/// #[derive(Copy, Clone)]
/// pub enum Number {
/// Signed(i64),
/// Unsigned(u64),
/// Floating(f64),
/// }
///
/// impl Number {
/// fn into_nsnumber(self) -> Id<NSNumber, Shared> {
/// match self {
/// Self::Signed(val) => NSNumber::new_i64(val),
/// Self::Unsigned(val) => NSNumber::new_u64(val),
/// Self::Floating(val) => NSNumber::new_f64(val),
/// }
/// }
/// }
///
/// impl From<&NSNumber> for Number {
/// fn from(n: &NSNumber) -> Self {
/// match n.encoding() {
/// Encoding::Char
/// | Encoding::Short
/// | Encoding::Int
/// | Encoding::Long
/// | Encoding::LongLong => Self::Signed(n.as_i64()),
/// Encoding::UChar
/// | Encoding::UShort
/// | Encoding::UInt
/// | Encoding::ULong
/// | Encoding::ULongLong => Self::Unsigned(n.as_u64()),
/// Encoding::Float
/// | Encoding::Double => Self::Floating(n.as_f64()),
/// _ => unreachable!(),
/// }
/// }
/// }
/// ```
pub fn encoding(&self) -> Encoding {
// Use NSValue::encoding
let enc = (**self)
.encoding()
.expect("NSNumber must have an encoding!");
// Guaranteed under "Subclassing Notes"
// <https://developer.apple.com/documentation/foundation/nsnumber?language=objc#1776615>
match enc {
"c" => Encoding::Char,
"C" => Encoding::UChar,
"s" => Encoding::Short,
"S" => Encoding::UShort,
"i" => Encoding::Int,
"I" => Encoding::UInt,
"l" => Encoding::Long,
"L" => Encoding::ULong,
"q" => Encoding::LongLong,
"Q" => Encoding::ULongLong,
"f" => Encoding::Float,
"d" => Encoding::Double,
_ => unreachable!("invalid encoding for NSNumber"),
}
}
}
extern_methods!(
unsafe impl NSNumber {
#[sel(compare:)]
fn compare(&self, other: &Self) -> NSComparisonResult;
#[sel(isEqualToNumber:)]
fn is_equal_to_number(&self, other: &Self) -> bool;
fn string(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, stringValue] }
}
}
);
unsafe impl NSCopying for NSNumber {
type Ownership = Shared;
type Output = NSNumber;
}
impl alloc::borrow::ToOwned for NSNumber {
type Owned = Id<NSNumber, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
/// Beware: This uses the Objective-C method "isEqualToNumber:", which has
/// different floating point NaN semantics than Rust!
impl PartialEq for NSNumber {
#[doc(alias = "isEqualToNumber:")]
fn eq(&self, other: &Self) -> bool {
// Use isEqualToNumber: instaed of isEqual: since it is faster
self.is_equal_to_number(other)
}
}
impl hash::Hash for NSNumber {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// Delegate to NSObject
(***self).hash(state)
}
}
/// Beware: This uses the Objective-C method "compare:", which has different
/// floating point NaN semantics than Rust!
impl PartialOrd for NSNumber {
#[doc(alias = "compare:")]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// Use Objective-C semantics for comparison
Some(self.compare(other).into())
}
}
impl fmt::Display for NSNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.string(), f)
}
}
impl fmt::Debug for NSNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Delegate to -[NSObject description]
// (happens to return the same as -[NSNumber stringValue])
fmt::Debug::fmt(&***self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn basic() {
let val = NSNumber::new_u32(13);
assert_eq!(val.as_u32(), 13);
}
#[test]
fn roundtrip() {
assert!(NSNumber::new_bool(true).as_bool());
assert!(!NSNumber::new_bool(false).as_bool());
fn assert_roundtrip_signed(val: i64) {
assert_eq!(NSNumber::new_i8(val as i8).as_i8(), val as i8);
assert_eq!(NSNumber::new_i16(val as i16).as_i16(), val as i16);
assert_eq!(NSNumber::new_i32(val as i32).as_i32(), val as i32);
assert_eq!(NSNumber::new_i64(val).as_i64(), val);
assert_eq!(NSNumber::new_isize(val as isize).as_isize(), val as isize);
}
assert_roundtrip_signed(i64::MIN);
assert_roundtrip_signed(i32::MIN as i64);
assert_roundtrip_signed(i16::MIN as i64);
assert_roundtrip_signed(i8::MIN as i64);
assert_roundtrip_signed(-1);
assert_roundtrip_signed(0);
assert_roundtrip_signed(1);
assert_roundtrip_signed(i8::MAX as i64);
assert_roundtrip_signed(i16::MAX as i64);
assert_roundtrip_signed(i32::MAX as i64);
assert_roundtrip_signed(i64::MAX);
fn assert_roundtrip_unsigned(val: u64) {
assert_eq!(NSNumber::new_u8(val as u8).as_u8(), val as u8);
assert_eq!(NSNumber::new_u16(val as u16).as_u16(), val as u16);
assert_eq!(NSNumber::new_u32(val as u32).as_u32(), val as u32);
assert_eq!(NSNumber::new_u64(val).as_u64(), val);
assert_eq!(NSNumber::new_usize(val as usize).as_usize(), val as usize);
}
assert_roundtrip_unsigned(0);
assert_roundtrip_unsigned(1);
assert_roundtrip_unsigned(u8::MAX as u64);
assert_roundtrip_unsigned(u16::MAX as u64);
assert_roundtrip_unsigned(u32::MAX as u64);
assert_roundtrip_unsigned(u64::MAX);
fn assert_roundtrip_float(val: f64) {
assert_eq!(NSNumber::new_f32(val as f32).as_f32(), val as f32);
assert_eq!(NSNumber::new_f64(val).as_f64(), val);
}
assert_roundtrip_float(0.0);
assert_roundtrip_float(-1.0);
assert_roundtrip_float(1.0);
assert_roundtrip_float(f64::INFINITY);
assert_roundtrip_float(-f64::INFINITY);
assert_roundtrip_float(f64::MAX);
assert_roundtrip_float(f64::MIN);
assert_roundtrip_float(f64::MIN_POSITIVE);
assert!(NSNumber::new_f32(f32::NAN).as_f32().is_nan());
assert!(NSNumber::new_f64(f64::NAN).as_f64().is_nan());
assert!(NSNumber::new_f32(-f32::NAN).as_f32().is_nan());
assert!(NSNumber::new_f64(-f64::NAN).as_f64().is_nan());
}
#[test]
fn cast_between_types() {
assert_eq!(NSNumber::new_bool(true).as_i8(), 1);
assert_eq!(NSNumber::new_i32(i32::MAX).as_u32(), i32::MAX as u32);
assert_eq!(NSNumber::new_f32(1.0).as_u32(), 1);
assert_eq!(NSNumber::new_f32(1.0).as_u32(), 1);
}
#[test]
fn equality() {
let val1 = NSNumber::new_u32(123);
let val2 = NSNumber::new_u32(123);
let val3 = NSNumber::new_u8(123);
assert_eq!(val1, val1);
assert_eq!(val1, val2);
assert_eq!(val1, val3);
let val4 = NSNumber::new_u32(456);
assert_ne!(val1, val4);
}
#[test]
fn display_debug() {
fn assert_display_debug<T: fmt::Debug + fmt::Display>(val: T, expected: &str) {
// The two impls for these happen to be the same
assert_eq!(format!("{}", val), expected);
assert_eq!(format!("{:?}", val), expected);
}
assert_display_debug(NSNumber::new_u8(171), "171");
assert_display_debug(NSNumber::new_i8(-12), "-12");
assert_display_debug(NSNumber::new_u32(0xdeadbeef), "3735928559");
assert_display_debug(NSNumber::new_f32(1.1), "1.1");
assert_display_debug(NSNumber::new_f32(1.0), "1");
assert_display_debug(NSNumber::new_bool(true), "1");
assert_display_debug(NSNumber::new_bool(false), "0");
}
}

View file

@ -0,0 +1,205 @@
use core::fmt;
use core::hash;
use super::NSString;
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::runtime::{Class, Object};
use crate::{ClassType, __inner_extern_class, class, extern_methods, msg_send_id};
__inner_extern_class! {
@__inner
pub struct NSObject () {}
unsafe impl () for NSObject {
INHERITS = [Object];
}
}
unsafe impl ClassType for NSObject {
type Super = Object;
const NAME: &'static str = "NSObject";
#[inline]
fn class() -> &'static Class {
class!(NSObject)
}
fn as_super(&self) -> &Self::Super {
&self.__inner
}
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.__inner
}
}
extern_methods!(
unsafe impl NSObject {
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(isKindOfClass:)]
fn is_kind_of_inner(&self, cls: &Class) -> bool;
#[sel(isEqual:)]
fn is_equal(&self, other: &Self) -> bool;
#[sel(hash)]
fn hash_code(&self) -> usize;
/// Check if the object is an instance of the class, or one of it's
/// subclasses.
///
/// See [Apple's documentation][apple-doc] for more details on what you
/// may (and what you may not) do with this information.
///
/// [apple-doc]: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418511-iskindofclass
#[doc(alias = "isKindOfClass:")]
pub fn is_kind_of<T: ClassType>(&self) -> bool {
self.is_kind_of_inner(T::class())
}
// Note: We don't provide a method to convert `NSObject` to `T` based on
// `is_kind_of`, since that is not possible to do in general!
//
// For example, something may have a return type of `NSString`, while
// behind the scenes they really return `NSMutableString` and expect it to
// not be modified.
}
);
/// Objective-C equality has approximately the same semantics as Rust
/// equality (although less aptly specified).
///
/// At the very least, equality is _expected_ to be symmetric and
/// transitive, and that's about the best we can do.
///
/// See also <https://nshipster.com/equality/>
impl PartialEq for NSObject {
#[inline]
#[doc(alias = "isEqual:")]
fn eq(&self, other: &Self) -> bool {
self.is_equal(other)
}
}
/// Most types' equality is reflexive.
impl Eq for NSObject {}
/// Hashing in Objective-C has the exact same requirement as in Rust:
///
/// > If two objects are equal (as determined by the isEqual: method),
/// > they must have the same hash value.
///
/// See <https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash>
impl hash::Hash for NSObject {
#[inline]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.hash_code().hash(state);
}
}
impl fmt::Debug for NSObject {
#[doc(alias = "description")]
#[doc(alias = "debugDescription")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Get description
let description: Option<Id<NSString, Shared>> = unsafe { msg_send_id![self, description] };
match description {
// Attempt to format with description
Some(description) => fmt::Display::fmt(&description, f),
// If description was `NULL`, use `Object`'s `Debug` impl instead
None => {
let obj: &Object = self;
fmt::Debug::fmt(obj, f)
}
}
}
}
impl DefaultId for NSObject {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn test_deref() {
let mut obj: Id<NSObject, Owned> = NSObject::new();
let _: &NSObject = &obj;
let _: &mut NSObject = &mut obj;
let _: &Object = &obj;
let _: &mut Object = &mut obj;
}
#[test]
fn test_as_ref_borrow() {
use core::borrow::{Borrow, BorrowMut};
fn impls_as_ref<T: AsRef<U> + Borrow<U> + ?Sized, U: ?Sized>(_: &T) {}
fn impls_as_mut<T: AsMut<U> + BorrowMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
let mut obj = NSObject::new();
impls_as_ref::<Id<NSObject, Owned>, NSObject>(&obj);
impls_as_mut::<Id<NSObject, Owned>, NSObject>(&mut obj);
impls_as_ref::<NSObject, NSObject>(&obj);
impls_as_mut::<NSObject, NSObject>(&mut obj);
impls_as_ref::<NSObject, Object>(&obj);
impls_as_mut::<NSObject, Object>(&mut obj);
}
#[test]
fn test_equality() {
let obj1 = NSObject::new();
assert_eq!(obj1, obj1);
let obj2 = NSObject::new();
assert_ne!(obj1, obj2);
}
#[test]
fn test_hash() {
use core::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
let obj1 = NSObject::new();
let mut hashstate1 = DefaultHasher::new();
let mut hashstate2 = DefaultHasher::new();
obj1.hash(&mut hashstate1);
obj1.hash(&mut hashstate2);
assert_eq!(hashstate1.finish(), hashstate2.finish());
let obj2 = NSObject::new();
let mut hashstate2 = DefaultHasher::new();
obj2.hash(&mut hashstate2);
assert_ne!(hashstate1.finish(), hashstate2.finish());
}
#[test]
fn test_debug() {
let obj = NSObject::new();
let expected = format!("<NSObject: {:p}>", &*obj);
assert_eq!(format!("{:?}", obj), expected);
}
#[test]
fn test_is_kind_of() {
let obj = NSObject::new();
assert!(obj.is_kind_of::<NSObject>());
assert!(!obj.is_kind_of::<NSString>());
}
}

View file

@ -0,0 +1,65 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSObject, NSString};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A collection of information about the current process.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsprocessinfo?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSProcessInfo;
unsafe impl ClassType for NSProcessInfo {
type Super = NSObject;
}
);
// SAFETY: The documentation explicitly states:
// > NSProcessInfo is thread-safe in macOS 10.7 and later.
unsafe impl Send for NSProcessInfo {}
unsafe impl Sync for NSProcessInfo {}
impl UnwindSafe for NSProcessInfo {}
impl RefUnwindSafe for NSProcessInfo {}
extern_methods!(
unsafe impl NSProcessInfo {
pub fn process_info() -> Id<NSProcessInfo, Shared> {
unsafe { msg_send_id![Self::class(), processInfo] }
}
pub fn process_name(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, processName] }
}
// TODO: This contains a lot more important functionality!
}
);
impl fmt::Debug for NSProcessInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NSProcessInfo")
.field("process_name", &self.process_name())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn test_debug() {
let info = NSProcessInfo::process_info();
let expected = format!(
"NSProcessInfo {{ process_name: {:?}, .. }}",
info.process_name()
);
assert_eq!(format!("{:?}", info), expected);
}
}

View file

@ -0,0 +1,183 @@
use core::ops::Range;
use super::NSUInteger;
use crate::{Encode, Encoding, RefEncode};
/// TODO.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrange?language=objc).
#[repr(C)]
// PartialEq is same as NSEqualRanges
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NSRange {
/// The lower bound of the range (inclusive).
pub location: NSUInteger,
/// The number of items in the range, starting from `location`.
pub length: NSUInteger,
}
impl NSRange {
/// Create a new range with the given values.
///
/// # Examples
///
/// ```
/// use objc2::foundation::NSRange;
/// assert_eq!(NSRange::new(3, 2), NSRange::from(3..5));
/// ```
#[inline]
#[doc(alias = "NSMakeRange")]
pub const fn new(location: usize, length: usize) -> Self {
// Equivalent to NSMakeRange
Self { location, length }
}
/// Returns `true` if the range contains no items.
///
/// # Examples
///
/// ```
/// use objc2::foundation::NSRange;
///
/// assert!(!NSRange::from(3..5).is_empty());
/// assert!( NSRange::from(3..3).is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.length == 0
}
/// Returns `true` if the index is within the range.
///
/// # Examples
///
/// ```
/// use objc2::foundation::NSRange;
///
/// assert!(!NSRange::from(3..5).contains(2));
/// assert!( NSRange::from(3..5).contains(3));
/// assert!( NSRange::from(3..5).contains(4));
/// assert!(!NSRange::from(3..5).contains(5));
///
/// assert!(!NSRange::from(3..3).contains(3));
/// ```
#[inline]
#[doc(alias = "NSLocationInRange")]
pub fn contains(&self, index: usize) -> bool {
// Same as NSLocationInRange
if let Some(len) = index.checked_sub(self.location) {
len < self.length
} else {
// index < self.location
false
}
}
/// Returns the upper bound of the range (exclusive).
#[inline]
#[doc(alias = "NSMaxRange")]
pub fn end(&self) -> usize {
self.location
.checked_add(self.length)
.expect("NSRange too large")
}
// TODO: https://developer.apple.com/documentation/foundation/1408420-nsrangefromstring
// TODO: NSUnionRange
// TODO: NSIntersectionRange
}
// Sadly, we can't do this:
// impl RangeBounds<usize> for NSRange {
// fn start_bound(&self) -> Bound<&usize> {
// Bound::Included(&self.location)
// }
// fn end_bound(&self) -> Bound<&usize> {
// Bound::Excluded(&(self.location + self.length))
// }
// }
impl From<Range<usize>> for NSRange {
fn from(range: Range<usize>) -> Self {
let length = range
.end
.checked_sub(range.start)
.expect("Range end < start");
Self {
location: range.start,
length,
}
}
}
impl From<NSRange> for Range<usize> {
#[inline]
fn from(nsrange: NSRange) -> Self {
Self {
start: nsrange.location,
end: nsrange.end(),
}
}
}
unsafe impl Encode for NSRange {
const ENCODING: Encoding = Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]);
}
unsafe impl RefEncode for NSRange {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_range() {
let cases: &[(Range<usize>, NSRange)] = &[
(0..0, NSRange::new(0, 0)),
(0..10, NSRange::new(0, 10)),
(10..10, NSRange::new(10, 0)),
(10..20, NSRange::new(10, 10)),
];
for (range, expected) in cases {
assert_eq!(NSRange::from(range.clone()), *expected);
}
}
#[test]
#[should_panic = "Range end < start"]
#[allow(clippy::reversed_empty_ranges)]
fn test_from_range_inverted() {
let _ = NSRange::from(10..0);
}
#[test]
fn test_contains() {
let range = NSRange::from(10..20);
assert!(!range.contains(0));
assert!(!range.contains(9));
assert!(range.contains(10));
assert!(range.contains(11));
assert!(!range.contains(20));
assert!(!range.contains(21));
}
#[test]
fn test_end() {
let range = NSRange::from(10..20);
assert!(!range.contains(0));
assert!(!range.contains(9));
assert!(range.contains(10));
assert!(range.contains(11));
assert!(!range.contains(20));
assert!(!range.contains(21));
}
#[test]
#[should_panic = "NSRange too large"]
fn test_end_large() {
let _ = NSRange::new(usize::MAX, usize::MAX).end();
}
}

View file

@ -0,0 +1,680 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableCopying,
NSMutableSet, NSObject,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::runtime::Class;
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
/// An immutable unordered collection of unique objects.
///
/// See [Apple's documentation][apple-doc].
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsset?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSSet<T: Message, O: Ownership = Shared> {
item: PhantomData<Id<T, O>>,
notunwindsafe: PhantomData<&'static mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSSet<T, O> {
type Super = NSObject;
}
);
// SAFETY: Same as NSArray<T, O>
unsafe impl<T: Message + Sync + Send> Sync for NSSet<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSSet<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSSet<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSSet<T, Owned> {}
// SAFETY: Same as NSArray<T, O>
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSSet<T, O> {}
impl<T: Message + RefUnwindSafe> UnwindSafe for NSSet<T, Shared> {}
impl<T: Message + UnwindSafe> UnwindSafe for NSSet<T, Owned> {}
#[track_caller]
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
cls: &Class,
objects: &[&T],
) -> Id<R, O> {
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithObjects: objects.as_ptr(),
count: objects.len()
]
}
}
extern_methods!(
unsafe impl<T: Message, O: Ownership> NSSet<T, O> {
/// Creates an empty [`NSSet`].
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set = NSSet::<NSString>::new();
/// ```
pub fn new() -> Id<Self, Shared> {
// SAFETY:
// - `new` may not create a new object, but instead return a shared
// instance. We remedy this by returning `Id<Self, Shared>`.
// - `O` don't actually matter here! E.g. `NSSet<T, Owned>` is
// perfectly legal, since the set doesn't have any elements, and
// hence the notion of ownership over the elements is void.
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates an [`NSSet`] from a vector.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec();
/// let set = NSSet::from_vec(strs);
/// ```
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
// SAFETY:
// When we know that we have ownership over the variables, we also
// know that there cannot be another set in existence with the same
// objects, so `Id<NSSet<T, Owned>, Owned>` is safe to return when
// we receive `Vec<Id<T, Owned>>`.
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
}
/// Returns the number of elements in the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// assert_eq!(set.len(), 3);
/// ```
#[doc(alias = "count")]
#[sel(count)]
pub fn len(&self) -> usize;
/// Returns `true` if the set contains no elements.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set = NSSet::<NSString>::new();
/// assert!(set.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a reference to one of the objects in the set, or `None` if
/// the set is empty.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// let any = set.get_any().unwrap();
/// assert!(any == &*strs[0] || any == &*strs[1] || any == &*strs[2]);
/// ```
#[doc(alias = "anyObject")]
#[sel(anyObject)]
pub fn get_any(&self) -> Option<&T>;
/// An iterator visiting all elements in arbitrary order.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// for s in set.iter() {
/// println!("{s}");
/// }
/// ```
#[doc(alias = "objectEnumerator")]
pub fn iter(&self) -> NSEnumerator<'_, T> {
unsafe {
let result = msg_send![self, objectEnumerator];
NSEnumerator::from_ptr(result)
}
}
/// Returns a [`Vec`] containing the set's elements, consuming the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableString, NSSet};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = vec![
/// NSMutableString::from_str("one"),
/// NSMutableString::from_str("two"),
/// NSMutableString::from_str("three"),
/// ];
/// let set = NSSet::from_vec(strs);
/// let vec = NSSet::into_vec(set);
/// assert_eq!(vec.len(), 3);
/// ```
pub fn into_vec(set: Id<Self, O>) -> Vec<Id<T, O>> {
set.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
unsafe impl<T: Message> NSSet<T, Shared> {
/// Creates an [`NSSet`] from a slice.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// ```
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
// SAFETY:
// Taking `&T` would not be sound, since the `&T` could come from
// an `Id<T, Owned>` that would now no longer be owned!
//
// We always return `Id<NSSet<T, Shared>, Shared>` because the
// elements are shared.
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
/// Returns an [`NSArray`] containing the set's elements, or an empty
/// array if the set is empty.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSNumber, NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let nums = [1, 2, 3];
/// let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
///
/// assert_eq!(set.to_array().len(), 3);
/// assert!(set.to_array().iter().all(|i| nums.contains(&i.as_i32())));
/// ```
#[doc(alias = "allObjects")]
pub fn to_array(&self) -> Id<NSArray<T, Shared>, Shared> {
// SAFETY:
// We only define this method for sets with shared elements
// because we can't return copies of owned elements.
unsafe { msg_send_id![self, allObjects] }
}
}
// We're explicit about `T` being `PartialEq` for these methods because the
// set compares the input value(s) with elements in the set
// For comparison: Rust's HashSet requires similar methods to be `Hash` + `Eq`
unsafe impl<T: Message + PartialEq, O: Ownership> NSSet<T, O> {
/// Returns `true` if the set contains a value.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// assert!(set.contains(ns_string!("one")));
/// ```
#[doc(alias = "containsObject:")]
#[sel(containsObject:)]
pub fn contains(&self, value: &T) -> bool;
/// Returns a reference to the value in the set, if any, that is equal
/// to the given value.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// assert_eq!(set.get(ns_string!("one")), Some(&*strs[0]));
/// assert_eq!(set.get(ns_string!("four")), None);
/// ```
#[doc(alias = "member:")]
#[sel(member:)]
pub fn get(&self, value: &T) -> Option<&T>;
/// Returns `true` if the set is a subset of another, i.e., `other`
/// contains at least all the values in `self`.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
///
/// assert!(set1.is_subset(&set2));
/// assert!(!set2.is_subset(&set1));
/// ```
#[doc(alias = "isSubsetOfSet:")]
#[sel(isSubsetOfSet:)]
pub fn is_subset(&self, other: &NSSet<T, O>) -> bool;
/// Returns `true` if the set is a superset of another, i.e., `self`
/// contains at least all the values in `other`.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
///
/// assert!(!set1.is_superset(&set2));
/// assert!(set2.is_superset(&set1));
/// ```
pub fn is_superset(&self, other: &NSSet<T, O>) -> bool {
other.is_subset(self)
}
#[sel(intersectsSet:)]
fn intersects_set(&self, other: &NSSet<T, O>) -> bool;
/// Returns `true` if `self` has no elements in common with `other`.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
/// let set3 = NSSet::from_slice(&["four", "five", "six"].map(NSString::from_str));
///
/// assert!(!set1.is_disjoint(&set2));
/// assert!(set1.is_disjoint(&set3));
/// assert!(set2.is_disjoint(&set3));
/// ```
pub fn is_disjoint(&self, other: &NSSet<T, O>) -> bool {
!self.intersects_set(other)
}
}
);
unsafe impl<T: Message> NSCopying for NSSet<T, Shared> {
type Ownership = Shared;
type Output = NSSet<T, Shared>;
}
unsafe impl<T: Message> NSMutableCopying for NSSet<T, Shared> {
type Output = NSMutableSet<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSSet<T, Shared> {
type Owned = Id<NSSet<T, Shared>, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSSet<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSSet<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSSet<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message, O: Ownership> DefaultId for NSSet<T, O> {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSSet<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.iter_fast()).finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec;
use super::*;
use crate::foundation::{NSMutableString, NSNumber, NSString};
use crate::ns_string;
use crate::rc::{RcTestObject, ThreadTestData};
#[test]
fn test_new() {
let set = NSSet::<NSString>::new();
assert!(set.is_empty());
}
#[test]
fn test_from_vec() {
let set = NSSet::<NSString>::from_vec(Vec::new());
assert!(set.is_empty());
let strs = ["one", "two", "three"].map(NSString::from_str);
let set = NSSet::from_vec(strs.to_vec());
assert!(strs.into_iter().all(|s| set.contains(&s)));
let nums = [1, 2, 3].map(NSNumber::new_i32);
let set = NSSet::from_vec(nums.to_vec());
assert!(nums.into_iter().all(|n| set.contains(&n)));
}
#[test]
fn test_from_slice() {
let set = NSSet::<NSString>::from_slice(&[]);
assert!(set.is_empty());
let strs = ["one", "two", "three"].map(NSString::from_str);
let set = NSSet::from_slice(&strs);
assert!(strs.into_iter().all(|s| set.contains(&s)));
let nums = [1, 2, 3].map(NSNumber::new_i32);
let set = NSSet::from_slice(&nums);
assert!(nums.into_iter().all(|n| set.contains(&n)));
}
#[test]
fn test_len() {
let set = NSSet::<NSString>::new();
assert!(set.is_empty());
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
assert_eq!(set.len(), 2);
let set = NSSet::from_vec(vec![NSObject::new(), NSObject::new(), NSObject::new()]);
assert_eq!(set.len(), 3);
}
#[test]
fn test_get() {
let set = NSSet::<NSString>::new();
assert!(set.get(ns_string!("one")).is_none());
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
assert!(set.get(ns_string!("two")).is_some());
assert!(set.get(ns_string!("three")).is_none());
}
#[test]
fn test_get_return_lifetime() {
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
let res = {
let value = NSString::from_str("one");
set.get(&value)
};
assert_eq!(res, Some(ns_string!("one")));
}
#[test]
fn test_get_any() {
let set = NSSet::<NSString>::new();
assert!(set.get_any().is_none());
let strs = ["one", "two", "three"].map(NSString::from_str);
let set = NSSet::from_slice(&strs);
let any = set.get_any().unwrap();
assert!(any == &*strs[0] || any == &*strs[1] || any == &*strs[2]);
}
#[test]
fn test_contains() {
let set = NSSet::<NSString>::new();
assert!(!set.contains(ns_string!("one")));
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
assert!(set.contains(ns_string!("one")));
assert!(!set.contains(ns_string!("three")));
}
#[test]
fn test_is_subset() {
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
assert!(set1.is_subset(&set2));
assert!(!set2.is_subset(&set1));
}
#[test]
fn test_is_superset() {
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
assert!(!set1.is_superset(&set2));
assert!(set2.is_superset(&set1));
}
#[test]
fn test_is_disjoint() {
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
let set3 = NSSet::from_slice(&["four", "five", "six"].map(NSString::from_str));
assert!(!set1.is_disjoint(&set2));
assert!(set1.is_disjoint(&set3));
assert!(set2.is_disjoint(&set3));
}
#[test]
fn test_to_array() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert_eq!(set.to_array().len(), 3);
assert!(set.to_array().iter().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_iter() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert_eq!(set.iter().count(), 3);
assert!(set.iter().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_iter_fast() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert_eq!(set.iter_fast().count(), 3);
assert!(set.iter_fast().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_into_iter() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert!(set.into_iter().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_into_vec() {
let strs = vec![
NSMutableString::from_str("one"),
NSMutableString::from_str("two"),
NSMutableString::from_str("three"),
];
let set = NSSet::from_vec(strs);
let mut vec = NSSet::into_vec(set);
for str in vec.iter_mut() {
str.push_nsstring(ns_string!(" times zero is zero"));
}
assert_eq!(vec.len(), 3);
let suffix = ns_string!("zero");
assert!(vec.iter().all(|str| str.has_suffix(suffix)));
}
#[test]
fn test_equality() {
let set1 = NSSet::<NSString>::new();
let set2 = NSSet::<NSString>::new();
assert_eq!(set1, set2);
}
#[test]
fn test_copy() {
let set1 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
let set2 = set1.copy();
assert_eq!(set1, set2);
}
#[test]
fn test_debug() {
let set = NSSet::<NSString>::new();
assert_eq!(format!("{:?}", set), "{}");
let set = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
assert!(matches!(
format!("{:?}", set).as_str(),
"{\"one\", \"two\"}" | "{\"two\", \"one\"}"
));
}
#[test]
fn test_retains_stored() {
let obj = Id::into_shared(RcTestObject::new());
let mut expected = ThreadTestData::current();
let input = [obj.clone(), obj.clone()];
expected.retain += 2;
expected.assert_current();
let set = NSSet::from_slice(&input);
expected.retain += 1;
expected.assert_current();
let _obj = set.get_any().unwrap();
expected.assert_current();
drop(set);
expected.release += 1;
expected.assert_current();
let set = NSSet::from_vec(Vec::from(input));
expected.retain += 1;
expected.release += 2;
expected.assert_current();
drop(set);
expected.release += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_nscopying_uses_retain() {
let obj = Id::into_shared(RcTestObject::new());
let set = NSSet::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let _copy = set.copy();
expected.assert_current();
let _copy = set.mutable_copy();
expected.retain += 1;
expected.assert_current();
}
#[test]
#[cfg_attr(
feature = "apple",
ignore = "this works differently on different framework versions"
)]
fn test_iter_no_retain() {
let obj = Id::into_shared(RcTestObject::new());
let set = NSSet::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let iter = set.iter();
expected.retain += 0;
expected.assert_current();
assert_eq!(iter.count(), 1);
expected.autorelease += 0;
expected.assert_current();
let iter = set.iter_fast();
assert_eq!(iter.count(), 1);
expected.assert_current();
}
}

View file

@ -0,0 +1,527 @@
use alloc::borrow::ToOwned;
use core::cmp;
use core::ffi::c_void;
use core::fmt;
use core::panic::RefUnwindSafe;
use core::panic::UnwindSafe;
use core::ptr::NonNull;
use core::slice;
use core::str;
use std::os::raw::c_char;
use super::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NSObject};
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};
#[cfg(feature = "apple")]
const UTF8_ENCODING: usize = 4;
#[cfg(feature = "gnustep-1-7")]
const UTF8_ENCODING: i32 = 4;
extern_class!(
/// An immutable, plain-text Unicode string object.
///
/// Can be created statically using the [`ns_string!`] macro.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring?language=objc).
///
/// [`ns_string!`]: crate::ns_string
#[derive(PartialEq, Eq, Hash)]
pub struct NSString;
// TODO: Use isEqualToString: for comparison (instead of just isEqual:)
// The former is more performant
// TODO: Check if performance of NSSelectorFromString is worthwhile
unsafe impl ClassType for NSString {
type Super = NSObject;
}
);
// SAFETY: `NSString` is immutable and `NSMutableString` can only be mutated
// from `&mut` methods.
unsafe impl Sync for NSString {}
unsafe impl Send for NSString {}
// Even if an exception occurs inside a string method, the state of the string
// (should) still be perfectly safe to access.
impl UnwindSafe for NSString {}
impl RefUnwindSafe for NSString {}
extern_methods!(
unsafe impl NSString {
/// Construct an empty NSString.
pub fn new() -> Id<Self, Shared> {
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<Self, Shared> {
// 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<Self, Shared> {
// 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:")]
pub fn len(&self) -> usize {
unsafe { msg_send![self, lengthOfBytesUsingEncoding: UTF8_ENCODING] }
}
/// The number of UTF-16 code units in the string.
///
/// See also [`NSString::len`].
#[doc(alias = "length")]
#[sel(length)]
pub fn len_utf16(&self) -> usize;
pub fn is_empty(&self) -> bool {
// TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for
// other reasons, so this is not really correct!
self.len() == 0
}
/// Get the [`str`](`prim@str`) representation of this string if it can be
/// done efficiently.
///
/// Returns [`None`] if the internal storage does not allow this to be
/// done efficiently. Use [`NSString::as_str`] or `NSString::to_string`
/// if performance is not an issue.
#[doc(alias = "CFStringGetCStringPtr")]
#[allow(unused)]
// TODO: Finish this
fn as_str_wip(&self) -> Option<&str> {
type CFStringEncoding = u32;
#[allow(non_upper_case_globals)]
// https://developer.apple.com/documentation/corefoundation/cfstringbuiltinencodings/kcfstringencodingutf8?language=objc
const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
extern "C" {
// https://developer.apple.com/documentation/corefoundation/1542133-cfstringgetcstringptr?language=objc
fn CFStringGetCStringPtr(s: &NSString, encoding: CFStringEncoding)
-> *const c_char;
}
let bytes = unsafe { CFStringGetCStringPtr(self, kCFStringEncodingUTF8) };
NonNull::new(bytes as *mut u8).map(|bytes| {
let len = self.len();
let bytes: &[u8] = unsafe { slice::from_raw_parts(bytes.as_ptr(), len) };
str::from_utf8(bytes).unwrap()
})
}
/// Get an [UTF-16] string slice if it can be done efficiently.
///
/// Returns [`None`] if the internal storage of `self` does not allow this
/// to be returned efficiently.
///
/// See [`as_str`](Self::as_str) for the UTF-8 equivalent.
///
/// [UTF-16]: https://en.wikipedia.org/wiki/UTF-16
#[allow(unused)]
// TODO: Finish this
fn as_utf16(&self) -> Option<&[u16]> {
extern "C" {
// https://developer.apple.com/documentation/corefoundation/1542939-cfstringgetcharactersptr?language=objc
fn CFStringGetCharactersPtr(s: &NSString) -> *const u16;
}
let ptr = unsafe { CFStringGetCharactersPtr(self) };
NonNull::new(ptr as *mut u16)
.map(|ptr| unsafe { slice::from_raw_parts(ptr.as_ptr(), self.len_utf16()) })
}
/// Get the [`str`](`prim@str`) representation of this.
///
/// TODO: Further explain this.
#[doc(alias = "UTF8String")]
pub fn as_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: &'p AutoreleasePool) -> &'r str {
// NOTE: Please keep up to date with `objc2::exception`!
// This is necessary until `auto` types stabilizes.
pool.__verify_is_inner();
// The documentation on `UTF8String` is a bit sparse, but with
// educated guesses and testing I've determined that NSString stores
// a pointer to the string data, sometimes with an UTF-8 encoding,
// (usual for ascii data), sometimes in other encodings (UTF-16?).
//
// `UTF8String` then checks the internal encoding:
// - If the data is UTF-8 encoded, it returns the internal pointer.
// - If the data is in another encoding, it creates a new allocation,
// writes the UTF-8 representation of the string into it,
// autoreleases the allocation and returns a pointer to it.
//
// So the lifetime of the returned pointer is either the same as the
// NSString OR the lifetime of the innermost @autoreleasepool.
//
// https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc
let bytes: *const c_char = unsafe { msg_send![self, UTF8String] };
let bytes: *const u8 = bytes.cast();
let len = self.len();
// SAFETY:
// The held AutoreleasePool is the innermost, and the reference is
// constrained both by the pool and the NSString.
//
// `len` is the length of the string in the UTF-8 encoding.
//
// `bytes` is a null-terminated C string (with length = len + 1), so
// it is never a NULL pointer.
let bytes: &'r [u8] = unsafe { slice::from_raw_parts(bytes, len) };
// TODO: Always UTF-8, so should we use `from_utf8_unchecked`?
str::from_utf8(bytes).unwrap()
// NOTE: Please keep up to date with `objc2::exception`!
}
// TODO: Allow usecases where the NUL byte from `UTF8String` is kept?
/// Creates an immutable `NSString` by copying the given string slice.
///
/// Prefer using the [`ns_string!`] macro when possible.
///
/// [`ns_string!`]: crate::ns_string
#[doc(alias = "initWithBytes")]
#[doc(alias = "initWithBytes:length:encoding:")]
#[allow(clippy::should_implement_trait)] // Not really sure of a better name
pub fn from_str(string: &str) -> Id<Self, Shared> {
unsafe {
let obj = from_str(Self::class(), string);
Id::new(obj.cast()).unwrap()
}
}
// TODO: initWithBytesNoCopy:, maybe add lifetime parameter to NSString?
// See https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L350-L381
// Might be quite difficult, as Objective-C code might assume the NSString
// is always alive?
// See https://github.com/drewcrawford/foundationr/blob/b27683417a35510e8e5d78a821f081905b803de6/src/nsstring.rs
/// Whether the given string matches the beginning characters of this
/// string.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1410309-hasprefix?language=objc).
#[doc(alias = "hasPrefix")]
#[doc(alias = "hasPrefix:")]
#[sel(hasPrefix:)]
pub fn has_prefix(&self, prefix: &NSString) -> bool;
/// Whether the given string matches the ending characters of this string.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1416529-hassuffix?language=objc).
#[doc(alias = "hasSuffix")]
#[doc(alias = "hasSuffix:")]
#[sel(hasSuffix:)]
pub fn has_suffix(&self, suffix: &NSString) -> bool;
// TODO: Other comparison methods:
// - compare:options:
// - compare:options:range:
// - compare:options:range:locale:
// - localizedCompare:
// - caseInsensitiveCompare:
// - localizedCaseInsensitiveCompare:
// - localizedStandardCompare:
#[sel(compare:)]
fn compare(&self, other: &Self) -> NSComparisonResult;
// pub fn from_nsrange(range: NSRange) -> Id<Self, Shared>
// https://developer.apple.com/documentation/foundation/1415155-nsstringfromrange?language=objc
}
);
pub(crate) fn from_str(cls: &Class, string: &str) -> *mut Object {
let bytes: *const c_void = string.as_ptr().cast();
unsafe {
let obj: *mut Object = msg_send![cls, alloc];
msg_send![
obj,
initWithBytes: bytes,
length: string.len(),
encoding: UTF8_ENCODING,
]
}
}
impl PartialOrd for NSString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NSString {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.compare(other).into()
}
}
// TODO: PartialEq and PartialOrd against &str
// See `fruity`'s implementation:
// https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L69-L163
impl DefaultId for NSString {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSString {
type Ownership = Shared;
type Output = NSString;
}
unsafe impl NSMutableCopying for NSString {
type Output = NSMutableString;
}
impl ToOwned for NSString {
type Owned = Id<NSString, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Display for NSString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The call to `to_owned` is unfortunate, but is required to work
// around `f` not being AutoreleaseSafe.
// TODO: Fix this!
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
fmt::Display::fmt(&s, f)
}
}
impl fmt::Debug for NSString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The call to `to_owned` is unfortunate, but is required to work
// around `f` not being AutoreleaseSafe.
// TODO: Fix this!
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
fmt::Debug::fmt(&s, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use core::ptr;
#[test]
fn test_equality() {
let s1 = NSString::from_str("abc");
let s2 = NSString::from_str("abc");
assert_eq!(s1, s1);
assert_eq!(s1, s2);
let s3 = NSString::from_str("def");
assert_ne!(s1, s3);
}
#[test]
fn display_debug() {
let s = NSString::from_str("xyz\"123");
assert_eq!(format!("{}", s), "xyz\"123");
assert_eq!(format!("{:?}", s), r#""xyz\"123""#);
}
#[test]
fn test_empty() {
let s1 = NSString::from_str("");
let s2 = NSString::new();
assert_eq!(s1.len(), 0);
assert_eq!(s2.len(), 0);
assert_eq!(s1, s2);
autoreleasepool(|pool| {
assert_eq!(s1.as_str(pool), "");
assert_eq!(s2.as_str(pool), "");
});
}
#[test]
fn test_utf8() {
let expected = "ประเทศไทย中华Việt Nam";
let s = NSString::from_str(expected);
assert_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_nul() {
let expected = "\0";
let s = NSString::from_str(expected);
assert_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_interior_nul() {
let expected = "Hello\0World";
let s = NSString::from_str(expected);
assert_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_copy() {
let s1 = NSString::from_str("abc");
let s2 = s1.copy();
// An optimization that NSString makes, since it is immutable
assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
assert!(s2.is_kind_of::<NSString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
assert!(s3.is_kind_of::<NSMutableString>());
}
#[test]
fn test_copy_nsstring_is_same() {
let string1 = NSString::from_str("Hello, world!");
let string2 = string1.copy();
assert!(
ptr::eq(&*string1, &*string2),
"Cloned NSString didn't have the same address"
);
}
#[test]
/// Apparently NSString does this for some reason?
fn test_strips_first_leading_zero_width_no_break_space() {
let ns_string = NSString::from_str("\u{feff}");
let expected = "";
autoreleasepool(|pool| {
assert_eq!(ns_string.as_str(pool), expected);
});
assert_eq!(ns_string.len(), 0);
let s = "\u{feff}\u{feff}a\u{feff}";
// Huh, this difference might be a GNUStep bug?
#[cfg(feature = "apple")]
let expected = "\u{feff}a\u{feff}";
#[cfg(feature = "gnustep-1-7")]
let expected = "a\u{feff}";
let ns_string = NSString::from_str(s);
autoreleasepool(|pool| {
assert_eq!(ns_string.as_str(pool), expected);
});
assert_eq!(ns_string.len(), expected.len());
}
#[test]
fn test_hash() {
use core::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
let s1 = NSString::from_str("example string goes here");
let s2 = NSString::from_str("example string goes here");
let mut hashstate = DefaultHasher::new();
let mut hashstate2 = DefaultHasher::new();
s1.hash(&mut hashstate);
s2.hash(&mut hashstate2);
assert_eq!(hashstate.finish(), hashstate2.finish());
}
#[test]
fn test_prefix_suffix() {
let s = NSString::from_str("abcdef");
let prefix = NSString::from_str("abc");
let suffix = NSString::from_str("def");
assert!(s.has_prefix(&prefix));
assert!(s.has_suffix(&suffix));
assert!(!s.has_prefix(&suffix));
assert!(!s.has_suffix(&prefix));
}
#[test]
#[allow(clippy::nonminimal_bool)]
fn test_cmp() {
let s1 = NSString::from_str("aa");
assert!(s1 <= s1);
assert!(s1 >= s1);
let s2 = NSString::from_str("ab");
assert!(s1 < s2);
assert!(!(s1 > s2));
assert!(s1 <= s2);
assert!(!(s1 >= s2));
let s3 = NSString::from_str("ba");
assert!(s1 < s3);
assert!(!(s1 > s3));
assert!(s1 <= s3);
assert!(!(s1 >= s3));
assert!(s2 < s3);
assert!(!(s2 > s3));
assert!(s2 <= s3);
assert!(!(s2 >= s3));
let s = NSString::from_str("abc");
let shorter = NSString::from_str("a");
let longer = NSString::from_str("abcdef");
assert!(s > shorter);
assert!(s < longer);
}
}

View file

@ -0,0 +1,237 @@
use core::fmt;
use core::marker::PhantomData;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSObject, NSString};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A thread of execution.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsthread?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSThread;
unsafe impl ClassType for NSThread {
type Super = NSObject;
}
);
unsafe impl Send for NSThread {}
unsafe impl Sync for NSThread {}
impl UnwindSafe for NSThread {}
impl RefUnwindSafe for NSThread {}
extern_methods!(
unsafe impl NSThread {
/// Returns the [`NSThread`] object representing the current thread.
pub fn current() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentThread] }
}
/// Returns the [`NSThread`] object representing the main thread.
pub fn main() -> Id<NSThread, Shared> {
// The main thread static may not have been initialized
// This can at least fail in GNUStep!
let obj: Option<_> = unsafe { msg_send_id![Self::class(), mainThread] };
obj.expect("Could not retrieve main thread.")
}
/// Returns `true` if the thread is the main thread.
#[sel(isMainThread)]
pub fn is_main(&self) -> bool;
/// The name of the thread.
pub fn name(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, name] }
}
unsafe fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(start)]
unsafe fn start(&self);
#[sel(isMainThread)]
fn is_current_main() -> bool;
#[sel(isMultiThreaded)]
fn is_global_multi() -> bool;
}
);
impl fmt::Debug for NSThread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Use -[NSThread description] since that includes the thread number
let obj: &NSObject = self;
fmt::Debug::fmt(obj, f)
}
}
/// Whether the application is multithreaded according to Cocoa.
pub fn is_multi_threaded() -> bool {
NSThread::is_global_multi()
}
/// Whether the current thread is the main thread.
pub fn is_main_thread() -> bool {
NSThread::is_current_main()
}
#[allow(unused)]
fn make_multithreaded() {
let thread = unsafe { NSThread::new() };
unsafe { thread.start() };
// Don't bother waiting for it to complete!
}
/// A marker type taken by functions that can only be executed on the main
/// thread.
///
/// By design, this is neither [`Send`] nor [`Sync`], and can only be created
/// on the main thread, meaning that if you're holding this, you know you're
/// running on the main thread.
///
/// See the following links for more information on main-thread-only APIs:
/// - [Main Thread Only APIs on OS X](https://www.dribin.org/dave/blog/archives/2009/02/01/main_thread_apis/)
/// - [Thread Safety Summary](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1)
/// - [Are the Cocoa Frameworks Thread Safe?](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47)
/// - [Technical Note TN2028 - Threading Architectures](https://developer.apple.com/library/archive/technotes/tn/tn2028.html#//apple_ref/doc/uid/DTS10003065)
/// - [Thread Management](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html)
/// - [Mike Ash' article on thread safety](https://www.mikeash.com/pyblog/friday-qa-2009-01-09.html)
///
///
/// # Examples
///
/// Use when designing APIs that are only safe to use on the main thread:
///
/// ```no_run
/// use objc2::foundation::MainThreadMarker;
/// use objc2::runtime::Object;
/// use objc2::msg_send;
/// # let obj = 0 as *const Object;
///
/// // This action requires the main thread, so we take a marker as parameter.
/// // It signals clearly to users "this requires the main thread".
/// unsafe fn do_thing(obj: *const Object, _mtm: MainThreadMarker) {
/// msg_send![obj, someActionThatRequiresTheMainThread]
/// }
///
/// // Usage
/// let mtm = MainThreadMarker::new().unwrap();
/// unsafe { do_thing(obj, mtm) }
/// ```
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
// This is valid to Copy because it's still `!Send` and `!Sync`.
pub struct MainThreadMarker {
// No lifetime information needed; the main thread is static and available
// throughout the entire program!
_priv: PhantomData<*mut ()>,
}
impl MainThreadMarker {
/// Construct a new [`MainThreadMarker`].
///
/// Returns [`None`] if the current thread was not the main thread.
pub fn new() -> Option<Self> {
if is_main_thread() {
// SAFETY: We just checked that we are running on the main thread.
Some(unsafe { Self::new_unchecked() })
} else {
None
}
}
/// Construct a new [`MainThreadMarker`] without first checking whether
/// the current thread is the main one.
///
/// # Safety
///
/// The current thread must be the main thread.
pub unsafe fn new_unchecked() -> Self {
// SAFETY: Upheld by caller
Self { _priv: PhantomData }
}
}
impl fmt::Debug for MainThreadMarker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("MainThreadMarker").finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
)]
fn test_main_thread() {
let current = NSThread::current();
let main = NSThread::main();
assert!(main.is_main());
if main == current {
assert!(current.is_main());
assert!(is_main_thread());
} else {
assert!(!current.is_main());
assert!(!is_main_thread());
}
}
#[test]
fn test_not_main_thread() {
let res = std::thread::spawn(|| (is_main_thread(), NSThread::current().is_main()))
.join()
.unwrap();
assert_eq!(res, (false, false));
}
#[test]
fn test_main_thread_auto_traits() {
fn assert_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized>() {}
assert_traits::<MainThreadMarker>()
}
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
)]
fn test_debug() {
let thread = NSThread::main();
let actual = format!("{:?}", thread);
let expected = [
// macOS 11
format!("<NSThread: {:p}>{{number = 1, name = (null)}}", thread),
format!("<NSThread: {:p}>{{number = 1, name = main}}", thread),
// macOS 12
format!("<_NSMainThread: {:p}>{{number = 1, name = (null)}}", thread),
format!("<_NSMainThread: {:p}>{{number = 1, name = main}}", thread),
];
assert!(
expected.contains(&actual),
"Expected one of {:?}, got {:?}",
expected,
actual,
);
// SAFETY: We don't use the marker for anything other than its Debug
// impl, so this test doesn't actually need to run on the main thread!
let marker = unsafe { MainThreadMarker::new_unchecked() };
assert_eq!(format!("{:?}", marker), "MainThreadMarker");
}
}

View file

@ -0,0 +1,189 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType, Encode, Encoding, RefEncode};
extern_class!(
/// A universally unique value.
///
/// Can be used to identify types, interfaces, and other items.
///
/// Conversion methods to/from UUIDs from the `uuid` crate can be
/// enabled with the `uuid` crate feature.
///
/// macOS: This is only available on 10.8 and above.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsuuid?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSUUID;
unsafe impl ClassType for NSUUID {
type Super = NSObject;
}
);
/// The headers describe `initWithUUIDBytes:` and `getUUIDBytes:` as
/// taking `uuid_t`, but something fishy is going on, in reality they
/// expect a reference to these!
///
/// Hence we create this newtype to change the encoding.
#[repr(transparent)]
struct UuidBytes([u8; 16]);
unsafe impl RefEncode for UuidBytes {
const ENCODING_REF: Encoding = Encoding::Array(16, &u8::ENCODING);
}
// SAFETY: `NSUUID` is immutable.
unsafe impl Sync for NSUUID {}
unsafe impl Send for NSUUID {}
impl UnwindSafe for NSUUID {}
impl RefUnwindSafe for NSUUID {}
extern_methods!(
unsafe impl NSUUID {
pub fn new_v4() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
/// The 'nil UUID'.
pub fn nil() -> Id<Self, Shared> {
Self::from_bytes([0; 16])
}
pub fn from_bytes(bytes: [u8; 16]) -> Id<Self, Shared> {
let bytes = UuidBytes(bytes);
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithUUIDBytes: &bytes]
}
}
pub fn from_string(string: &NSString) -> Option<Id<Self, Shared>> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithUUIDString: string]
}
}
#[sel(getUUIDBytes:)]
fn get_bytes_raw(&self, bytes: &mut UuidBytes);
pub fn as_bytes(&self) -> [u8; 16] {
let mut bytes = UuidBytes([0; 16]);
self.get_bytes_raw(&mut bytes);
bytes.0
}
pub fn string(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, UUIDString] }
}
}
);
impl fmt::Display for NSUUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.string(), f)
}
}
impl fmt::Debug for NSUUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The `uuid` crate does `Debug` and `Display` equally, and so do we
fmt::Display::fmt(&self.string(), f)
}
}
// UUID `compare:` is broken for some reason?
// impl PartialOrd for NSUUID {
// #[inline]
// fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
// Some(self.cmp(other))
// }
// }
// impl Ord for NSUUID {
// fn cmp(&self, other: &Self) -> cmp::Ordering {
// let res: NSComparisonResult = unsafe { msg_send![self, compare: other] };
// res.into()
// }
// }
/// Conversion methods to/from `uuid` crate.
#[cfg(feature = "uuid")]
impl NSUUID {
pub fn from_uuid(uuid: uuid::Uuid) -> Id<Self, Shared> {
Self::from_bytes(uuid.into_bytes())
}
pub fn as_uuid(&self) -> uuid::Uuid {
uuid::Uuid::from_bytes(self.as_bytes())
}
}
impl DefaultId for NSUUID {
type Ownership = Shared;
fn default_id() -> Id<Self, Self::Ownership> {
Self::nil()
}
}
unsafe impl NSCopying for NSUUID {
type Ownership = Shared;
type Output = NSUUID;
}
impl alloc::borrow::ToOwned for NSUUID {
type Owned = Id<NSUUID, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn test_new() {
let uuid1 = NSUUID::new_v4();
let uuid2 = NSUUID::new_v4();
assert_ne!(uuid1, uuid2, "Statistically very unlikely");
}
#[test]
fn test_bytes() {
let uuid = NSUUID::from_bytes([10; 16]);
assert_eq!(uuid.as_bytes(), [10; 16]);
}
#[test]
fn display_debug() {
let uuid = NSUUID::from_bytes([10; 16]);
let expected = "0A0A0A0A-0A0A-0A0A-0A0A-0A0A0A0A0A0A";
assert_eq!(format!("{}", uuid), expected);
assert_eq!(format!("{:?}", uuid), expected);
}
// #[test]
// fn test_compare() {
// let uuid1 = NSUUID::from_bytes([10; 16]);
// let uuid2 = NSUUID::from_bytes([9; 16]);
// assert!(uuid1 > uuid2);
// }
#[cfg(feature = "uuid")]
#[test]
fn test_convert_roundtrip() {
let nsuuid1 = NSUUID::new_v4();
let uuid = nsuuid1.as_uuid();
let nsuuid2 = NSUUID::from_uuid(uuid);
assert_eq!(nsuuid1, nsuuid2);
}
}

View file

@ -0,0 +1,363 @@
use alloc::string::ToString;
use core::ffi::c_void;
use core::fmt;
use core::hash;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::str;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use super::{NSCopying, NSObject, NSPoint, NSRange, NSRect, NSSize};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encode};
extern_class!(
/// A container wrapping any encodable type as an Obective-C object.
///
/// Since Objective-C collections like [`NSArray`] can only contain
/// objects, it is common to wrap pointers or structures like [`NSRange`].
///
/// Note that creating `NSValue`s is not `unsafe`, but almost all usage of
/// it is, since we cannot guarantee that the type that was used to
/// construct it is the same as the expected output type.
///
/// See also the [`NSNumber`] subclass for when you want to wrap numbers.
///
/// See [Apple's documentation][apple-doc] for more information.
///
/// [`NSArray`]: super::NSArray
/// [`NSRange`]: super::NSRange
/// [`NSNumber`]: super::NSNumber
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsnumber?language=objc
pub struct NSValue;
unsafe impl ClassType for NSValue {
type Super = NSObject;
}
);
// We can't implement any auto traits for NSValue, since it can contain an
// arbitary object!
extern_methods!(
/// Creation methods.
unsafe impl NSValue {
// Default / empty new is not provided because `-init` returns `nil` on
// Apple and GNUStep throws an exception on all other messages to this
// invalid instance.
/// Create a new `NSValue` containing the given type.
///
/// Be careful when using this since you may accidentally pass a reference
/// when you wanted to pass a concrete type instead.
///
///
/// # Examples
///
/// Create an `NSValue` containing an [`NSPoint`][super::NSPoint].
///
/// ```
/// use objc2::foundation::{NSPoint, NSValue};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let val = NSValue::new::<NSPoint>(NSPoint::new(1.0, 1.0));
/// ```
pub fn new<T: 'static + Copy + Encode>(value: T) -> Id<Self, Shared> {
let bytes: *const T = &value;
let bytes: *const c_void = bytes.cast();
let encoding = CString::new(T::ENCODING.to_string()).unwrap();
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithBytes: bytes,
objCType: encoding.as_ptr(),
]
}
}
}
/// Getter methods.
unsafe impl NSValue {
/// Retrieve the data contained in the `NSValue`.
///
/// Note that this is broken on GNUStep for some types, see
/// [gnustep/libs-base#216].
///
/// [gnustep/libs-base#216]: https://github.com/gnustep/libs-base/pull/216
///
///
/// # Safety
///
/// The type of `T` must be what the NSValue actually stores, and any
/// safety invariants that the value has must be upheld.
///
/// Note that it may be, but is not always, enough to simply check whether
/// [`contains_encoding`] returns `true`. For example, `NonNull<T>` have
/// the same encoding as `*const T`, but `NonNull<T>` is clearly not
/// safe to return from this function even if you've checked the encoding
/// beforehand.
///
/// [`contains_encoding`]: Self::contains_encoding
///
///
/// # Examples
///
/// Store a pointer in `NSValue`, and retrieve it again afterwards.
///
/// ```
/// use std::ffi::c_void;
/// use std::ptr;
/// use objc2::foundation::NSValue;
///
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let val = NSValue::new::<*const c_void>(ptr::null());
/// // SAFETY: The value was just created with a pointer
/// let res = unsafe { val.get::<*const c_void>() };
/// assert!(res.is_null());
/// ```
pub unsafe fn get<T: 'static + Copy + Encode>(&self) -> T {
debug_assert!(
self.contains_encoding::<T>(),
"wrong encoding. NSValue tried to return something with encoding {}, but the encoding of the given type was {}",
self.encoding().unwrap_or("(NULL)"),
T::ENCODING,
);
let mut value = MaybeUninit::<T>::uninit();
let ptr: *mut c_void = value.as_mut_ptr().cast();
let _: () = unsafe { msg_send![self, getValue: ptr] };
// SAFETY: We know that `getValue:` initialized the value, and user
// ensures that it is safe to access.
unsafe { value.assume_init() }
}
pub fn get_range(&self) -> Option<NSRange> {
if self.contains_encoding::<NSRange>() {
// SAFETY: We just checked that this contains an NSRange
Some(unsafe { msg_send![self, rangeValue] })
} else {
None
}
}
pub fn get_point(&self) -> Option<NSPoint> {
if self.contains_encoding::<NSPoint>() {
// SAFETY: We just checked that this contains an NSPoint
//
// Note: The documentation says that `pointValue`, `sizeValue` and
// `rectValue` is only available on macOS, but turns out that they
// are actually available everywhere!
let res = unsafe { msg_send![self, pointValue] };
Some(res)
} else {
None
}
}
pub fn get_size(&self) -> Option<NSSize> {
if self.contains_encoding::<NSSize>() {
// SAFETY: We just checked that this contains an NSSize
let res = unsafe { msg_send![self, sizeValue] };
Some(res)
} else {
None
}
}
pub fn get_rect(&self) -> Option<NSRect> {
if self.contains_encoding::<NSRect>() {
// SAFETY: We just checked that this contains an NSRect
let res = unsafe { msg_send![self, rectValue] };
Some(res)
} else {
None
}
}
pub fn encoding(&self) -> Option<&str> {
let result: Option<NonNull<c_char>> = unsafe { msg_send![self, objCType] };
result.map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap())
}
pub fn contains_encoding<T: 'static + Copy + Encode>(&self) -> bool {
if let Some(encoding) = self.encoding() {
T::ENCODING.equivalent_to_str(encoding)
} else {
panic!("missing NSValue encoding");
}
}
#[sel(isEqualToValue:)]
fn is_equal_to_value(&self, other: &Self) -> bool;
}
);
unsafe impl NSCopying for NSValue {
type Ownership = Shared;
type Output = NSValue;
}
impl alloc::borrow::ToOwned for NSValue {
type Owned = Id<NSValue, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl PartialEq for NSValue {
#[doc(alias = "isEqualToValue:")]
fn eq(&self, other: &Self) -> bool {
// Use isEqualToValue: instaed of isEqual: since it is faster
self.is_equal_to_value(other)
}
}
impl hash::Hash for NSValue {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// Delegate to NSObject
(**self).hash(state)
}
}
impl fmt::Debug for NSValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let enc = self.encoding().unwrap_or("(NULL)");
let bytes = &**self; // Delegate to -[NSObject description]
f.debug_struct("NSValue")
.field("encoding", &enc)
.field("bytes", bytes)
.finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::{ptr, slice};
use super::*;
use crate::rc::{RcTestObject, ThreadTestData};
#[test]
fn basic() {
let val = NSValue::new(13u32);
assert_eq!(unsafe { val.get::<u32>() }, 13);
}
#[test]
fn does_not_retain() {
let obj = RcTestObject::new();
let expected = ThreadTestData::current();
let val = NSValue::new::<*const RcTestObject>(&*obj);
expected.assert_current();
assert!(ptr::eq(unsafe { val.get::<*const RcTestObject>() }, &*obj));
expected.assert_current();
let _clone = val.clone();
expected.assert_current();
let _copy = val.copy();
expected.assert_current();
drop(val);
expected.assert_current();
}
#[test]
fn test_equality() {
let val1 = NSValue::new(123u32);
let val2 = NSValue::new(123u32);
assert_eq!(val1, val1);
assert_eq!(val1, val2);
let val3 = NSValue::new(456u32);
assert_ne!(val1, val3);
}
#[test]
fn test_equality_across_types() {
let val1 = NSValue::new(123i32);
let val2 = NSValue::new(123u32);
// Test that `objCType` is checked when comparing equality
assert_ne!(val1, val2);
}
#[test]
#[ignore = "the debug output changes depending on OS version"]
fn test_debug() {
let expected = if cfg!(feature = "gnustep-1-7") {
r#"NSValue { encoding: "C", bytes: (C) <ab> }"#
} else if cfg!(newer_apple) {
r#"NSValue { encoding: "C", bytes: {length = 1, bytes = 0xab} }"#
} else {
r#"NSValue { encoding: "C", bytes: <ab> }"#
};
assert_eq!(format!("{:?}", NSValue::new(171u8)), expected);
}
#[test]
fn nsrange() {
let range = NSRange::from(1..2);
let val = NSValue::new(range);
assert_eq!(val.get_range(), Some(range));
assert_eq!(val.get_point(), None);
assert_eq!(val.get_size(), None);
assert_eq!(val.get_rect(), None);
// NSValue -getValue is broken on GNUStep for some types
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSRange>() }, range);
}
#[test]
fn nspoint() {
let point = NSPoint::new(1.0, 2.0);
let val = NSValue::new(point);
assert_eq!(val.get_point(), Some(point));
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSPoint>() }, point);
}
#[test]
fn nssize() {
let point = NSSize::new(1.0, 2.0);
let val = NSValue::new(point);
assert_eq!(val.get_size(), Some(point));
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSSize>() }, point);
}
#[test]
fn nsrect() {
let rect = NSRect::new(NSPoint::new(1.0, 2.0), NSSize::new(3.0, 4.0));
let val = NSValue::new(rect);
assert_eq!(val.get_rect(), Some(rect));
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSRect>() }, rect);
}
#[test]
fn store_str() {
let s = "abc";
let val = NSValue::new(s.as_ptr());
assert!(val.contains_encoding::<*const u8>());
let slice = unsafe { slice::from_raw_parts(val.get(), s.len()) };
let s2 = str::from_utf8(slice).unwrap();
assert_eq!(s2, s);
}
#[test]
fn store_cstr() {
// The following Apple article says that NSValue can't easily store
// C-strings, but apparently that doesn't apply to us!
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/NumbersandValues/Articles/Values.html#//apple_ref/doc/uid/20000174-BAJJHDEG>
let s = CStr::from_bytes_with_nul(b"test123\0").unwrap();
let val = NSValue::new(s.as_ptr());
assert!(val.contains_encoding::<*const c_char>());
let s2 = unsafe { CStr::from_ptr(val.get()) };
assert_eq!(s2, s);
}
}

View file

@ -0,0 +1,95 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use crate::ffi;
#[cfg(feature = "gnustep-1-7")]
use crate::Encode;
use crate::{Encoding, RefEncode};
/// A type used to identify and manage memory zones.
///
/// Zones are ignored on all newer platforms, you should very rarely need to
/// use this, but may be useful if you need to implement `copyWithZone:` or
/// `allocWithZone:`.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nszone?language=objc).
pub struct NSZone {
// Use `objc_object` to mark the types as !Send, !Sync and UnsafeCell.
//
// This works since `objc_object` is a ZST
_inner: ffi::objc_object,
}
impl fmt::Debug for NSZone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<NSZone {:p}>", self)
}
}
// Note: We don't know anything about the internals of `NSZone`, so best not
// to make it `Send` and `Sync` for now.
impl UnwindSafe for NSZone {}
impl RefUnwindSafe for NSZone {}
unsafe impl RefEncode for NSZone {
#[cfg(feature = "apple")]
const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct("_NSZone", &[]));
#[cfg(feature = "gnustep-1-7")]
const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct(
"_NSZone",
&[
// Functions
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
// Stats
Encoding::Pointer(&Encoding::Unknown),
// Zone granularity
usize::ENCODING,
// Name of zone
Encoding::Object,
// Next zone - note that the contents of this doesn't matter,
// since this is nested far enough that the encoding string just
// ends up ignoring it.
Encoding::Pointer(&Encoding::Struct("_NSZone", &[])),
],
));
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use core::ptr;
use super::*;
use crate::foundation::NSObject;
use crate::msg_send_id;
use crate::rc::{Allocated, Id, Owned};
use crate::ClassType;
#[test]
fn alloc_with_zone() {
let zone: *const NSZone = ptr::null();
let _obj: Id<Allocated<NSObject>, Owned> =
unsafe { msg_send_id![NSObject::class(), allocWithZone: zone] };
}
#[test]
fn verify_encoding() {
let expected = if cfg!(all(feature = "gnustep-1-7", target_pointer_width = "64")) {
"^{_NSZone=^?^?^?^?^?^?^?Q@^{_NSZone}}"
} else if cfg!(all(
feature = "gnustep-1-7",
not(target_pointer_width = "64")
)) {
"^{_NSZone=^?^?^?^?^?^?^?I@^{_NSZone}}"
} else {
"^{_NSZone=}"
};
assert_eq!(NSZone::ENCODING_REF.to_string(), expected);
}
}

260
third-party/vendor/objc2/src/lib.rs vendored Normal file
View file

@ -0,0 +1,260 @@
//! # Objective-C interface and runtime bindings
//!
//! Objective-C is[^note] the standard programming language on Apple platforms
//! like macOS, iOS, iPadOS, tvOS and watchOS. It is an object-oriented
//! language centered around "sending messages" to its instances - this can
//! for the most part be viewed as a simple method call.
//!
//! Most of the core libraries and frameworks that are in use on Apple systems
//! are written in Objective-C, and hence we would like the ability to
//! interract with these using Rust; this crate enables you to do that, in
//! as safe a manner as possible.
//!
//! [^note]: Yes, I know, "was", Swift now exists. All the existing frameworks
//! are written in Objective-C though, so the point still holds.
//!
//!
//! ## Basic usage
//!
//! This example illustrates major parts of the functionality in this crate:
//!
//! First, we get a reference to the `NSObject`'s [`runtime::Class`] using the
//! [`class!`] macro.
//! Next, we creates a new [`runtime::Object`] pointer, and ensure it is
//! deallocated after we've used it by putting it into an [`rc::Owned`]
//! [`rc::Id`].
//! Now we're free to send messages to the object to our hearts desire using
//! the [`msg_send!`] or [`msg_send_id!`] macros (depending on the return type
//! of the method).
//! Finally, the `Id<Object, _>` goes out of scope, and the object is released
//! and deallocated.
//!
#![cfg_attr(feature = "apple", doc = "```")]
#![cfg_attr(not(feature = "apple"), doc = "```no_run")]
//! use objc2::{class, msg_send, msg_send_id};
//! use objc2::ffi::NSUInteger;
//! use objc2::rc::{Id, Owned, Shared};
//! use objc2::runtime::Object;
//!
//! let cls = class!(NSObject);
//!
//! // Creation
//!
//! let obj1: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
//! let obj2: Id<Object, Owned> = unsafe {
//! // Equivalent to using `new`
//! msg_send_id![msg_send_id![cls, alloc], init]
//! };
//!
//! // Usage
//!
//! let hash1: NSUInteger = unsafe { msg_send![&obj1, hash] };
//! let hash2: NSUInteger = unsafe { msg_send![&obj2, hash] };
//! assert_ne!(hash1, hash2);
//!
//! let is_kind: bool = unsafe { msg_send![&obj1, isKindOfClass: cls] };
//! assert!(is_kind);
//!
//! // We're going to create a new reference to the first object, so
//! // relinquish mutable ownership.
//! let obj1: Id<Object, Shared> = obj1.into();
//! let obj1_self: Id<Object, Shared> = unsafe { msg_send_id![&obj1, self] };
//! let is_equal: bool = unsafe { msg_send![&obj1, isEqual: &*obj1_self] };
//! assert!(is_equal);
//!
//! // Deallocation on drop
//! ```
//!
//! Note that this very simple example contains **a lot** of `unsafe` (which
//! should all ideally be justified with a `// SAFETY` comment). This is
//! required because our compiler can verify very little about the Objective-C
//! invocation, including all argument and return types used in [`msg_send!`];
//! we could have just as easily accidentally made `hash` an `f32`, or any
//! other type, and this would trigger undefined behaviour!
//!
//! Making the ergonomics better is something that is currently being worked
//! on, the [`foundation`] module contains more ergonomic usage of at
//! least parts of the `Foundation` framework.
//!
//! Anyhow, all of this `unsafe` nicely leads us to another feature that this
//! crate has:
//!
//! [`runtime::Class`]: crate::runtime::Class
//! [`runtime::Object`]: crate::runtime::Object
//! [`rc::Owned`]: crate::rc::Owned
//! [`rc::Id`]: crate::rc::Id
//! [`foundation`]: crate::foundation
//!
//!
//! ## Encodings and message type verification
//!
//! The Objective-C runtime includes encodings for each method that describe
//! the argument and return types. See the [`objc2-encode`] crate for the
//! full overview of what this is (its types are re-exported in this crate).
//!
//! The important part is: To make message sending safer, all arguments and
//! return values for messages must implement [`Encode`]. This allows the Rust
//! compiler to prevent you from passing e.g. a [`Box`] into Objective-C,
//! which would both be UB and leak the box.
//!
//! Furthermore, we can take advantage of the encodings provided by the
//! runtime to verify that the types used in Rust actually match the types
//! encoded for the method. This is not a perfect solution for ensuring safety
//! (some Rust types have the same Objective-C encoding, but are not
//! equivalent), but it gets us much closer to it!
//!
//! To use this functionality, enable the `"verify_message"` cargo feature
//! while debugging. With this feature enabled, encodings are checked every
//! time you send a message, and the message send will panic if they are not
//! equivalent.
//!
//! To take the example above, if we changed the `hash` method's return type
//! as in the following example, it panics when the feature is enabled:
//!
#![cfg_attr(
all(feature = "apple", feature = "verify_message"),
doc = "```should_panic"
)]
#![cfg_attr(
not(all(feature = "apple", feature = "verify_message")),
doc = "```no_run"
)]
//! # use objc2::{class, msg_send, msg_send_id};
//! # use objc2::rc::{Id, Owned};
//! # use objc2::runtime::Object;
//! #
//! # let cls = class!(NSObject);
//! # let obj1: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
//! #
//! // Wrong return type - this is UB!
//! let hash1: f32 = unsafe { msg_send![&obj1, hash] };
//! ```
//!
//! [`objc2-encode`]: objc2_encode
//! [`Box`]: std::boxed::Box
//!
//!
//! ## Crate features
//!
//! This crate exports several optional cargo features, see [`Cargo.toml`] for
//! an overview and description of these.
//!
//! [`Cargo.toml`]: https://github.com/madsmtm/objc2/blob/master/objc2/Cargo.toml
//!
//!
//! ## Support for other Operating Systems
//!
//! The bindings can be used on Linux or *BSD utilizing the
//! [GNUstep Objective-C runtime](https://www.github.com/gnustep/libobjc2),
//! see the [`objc-sys`][`objc_sys`] crate for how to configure this.
//!
//!
//! ## Other functionality
//!
//! That was a quick introduction, this library also has [support for handling
//! exceptions][exc], [the ability to dynamically declare Objective-C
//! classes][declare], [advanced reference-counting utilities][rc], and more -
//! peruse the documentation at will!
//!
//! [exc]: crate::exception
//! [declare]: crate::declare
//! [rc]: crate::rc
#![no_std]
#![cfg_attr(
feature = "unstable-autoreleasesafe",
feature(negative_impls, auto_traits)
)]
#![cfg_attr(feature = "unstable-c-unwind", feature(c_unwind))]
#![cfg_attr(feature = "unstable-docsrs", feature(doc_auto_cfg))]
#![warn(elided_lifetimes_in_paths)]
#![warn(missing_docs)]
#![deny(non_ascii_idents)]
#![warn(unreachable_pub)]
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(clippy::cargo)]
#![warn(clippy::ptr_as_ptr)]
// Update in Cargo.toml as well.
#![doc(html_root_url = "https://docs.rs/objc2/0.3.0-beta.3.patch-leaks.3")]
#[cfg(not(feature = "alloc"))]
compile_error!("The `alloc` feature currently must be enabled.");
#[cfg(not(feature = "std"))]
compile_error!("The `std` feature currently must be enabled.");
extern crate alloc;
extern crate std;
// The example uses NSObject without doing the __gnustep_hack
#[cfg(all(feature = "apple", doctest))]
#[doc = include_str!("../README.md")]
extern "C" {}
pub use objc2_encode as encode;
pub use objc_sys as ffi;
#[doc(no_inline)]
pub use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
pub use crate::class_type::ClassType;
pub use crate::message::{Message, MessageArguments, MessageReceiver};
#[cfg(feature = "malloc")]
pub use crate::verify::VerificationError;
#[cfg(feature = "objc2-proc-macros")]
#[doc(hidden)]
pub use objc2_proc_macros::__hash_idents;
#[cfg(not(feature = "objc2-proc-macros"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __hash_idents {
// Noop; used to make our other macros a bit easier to read
($($x:tt)*) => {$($x)*};
}
#[doc(hidden)]
pub mod __macro_helpers;
mod cache;
mod class_type;
pub mod declare;
pub mod exception;
#[cfg(feature = "foundation")]
pub mod foundation;
mod macros;
mod message;
pub mod rc;
pub mod runtime;
#[cfg(test)]
mod test_utils;
#[cfg(feature = "malloc")]
mod verify;
/// Hacky way to make GNUStep link properly to Foundation while testing.
///
/// This is a temporary solution to make our CI work for now!
#[doc(hidden)]
#[cfg(feature = "gnustep-1-7")]
pub mod __gnustep_hack {
use super::runtime::Class;
extern "C" {
// The linking changed in libobjc2 v2.0
#[cfg_attr(feature = "gnustep-2-0", link_name = "._OBJC_CLASS_NSObject")]
#[cfg_attr(not(feature = "gnustep-2-0"), link_name = "_OBJC_CLASS_NSObject")]
static OBJC_CLASS_NSObject: Class;
// Others:
// __objc_class_name_NSObject
// _OBJC_CLASS_REF_NSObject
}
pub unsafe fn get_class_to_force_linkage() -> &'static Class {
unsafe { core::ptr::read_volatile(&&OBJC_CLASS_NSObject) }
}
#[test]
fn ensure_linkage() {
unsafe { get_class_to_force_linkage() };
}
}

1010
third-party/vendor/objc2/src/macros.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,102 @@
#[doc(hidden)]
#[macro_export]
macro_rules! __rewrite_self_arg {
{
($out_macro:path)
($($args:tt)*)
$($macro_args:tt)*
} => {
$crate::__rewrite_self_arg! {
($out_macro)
// Duplicate args out so that we can match on `self`, while still
// use it as a function argument
@($($args)*)
@($($args)*)
$($macro_args)*
}
};
// Instance method
{
($out_macro:path)
@(&mut self $($__rest_args:tt)*)
@(&mut $self:ident $(, $($rest:tt)*)?)
$($macro_args:tt)*
} => {
$out_macro! {
$($macro_args)*
@(instance_method)
@(
$self: &mut Self,
_: $crate::runtime::Sel,
)
@($($($rest)*)?)
}
};
{
($out_macro:path)
@(&self $($__rest_args:tt)*)
@(&$self:ident $(, $($rest:tt)*)?)
$($macro_args:tt)*
} => {
$out_macro! {
$($macro_args)*
@(instance_method)
@(
$self: &Self,
_: $crate::runtime::Sel,
)
@($($($rest)*)?)
}
};
{
($out_macro:path)
@(mut self: $__self_ty:ty $(, $($__rest_args:tt)*)?)
@(mut $self:ident: $self_ty:ty $(, $($rest:tt)*)?)
$($macro_args:tt)*
} => {
$out_macro! {
$($macro_args)*
@(instance_method)
@(
mut $self: $self_ty,
_: $crate::runtime::Sel,
)
@($($($rest)*)?)
}
};
{
($out_macro:path)
@(self: $__self_ty:ty $(, $($__rest_args:tt)*)?)
@($self:ident: $self_ty:ty $(, $($rest:tt)*)?)
$($macro_args:tt)*
} => {
$out_macro! {
$($macro_args)*
@(instance_method)
@(
$self: $self_ty,
_: $crate::runtime::Sel,
)
@($($($rest)*)?)
}
};
// Class method
{
($out_macro:path)
@($($__args:tt)*)
@($($args:tt)*)
$($macro_args:tt)*
} => {
$out_macro! {
$($macro_args)*
@(class_method)
@(
_: &$crate::runtime::Class,
_: $crate::runtime::Sel,
)
@($($args)*)
}
};
}

View file

@ -0,0 +1,831 @@
#[doc(hidden)]
#[macro_export]
macro_rules! __inner_declare_class {
{@rewrite_methods @($($output:tt)*)} => {};
{
// Unsafe variant
@rewrite_methods
@($($output:tt)*)
$(#[$($m:tt)*])*
unsafe fn $name:ident($($args:tt)*) $(-> $ret:ty)? $body:block
$($rest:tt)*
} => {
$crate::__rewrite_self_arg! {
($crate::__inner_declare_class)
($($args)*)
// Split the function into parts, and send the arguments down to
// be used later on
@$($output)*
@($(#[$($m)*])*)
@(unsafe extern "C")
@($name)
@($($ret)?)
@($body)
// Will add @(kind)
// Will add @(args_start)
// Will add @(args_rest)
}
$crate::__inner_declare_class! {
@rewrite_methods
@($($output)*)
$($rest)*
}
};
{
// Safe variant
@rewrite_methods
@($($output:tt)*)
$(#[$($m:tt)*])*
fn $name:ident($($args:tt)*) $(-> $ret:ty)? $body:block
$($rest:tt)*
} => {
$crate::__rewrite_self_arg! {
($crate::__inner_declare_class)
($($args)*)
@$($output)*
@($(#[$($m)*])*)
@(extern "C")
@($name)
@($($ret)?)
@($body)
// Same as above
}
$crate::__inner_declare_class! {
@rewrite_methods
@($($output)*)
$($rest)*
}
};
{
@method_out
@($(#[$($m:tt)*])*)
@($($qualifiers:tt)*)
@($name:ident)
@($($ret:ty)?)
@($($body:tt)*)
@($($_kind:tt)*)
@($($args_start:tt)*)
@($($args_rest:tt)*)
} => {
$crate::__fn_args! {
($crate::__inner_declare_class)
($($args_rest)*,)
()
()
@method_out_inner
@($(#[$($m)*])*)
@($($qualifiers)*)
@($name)
@($($ret)?)
@($($body)*)
@($($_kind)*)
@($($args_start)*)
// Will add @(args_converted)
// Will add @(body_prefix)
}
};
// No return type
{
@method_out_inner
@($(#[$($m:tt)*])*)
@($($qualifiers:tt)*)
@($name:ident)
@()
@($($body:tt)*)
@($($_kind:tt)*)
@($($args_start:tt)*)
@($($args_converted:tt)*)
@($($body_prefix:tt)*)
} => {
$crate::__attribute_helper! {
@strip_sel
$(@[$($m)*])*
($($qualifiers)* fn $name($($args_start)* $($args_converted)*) {
$($body_prefix)*
$($body)*
})
}
};
// With return type
{
@method_out_inner
@($(#[$($m:tt)*])*)
@($($qualifiers:tt)*)
@($name:ident)
@($ret:ty)
@($($body:tt)*)
@($($_kind:tt)*)
@($($args_start:tt)*)
@($($args_converted:tt)*)
@($($body_prefix:tt)*)
} => {
$crate::__attribute_helper! {
@strip_sel
$(@[$($m)*])*
($($qualifiers)* fn $name($($args_start)* $($args_converted)*) -> <$ret as $crate::encode::EncodeConvert>::__Inner {
$($body_prefix)*
<$ret as $crate::encode::EncodeConvert>::__into_inner($($body)*)
})
}
};
{
@register_out($builder:ident)
@($(#[$($m:tt)*])*)
@($($qualifiers:tt)*)
@($name:ident)
@($($_ret:tt)*)
@($($_body:tt)*)
@(class_method)
@($($args_start:tt)*)
@($($args_rest:tt)*)
} => {
$builder.add_class_method(
$crate::__attribute_helper! {
@extract_sel
($crate::__inner_declare_class)
($(#[$($m)*])*)
@call_sel
},
Self::$name as $crate::__fn_ptr! {
@($($qualifiers)*) $($args_start)* $($args_rest)*
},
);
};
{
@register_out($builder:ident)
@($(#[$($m:tt)*])*)
@($($qualifiers:tt)*)
@($name:ident)
@($($_ret:tt)*)
@($($_body:tt)*)
@(instance_method)
@($($args_start:tt)*)
@($($args_rest:tt)*)
} => {
$builder.add_method(
$crate::__attribute_helper! {
@extract_sel
($crate::__inner_declare_class)
($(#[$($m)*])*)
@call_sel
},
Self::$name as $crate::__fn_ptr! {
@($($qualifiers)*) $($args_start)* $($args_rest)*
},
);
};
{
@call_sel
@($($sel:tt)*)
} => {
$crate::sel!($($sel)*)
};
}
/// Create function pointer type with inferred arguments.
#[doc(hidden)]
#[macro_export]
macro_rules! __fn_ptr {
(
@($($qualifiers:tt)*)
$($(mut)? $($param:ident)? $(_)?: $param_ty:ty),* $(,)?
) => {
$($qualifiers)* fn($($crate::__fn_ptr!(@__to_anonymous $param_ty)),*) -> _
};
(@__to_anonymous $param_ty:ty) => { _ }
}
#[doc(hidden)]
#[macro_export]
macro_rules! __fn_args {
// Ignore `_`
{
($out_macro:path)
(_: $param_ty:ty, $($rest:tt)*)
($($args_converted:tt)*)
($($body_prefix:tt)*)
$($macro_args:tt)*
} => {
$crate::__fn_args! {
($out_macro)
($($rest)*)
($($args_converted)* _: $param_ty,)
($($body_prefix)*)
$($macro_args)*
}
};
// Convert mut
{
($out_macro:path)
(mut $param:ident: $param_ty:ty, $($rest:tt)*)
($($args_converted:tt)*)
($($body_prefix:tt)*)
$($macro_args:tt)*
} => {
$crate::__fn_args! {
($out_macro)
($($rest)*)
($($args_converted)* $param: <$param_ty as $crate::encode::EncodeConvert>::__Inner,)
(
$($body_prefix)*
let mut $param = <$param_ty as $crate::encode::EncodeConvert>::__from_inner($param);
)
$($macro_args)*
}
};
// Convert
{
($out_macro:path)
($param:ident: $param_ty:ty, $($rest:tt)*)
($($args_converted:tt)*)
($($body_prefix:tt)*)
$($macro_args:tt)*
} => {
$crate::__fn_args! {
($out_macro)
($($rest)*)
($($args_converted)* $param: <$param_ty as $crate::encode::EncodeConvert>::__Inner,)
(
$($body_prefix)*
let $param = <$param_ty as $crate::encode::EncodeConvert>::__from_inner($param);
)
$($macro_args)*
}
};
// Output result
{
($out_macro:path)
($(,)*)
($($args_converted:tt)*)
($($body_prefix:tt)*)
$($macro_args:tt)*
} => {
$out_macro! {
$($macro_args)*
@($($args_converted)*)
@($($body_prefix)*)
}
};
}
/// Declare a new Objective-C class.
///
/// This is mostly just a convenience macro on top of [`extern_class!`] and
/// the functionality in the [`declare`] module, but it can really help
/// with cutting down on boilerplate, in particular when defining delegate
/// classes!
///
///
/// # Specification
///
/// This macro consists of three parts:
/// - The class definition + ivar definition + inheritance specification.
/// - A set of method definitions.
/// - A set of protocol definitions.
///
///
/// ## Class and ivar definition
///
/// The class definition works a lot like [`extern_class!`], with the added
/// functionality that you can define custom instance variables on your class,
/// which are then wrapped in a [`declare::Ivar`] and made accessible
/// through the class. (E.g. you can use `self.my_ivar` as if it was a normal
/// Rust struct).
///
/// Note that the class name should be unique across the entire application!
/// You can declare the class with the desired unique name like
/// `"MyCrateCustomObject"` by specifying it in `ClassType::NAME`, and then
/// give the exposed type a different name like `CustomObject`.
///
/// The class is guaranteed to have been created and registered with the
/// Objective-C runtime after the [`ClassType::class`] function has been
/// called.
///
/// If any of the instance variables require being `Drop`'ed (e.g. are wrapped
/// in [`declare::IvarDrop`]), this macro will generate a `dealloc` method
/// automatically.
///
/// [`ClassType::class`]: crate::ClassType::class
/// [`declare::IvarDrop`]: crate::declare::IvarDrop
///
///
/// ## Method definitions
///
/// Within the `impl` block you can define two types of functions;
/// ["associated functions"] and ["methods"]. These are then mapped to the
/// Objective-C equivalents "class methods" and "instance methods". In
/// particular, if you use `self` your method will be registered as an
/// instance method, and if you don't it will be registered as a class method.
///
/// The desired selector can be specified using the `#[sel(my:selector:)]`
/// attribute, similar to the [`extern_methods!`] macro.
///
/// A transformation step is performed on the functions (to make them have the
/// correct ABI) and hence they shouldn't really be called manually. (You
/// can't mark them as `pub` for the same reason). Instead, use the
/// [`extern_methods!`] macro to create a Rust interface to the methods.
///
/// If the argument or return type is [`bool`], a conversion is performed to
/// make it behave similarly to the Objective-C `BOOL`. Use [`runtime::Bool`]
/// if you want to control this manually.
///
/// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods
/// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods
/// [`extern_methods!`]: crate::extern_methods
/// [`msg_send!`]: crate::msg_send
/// [`runtime::Bool`]: crate::runtime::Bool
///
///
/// ## Protocol definitions
///
/// You can specify protocols that the class should implement, along with any
/// required/optional methods for said protocols.
///
/// The methods work exactly as normal, they're only put "under" the protocol
/// definition to make things easier to read.
///
///
/// # Safety
///
/// Using this macro requires writing a few `unsafe` markers:
///
/// `unsafe impl ClassType for T` has the following safety requirements:
/// - Same as [`extern_class!`] (the inheritance chain has to be correct).
/// - Any instance variables you specify under the struct definition must
/// either be able to be created using [`MaybeUninit::zeroed`], or be
/// properly initialized in an `init` method.
///
/// `unsafe impl T { ... }` asserts that the types match those that are
/// expected when the method is invoked from Objective-C. Note that there are
/// no safe-guards here; you can easily write `i8`, but if Objective-C thinks
/// it's an `u32`, it will cause UB when called!
///
/// `unsafe impl Protocol<P> for T { ... }` requires that all required methods
/// of the specified protocol is implemented, and that any extra requirements
/// (implicit or explicit) that the protocol has are upheld. The methods in
/// this definition has the same safety requirements as above.
///
/// [`MaybeUninit::zeroed`]: core::mem::MaybeUninit::zeroed
///
///
/// # Examples
///
/// Declare a class `MyCustomObject` that inherits `NSObject`, has a few
/// instance variables and methods, and implements the `NSCopying` protocol.
///
/// ```
/// use std::os::raw::c_int;
/// use objc2::declare::{Ivar, IvarDrop};
/// use objc2::rc::{Id, Owned, Shared};
/// use objc2::foundation::{NSCopying, NSObject, NSString, NSZone};
/// use objc2::{declare_class, msg_send, msg_send_id, ns_string, ClassType};
/// #
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// declare_class!(
/// struct MyCustomObject {
/// foo: u8,
/// pub bar: c_int,
/// string: IvarDrop<Id<NSString, Shared>>,
/// }
///
/// unsafe impl ClassType for MyCustomObject {
/// type Super = NSObject;
/// // Optionally specify a different name
/// // const NAME: &'static str = "MyCustomObject";
/// }
///
/// unsafe impl MyCustomObject {
/// #[sel(initWithFoo:)]
/// fn init_with(&mut self, foo: u8) -> Option<&mut Self> {
/// let this: Option<&mut Self> = unsafe {
/// msg_send![super(self), init]
/// };
///
/// // TODO: `ns_string` can't be used inside closures.
/// let s = ns_string!("abc");
///
/// this.map(|this| {
/// // Initialize instance variables
///
/// // Some types like `u8`, `bool`, `Option<Box<T>>` and
/// // `Option<Id<T, O>>` are safe to zero-initialize, and
/// // we can simply write to the variable as normal:
/// *this.foo = foo;
/// *this.bar = 42;
///
/// // For others like `&u8`, `Box<T>` or `Id<T, O>`, we have
/// // to initialize them with `Ivar::write`:
/// Ivar::write(&mut this.string, s.copy());
///
/// // All the instance variables have been initialized; our
/// // initializer is sound
/// this
/// })
/// }
///
/// #[sel(foo)]
/// fn __get_foo(&self) -> u8 {
/// *self.foo
/// }
///
/// #[sel(string)]
/// fn __get_string(&self) -> *mut NSString {
/// Id::autorelease_return((*self.string).copy())
/// }
///
/// #[sel(myClassMethod)]
/// fn __my_class_method() -> bool {
/// true
/// }
/// }
///
/// unsafe impl Protocol<NSCopying> for MyCustomObject {
/// #[sel(copyWithZone:)]
/// fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self {
/// let mut obj = Self::new(*self.foo);
/// *obj.bar = *self.bar;
/// obj.autorelease_return()
/// }
/// }
/// );
///
/// impl MyCustomObject {
/// pub fn new(foo: u8) -> Id<Self, Owned> {
/// let cls = Self::class();
/// unsafe { msg_send_id![msg_send_id![cls, alloc], initWithFoo: foo] }
/// }
///
/// pub fn get_foo(&self) -> u8 {
/// unsafe { msg_send![self, foo] }
/// }
///
/// pub fn get_string(&self) -> Id<NSString, Shared> {
/// unsafe { msg_send_id![self, string] }
/// }
///
/// pub fn my_class_method() -> bool {
/// unsafe { msg_send![Self::class(), myClassMethod] }
/// }
/// }
///
/// unsafe impl NSCopying for MyCustomObject {
/// type Ownership = Owned;
/// type Output = Self;
/// }
///
/// fn main() {
/// let obj = MyCustomObject::new(3);
/// assert_eq!(*obj.foo, 3);
/// assert_eq!(*obj.bar, 42);
/// assert_eq!(*obj.string, NSString::from_str("abc"));
///
/// let obj = obj.copy();
/// assert_eq!(obj.get_foo(), 3);
/// assert_eq!(obj.get_string(), NSString::from_str("abc"));
///
/// assert!(MyCustomObject::my_class_method());
/// }
/// ```
///
/// Approximately equivalent to the following ARC-enabled Objective-C code.
///
/// ```text
/// #import <Foundation/Foundation.h>
///
/// @interface MyCustomObject: NSObject <NSCopying> {
/// // Public ivar
/// int bar;
/// }
///
/// - (instancetype)initWithFoo:(uint8_t)foo;
/// - (uint8_t)foo;
/// - (NSString*)string;
/// + (BOOL)myClassMethod;
///
/// @end
///
///
/// @implementation MyCustomObject {
/// // Private ivar
/// uint8_t foo;
/// NSString* _Nonnull string;
/// }
///
/// - (instancetype)initWithFoo:(uint8_t)foo_arg {
/// self = [super init];
/// if (self) {
/// self->foo = foo_arg;
/// self->bar = 42;
/// self->string = @"abc";
/// }
/// return self;
/// }
///
/// - (uint8_t)foo {
/// return self->foo; // Or just `foo`
/// }
///
/// - (NSString*)string {
/// return self->string;
/// }
///
/// + (BOOL)myClassMethod {
/// return YES;
/// }
///
/// // NSCopying
///
/// - (id)copyWithZone:(NSZone *)_zone {
/// MyCustomObject* obj = [[MyCustomObject alloc] initWithFoo: self->foo];
/// obj->bar = self->bar;
/// obj->string = self->string;
/// return obj;
/// }
///
/// @end
/// ```
///
/// [`extern_class!`]: crate::extern_class
/// [`declare`]: crate::declare
/// [`declare::Ivar`]: crate::declare::Ivar
#[doc(alias = "@interface")]
#[doc(alias = "@implementation")]
#[macro_export]
macro_rules! declare_class {
{
$(#[$m:meta])*
$v:vis struct $name:ident {
$($ivar_v:vis $ivar:ident: $ivar_ty:ty,)*
}
unsafe impl ClassType for $for:ty {
$(#[inherits($($inheritance_rest:ty),+)])?
type Super = $superclass:ty;
$(const NAME: &'static str = $name_const:literal;)?
}
$($methods:tt)*
} => {
$(
#[allow(non_camel_case_types)]
$ivar_v struct $ivar {
__priv: (),
}
unsafe impl $crate::declare::IvarType for $ivar {
type Type = $ivar_ty;
const NAME: &'static str = stringify!($ivar);
}
)*
$crate::__inner_extern_class! {
@__inner
$(#[$m])*
// SAFETY: Upheld by caller
$v struct $name () {
// SAFETY:
// - The ivars are in a type used as an Objective-C object.
// - The ivar is added to the class below.
// - Rust prevents having two fields with the same name.
// - Caller upholds that the ivars are properly initialized.
$($ivar_v $ivar: $crate::declare::Ivar<$ivar>,)*
}
unsafe impl () for $for {
INHERITS = [$superclass, $($($inheritance_rest,)+)? $crate::runtime::Object];
}
}
// Creation
unsafe impl ClassType for $for {
type Super = $superclass;
const NAME: &'static str = $crate::__select_name!($name; $($name_const)?);
fn class() -> &'static $crate::runtime::Class {
// TODO: Use `core::cell::LazyCell`
use $crate::__macro_helpers::Once;
static REGISTER_CLASS: Once = Once::new();
REGISTER_CLASS.call_once(|| {
let superclass = <$superclass as $crate::ClassType>::class();
let err_str = concat!(
"could not create new class ",
$crate::__select_name!($name; $($name_const)?),
". Perhaps a class with that name already exists?",
);
let mut builder = $crate::declare::ClassBuilder::new(Self::NAME, superclass).expect(err_str);
// Ivars
$(
builder.add_static_ivar::<$ivar>();
)*
// Check whether we need to add a `dealloc` method
if false $(
|| <<$ivar as $crate::declare::IvarType>::Type as $crate::declare::InnerIvarType>::__MAY_DROP
)* {
unsafe {
builder.add_method(
$crate::sel!(dealloc),
Self::__objc2_dealloc as unsafe extern "C" fn(_, _),
);
}
}
// Implement protocols and methods
$crate::__declare_class_methods!(
@register_out(builder)
$($methods)*
);
let _cls = builder.register();
});
// We just registered the class, so it should be available
$crate::runtime::Class::get(Self::NAME).unwrap()
}
#[inline]
fn as_super(&self) -> &Self::Super {
&self.__inner
}
#[inline]
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.__inner
}
}
impl $for {
// See the following links for more details:
// - <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#dealloc>
// - <https://developer.apple.com/documentation/objectivec/nsobject/1571947-dealloc>
// - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW2>
unsafe extern "C" fn __objc2_dealloc(&mut self, _cmd: $crate::runtime::Sel) {
$(
let ptr: *mut $crate::declare::Ivar<$ivar> = &mut self.$ivar;
// SAFETY: The ivar is valid, and since this is the
// `dealloc` method, we know the ivars are never going to
// be touched again.
unsafe { $crate::__macro_helpers::drop_in_place(ptr) };
)*
// Invoke the super class' `dealloc` method.
//
// Note: ARC does this automatically, so most Objective-C code
// in the wild don't contain this; but we don't have ARC, so
// we must do this.
unsafe { $crate::msg_send![super(self), dealloc] }
}
}
// Methods
$crate::__declare_class_methods!(
@method_out
$($methods)*
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __select_name {
($_name:ident; $name_const:literal) => {
$name_const
};
($name:ident;) => {
$crate::__macro_helpers::stringify!($name)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __declare_class_methods {
(@method_out) => {};
// With protocol
(
@method_out
$(#[$m:meta])*
unsafe impl Protocol<$protocol:ident> for $for:ty {
$($methods:tt)*
}
$($rest:tt)*
) => {
$(#[$m])*
impl $for {
$crate::__inner_declare_class! {
@rewrite_methods
@(method_out)
$($methods)*
}
}
$crate::__declare_class_methods!(
@method_out
$($rest)*
);
};
// Without protocol
(
@method_out
$(#[$m:meta])*
unsafe impl $for:ty {
$($methods:tt)*
}
$($rest:tt)*
) => {
$(#[$m])*
impl $for {
$crate::__inner_declare_class! {
@rewrite_methods
@(method_out)
$($methods)*
}
}
$crate::__declare_class_methods!(
@method_out
$($rest)*
);
};
(@register_out($builder:ident)) => {};
// With protocol
(
@register_out($builder:ident)
$(#[$m:meta])*
unsafe impl Protocol<$protocol:ident> for $for:ty {
$($methods:tt)*
}
$($rest:tt)*
) => {
// Implement protocol
let err_str = concat!("could not find protocol ", stringify!($protocol));
$builder.add_protocol($crate::runtime::Protocol::get(stringify!($protocol)).expect(err_str));
// SAFETY: Upheld by caller
unsafe {
$crate::__inner_declare_class! {
@rewrite_methods
@(register_out($builder))
$($methods)*
}
}
$crate::__declare_class_methods!(
@register_out($builder)
$($rest)*
);
};
// Without protocol
(
@register_out($builder:ident)
$(#[$m:meta])*
unsafe impl $for:ty {
$($methods:tt)*
}
$($rest:tt)*
) => {
// SAFETY: Upheld by caller
unsafe {
$crate::__inner_declare_class! {
@rewrite_methods
@(register_out($builder))
$($methods)*
}
}
$crate::__declare_class_methods!(
@register_out($builder)
$($rest)*
);
};
}

View file

@ -0,0 +1,491 @@
/// Create a new type to represent an Objective-C class.
///
/// This is similar to an `@interface` declaration in Objective-C.
///
/// The given struct name should correspond to a valid Objective-C class,
/// whose instances have the encoding [`Encoding::Object`]. (as an example:
/// `NSAutoreleasePool` does not have this!)
///
/// You must specify the superclass of this class, similar to how you would
/// in Objective-C.
///
/// Due to Rust trait limitations, specifying e.g. the superclass `NSData`
/// would not give you easy access to `NSObject`'s functionality. Therefore,
/// you may specify additional parts of the inheritance chain using the
/// `#[inherits(...)]` attribute.
///
/// [`Encoding::Object`]: crate::Encoding::Object
///
///
/// # Specification
///
/// The syntax is similar enough to Rust syntax that if you invoke the macro
/// with parentheses (as opposed to curly brackets), [`rustfmt` will be able to
/// format the contents][rustfmt-macros].
///
/// This creates an opaque struct containing the superclass (which means that
/// auto traits are inherited from the superclass), and implements the
/// following traits for it to allow easier usage as an Objective-C object:
///
/// - [`RefEncode`][crate::RefEncode]
/// - [`Message`][crate::Message]
/// - [`ClassType`][crate::ClassType]
/// - [`Deref<Target = $superclass>`][core::ops::Deref]
/// - [`DerefMut`][core::ops::DerefMut]
/// - [`AsRef<$inheritance_chain>`][AsRef]
/// - [`AsMut<$inheritance_chain>`][AsMut]
/// - [`Borrow<$inheritance_chain>`][core::borrow::Borrow]
/// - [`BorrowMut<$inheritance_chain>`][core::borrow::BorrowMut]
///
/// The macro allows specifying fields on the struct, but _only_ zero-sized
/// types like [`PhantomData`] and [`declare::Ivar`] are allowed here!
///
/// [rustfmt-macros]: https://github.com/rust-lang/rustfmt/discussions/5437
/// [`PhantomData`]: core::marker::PhantomData
/// [`declare::Ivar`]: crate::declare::Ivar
///
///
/// # Safety
///
/// The specified superclass must be correct. The object must also respond to
/// standard memory management messages (this is upheld if [`NSObject`] is
/// part of its inheritance chain).
///
/// [`NSObject`]: crate::foundation::NSObject
///
///
/// # Examples
///
/// Create a new type to represent the `NSFormatter` class.
///
/// ```
/// use objc2::foundation::NSObject;
/// use objc2::rc::{Id, Shared};
/// use objc2::{ClassType, extern_class, msg_send_id};
/// #
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// extern_class!(
/// /// An example description.
/// #[derive(PartialEq, Eq, Hash)] // Uses the superclass' implementation
/// // Specify the class and struct name to be used
/// pub struct NSFormatter;
///
/// // Specify the superclass, in this case `NSObject`
/// unsafe impl ClassType for NSFormatter {
/// type Super = NSObject;
/// }
/// );
///
/// // Provided by the implementation of `ClassType`
/// let cls = NSFormatter::class();
///
/// // `NSFormatter` implements `Message`:
/// let obj: Id<NSFormatter, Shared> = unsafe { msg_send_id![cls, new] };
/// ```
///
/// Represent the `NSDateFormatter` class, using the `NSFormatter` type we
/// declared previously to specify as its superclass.
///
/// ```
/// use objc2::foundation::NSObject;
/// use objc2::{extern_class, ClassType};
/// #
/// # extern_class!(
/// # #[derive(PartialEq, Eq, Hash)]
/// # pub struct NSFormatter;
/// #
/// # unsafe impl ClassType for NSFormatter {
/// # type Super = NSObject;
/// # }
/// # );
///
/// extern_class!(
/// #[derive(PartialEq, Eq, Hash)]
/// pub struct NSDateFormatter;
///
/// unsafe impl ClassType for NSDateFormatter {
/// // Specify the correct inheritance chain
/// #[inherits(NSObject)]
/// type Super = NSFormatter;
/// }
/// );
/// ```
///
/// See the source code of `objc2::foundation` in general for more examples.
#[doc(alias = "@interface")]
#[macro_export]
macro_rules! extern_class {
(
$(#[$m:meta])*
$v:vis struct $name:ident;
unsafe impl ClassType for $for:ty {
$(#[inherits($($inheritance_rest:ty),+)])?
type Super = $superclass:ty;
}
) => {
// Just shorthand syntax for the following
$crate::extern_class!(
$(#[$m])*
$v struct $name {}
unsafe impl ClassType for $for {
$(#[inherits($($inheritance_rest),+)])?
type Super = $superclass;
}
);
};
(
$(#[$m:meta])*
$v:vis struct $name:ident {
$($field_vis:vis $field:ident: $field_ty:ty,)*
}
unsafe impl ClassType for $for:ty {
$(#[inherits($($inheritance_rest:ty),+)])?
type Super = $superclass:ty;
}
) => {
$crate::__inner_extern_class!(
$(#[$m])*
$v struct $name<> {
$($field_vis $field: $field_ty,)*
}
unsafe impl<> ClassType for $for {
$(#[inherits($($inheritance_rest),+)])?
type Super = $superclass;
}
);
const _: () = {
if $crate::__macro_helpers::size_of::<$name>() != 0 {
panic!(concat!(
"the struct ",
stringify!($name),
" is not zero-sized!",
))
}
};
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_as_ref_borrow {
{
impl ($($t:tt)*) for $for:ty;
} => {};
{
impl ($($t:tt)*) for $for:ty; $item:ty, $($tail:ty,)*
} => {
impl<$($t)*> $crate::__macro_helpers::AsRef<$item> for $for {
#[inline]
fn as_ref(&self) -> &$item {
// Triggers Deref coercion depending on return type
&*self
}
}
impl<$($t)*> $crate::__macro_helpers::AsMut<$item> for $for {
#[inline]
fn as_mut(&mut self) -> &mut $item {
// Triggers DerefMut coercion depending on return type
&mut *self
}
}
// Borrow and BorrowMut are correct, since subclasses behaves
// identical to the class they inherit (message sending doesn't care).
//
// In particular, `Eq`, `Ord` and `Hash` all give the same results
// after borrow.
impl<$($t)*> $crate::__macro_helpers::Borrow<$item> for $for {
#[inline]
fn borrow(&self) -> &$item {
// Triggers Deref coercion depending on return type
&*self
}
}
impl<$($t)*> $crate::__macro_helpers::BorrowMut<$item> for $for {
#[inline]
fn borrow_mut(&mut self) -> &mut $item {
// Triggers Deref coercion depending on return type
&mut *self
}
}
$crate::__impl_as_ref_borrow! {
impl ($($t)*) for $for; $($tail,)*
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __inner_extern_class {
// TODO: Expose this variant in the `object` macro.
(
$(#[$m:meta])*
$v:vis struct $name:ident<$($t_struct:ident $(: $b_struct:ident $(= $default:ty)?)?),*> {
$($field_vis:vis $field:ident: $field_ty:ty,)*
}
unsafe impl<$($t_for:ident $(: $b_for:ident)?),*> ClassType for $for:ty {
$(#[inherits($($inheritance_rest:ty),+)])?
type Super = $superclass:ty;
}
) => {
$crate::__inner_extern_class! {
@__inner
$(#[$m])*
$v struct $name ($($t_struct $(: $b_struct $(= $default)?)?),*) {
$($field_vis $field: $field_ty,)*
}
unsafe impl ($($t_for $(: $b_for)?),*) for $for {
INHERITS = [$superclass, $($($inheritance_rest,)+)? $crate::runtime::Object];
}
}
unsafe impl<$($t_for $(: $b_for)?),*> ClassType for $for {
type Super = $superclass;
const NAME: &'static str = stringify!($name);
#[inline]
fn class() -> &'static $crate::runtime::Class {
$crate::class!($name)
}
#[inline]
fn as_super(&self) -> &Self::Super {
&self.__inner
}
#[inline]
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.__inner
}
}
};
(
@__inner
$(#[$m:meta])*
$v:vis struct $name:ident ($($t_struct:tt)*) {
$($field_vis:vis $field:ident: $field_ty:ty,)*
}
unsafe impl ($($t:tt)*) for $for:ty {
INHERITS = [$superclass:ty $(, $inheritance_rest:ty)*];
}
) => {
$(#[$m])*
// TODO: repr(transparent) when the inner pointer is no longer a ZST.
#[repr(C)]
$v struct $name<$($t_struct)*> {
__inner: $superclass,
// Additional fields (should only be zero-sized PhantomData or ivars).
$($field_vis $field: $field_ty,)*
}
// SAFETY:
// - The item is FFI-safe with `#[repr(C)]`.
// - The encoding is taken from the inner item, and caller verifies
// that it actually inherits said object.
// - The rest of the struct's fields are ZSTs, so they don't influence
// the layout.
unsafe impl<$($t)*> $crate::RefEncode for $for {
const ENCODING_REF: $crate::Encoding
= <$superclass as $crate::RefEncode>::ENCODING_REF;
}
// SAFETY: This is essentially just a newtype wrapper over `Object`
// (we even ensure that `Object` is always last in our inheritance
// tree), so it is always safe to reinterpret as that.
//
// That the object must work with standard memory management is upheld
// by the caller.
unsafe impl<$($t)*> $crate::Message for $for {}
// SAFETY: An instance can always be _used_ in exactly the same way as
// its superclasses (though not necessarily _constructed_ in the same
// way, but `Deref` doesn't allow this).
//
// Remember; while we (the Rust side) may intentionally be forgetting
// which instance we're holding, the Objective-C side will remember,
// and will always dispatch to the correct method implementations.
//
// Any lifetime information that the object may have been holding is
// safely kept in the returned reference.
//
// Generics are discarded (for example in the case of `&NSArray<T, O>`
// to `&NSObject`), but if the generic contained a lifetime, that
// lifetime is still included in the returned reference.
//
// Note that you can easily have two different variables pointing to
// the same object, `x: &T` and `y: &T::Target`, and this would be
// perfectly safe!
impl<$($t)*> $crate::__macro_helpers::Deref for $for {
type Target = $superclass;
#[inline]
fn deref(&self) -> &Self::Target {
&self.__inner
}
}
// SAFETY: Mutability does not change anything in the above
// consideration, the lifetime of `&mut Self::Target` is still tied to
// `&mut self`.
//
// Usually we don't want to allow `&mut` of immutable objects like
// `NSString`, because their `NSCopying` implementation returns the
// same object, and would violate aliasing rules.
//
// But `&mut NSMutableString` -> `&mut NSString` safe, since the
// `NSCopying` implementation of `NSMutableString` is used, and that
// is guaranteed to return a different object.
impl<$($t)*> $crate::__macro_helpers::DerefMut for $for {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.__inner
}
}
impl<$($t)*> $crate::__macro_helpers::AsRef<Self> for $for {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl<$($t)*> $crate::__macro_helpers::AsMut<Self> for $for {
#[inline]
fn as_mut(&mut self) -> &mut Self {
self
}
}
$crate::__impl_as_ref_borrow! {
impl ($($t)*) for $for; $superclass, $($inheritance_rest,)*
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __attribute_helper {
// Convert a set of attributes described with `@[...]` to `#[...]`, while
// parsing out the `sel(...)` attribute.
{
@strip_sel
@[sel($($_sel_args:tt)*)]
$(@[$($m_rest:tt)*])*
$(#[$($m:tt)*])*
($($fn:tt)*)
} => {
$crate::__attribute_helper! {
@strip_sel
$(@[$($m_rest)*])*
$(#[$($m)*])*
($($fn)*)
}
};
{
@strip_sel
@[$($m_checked:tt)*]
$(@[$($m_rest:tt)*])*
$(#[$($m:tt)*])*
($($fn:tt)*)
} => {
$crate::__attribute_helper! {
@strip_sel
$(@[$($m_rest)*])*
$(#[$($m)*])*
#[$($m_checked)*]
($($fn)*)
}
};
{
@strip_sel
$(#[$($m:tt)*])*
($($fn:tt)*)
} => {
$(#[$($m)*])*
$($fn)*
};
// Extract the `#[sel(...)]` attribute and send it to another macro
{
@extract_sel
($out_macro:path)
(
#[sel($($sel:tt)*)]
$($rest:tt)*
)
$($macro_args:tt)*
} => {{
$crate::__attribute_helper! {
@extract_sel_duplicate
$($rest)*
}
$out_macro!(
$($macro_args)*
@($($sel)*)
)
}};
{
@extract_sel
($out_macro:path)
(
#[$($m_checked:tt)*]
$($rest:tt)*
)
$($macro_args:tt)*
} => {{
$crate::__attribute_helper! {
@extract_sel
($out_macro)
($($rest)*)
$($macro_args)*
}
}};
{
@extract_sel
($out_macro:path)
()
$($macro_args:tt)*
} => {{
compile_error!("Must specify the desired selector using `#[sel(...)]`");
}};
{
@extract_sel_duplicate
#[sel($($_sel_args:tt)*)]
$($rest:tt)*
} => {{
compile_error!("Cannot not specify a selector twice!");
}};
{
@extract_sel_duplicate
#[$($m_checked:tt)*]
$($rest:tt)*
} => {{
$crate::__attribute_helper! {
@extract_sel_duplicate
$($rest)*
}
}};
{@extract_sel_duplicate} => {};
}

View file

@ -0,0 +1,395 @@
/// Define methods on an external class.
///
/// This is a convenience macro to easily generate associated functions and
/// methods that call [`msg_send!`][crate::msg_send] appropriately.
///
///
/// # Specification
///
/// Within the `impl` block you can define two types of functions without
/// bodies; ["associated functions"] and ["methods"]. These are then mapped to
/// the Objective-C equivalents "class methods" and "instance methods", and an
/// appropriate body is created for you. In particular, if you use `self` your
/// method will assumbed to be an instance method, and if you don't it will be
/// assumed to be a class method.
///
/// The desired selector can be specified using the `#[sel(my:selector:)]`
/// attribute. The name of the function doesn't matter.
///
/// If you specify a function/method with a body, the macro will simply ignore
/// it.
///
/// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods
/// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods
///
///
/// # Safety
///
/// You must ensure that any methods you declare with the `#[sel(...)]`
/// attribute upholds the safety guarantees decribed in the
/// [`msg_send!`][crate::msg_send] macro, _or_ are marked `unsafe`.
///
///
/// # Examples
///
/// Let's create a quick interface to the [`NSCalendar`] class:
///
/// [`NSCalendar`]: https://developer.apple.com/documentation/foundation/nscalendar?language=objc
///
/// ```
/// use objc2::foundation::{NSObject, NSRange, NSString, NSUInteger};
/// use objc2::rc::{Id, Shared};
/// use objc2::{extern_class, extern_methods, msg_send_id, Encode, Encoding, ClassType};
/// #
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// extern_class!(
/// #[derive(PartialEq, Eq, Hash)]
/// pub struct NSCalendar;
///
/// unsafe impl ClassType for NSCalendar {
/// type Super = NSObject;
/// }
/// );
///
/// pub type NSCalendarIdentifier = NSString;
///
/// #[repr(usize)] // NSUInteger
/// pub enum NSCalendarUnit {
/// Hour = 32,
/// Minute = 64,
/// Second = 128,
/// // TODO: More units
/// }
///
/// unsafe impl Encode for NSCalendarUnit {
/// const ENCODING: Encoding = usize::ENCODING;
/// }
///
/// extern_methods!(
/// /// Creation methods.
/// // TODO: Support methods returning `Id`
/// unsafe impl NSCalendar {
/// pub fn current() -> Id<Self, Shared> {
/// unsafe { msg_send_id![Self::class(), currentCalendar] }
/// }
///
/// pub fn new(identifier: &NSCalendarIdentifier) -> Id<Self, Shared> {
/// unsafe {
/// msg_send_id![
/// msg_send_id![Self::class(), alloc],
/// initWithCalendarIdentifier: identifier,
/// ]
/// }
/// }
/// }
///
/// /// Accessor methods.
/// // SAFETY: `first_weekday` is correctly defined
/// unsafe impl NSCalendar {
/// #[sel(firstWeekday)]
/// pub fn first_weekday(&self) -> NSUInteger;
///
/// pub fn am_symbol(&self) -> Id<NSString, Shared> {
/// unsafe { msg_send_id![self, amSymbol] }
/// }
///
/// #[sel(date:matchesComponents:)]
/// // `unsafe` because we don't have definitions for `NSDate` and
/// // `NSDateComponents` yet, so the user must ensure that is what's
/// // passed.
/// pub unsafe fn date_matches(&self, date: &NSObject, components: &NSObject) -> bool;
///
/// #[sel(maximumRangeOfUnit:)]
/// pub fn max_range(&self, unit: NSCalendarUnit) -> NSRange;
/// }
/// );
/// ```
///
/// The `extern_methods!` declaration then becomes:
///
/// ```
/// # use objc2::foundation::{NSObject, NSRange, NSString, NSUInteger};
/// # use objc2::rc::{Id, Shared};
/// # use objc2::{extern_class, extern_methods, msg_send_id, Encode, Encoding, ClassType};
/// #
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// #
/// # extern_class!(
/// # #[derive(PartialEq, Eq, Hash)]
/// # pub struct NSCalendar;
/// #
/// # unsafe impl ClassType for NSCalendar {
/// # type Super = NSObject;
/// # }
/// # );
/// #
/// # pub type NSCalendarIdentifier = NSString;
/// #
/// # #[repr(usize)] // NSUInteger
/// # pub enum NSCalendarUnit {
/// # Hour = 32,
/// # Minute = 64,
/// # Second = 128,
/// # // TODO: More units
/// # }
/// #
/// # unsafe impl Encode for NSCalendarUnit {
/// # const ENCODING: Encoding = usize::ENCODING;
/// # }
/// #
/// # use objc2::msg_send;
/// /// Creation methods.
/// impl NSCalendar {
/// pub fn current() -> Id<Self, Shared> {
/// unsafe { msg_send_id![Self::class(), currentCalendar] }
/// }
///
/// pub fn new(identifier: &NSCalendarIdentifier) -> Id<Self, Shared> {
/// unsafe {
/// msg_send_id![
/// msg_send_id![Self::class(), alloc],
/// initWithCalendarIdentifier: identifier,
/// ]
/// }
/// }
/// }
///
/// /// Accessor methods.
/// impl NSCalendar {
/// pub fn first_weekday(&self) -> NSUInteger {
/// unsafe { msg_send![self, firstWeekday] }
/// }
///
/// pub fn am_symbol(&self) -> Id<NSString, Shared> {
/// unsafe { msg_send_id![self, amSymbol] }
/// }
///
/// pub unsafe fn date_matches(&self, date: &NSObject, components: &NSObject) -> bool {
/// unsafe { msg_send![self, date: date, matchesComponents: components] }
/// }
///
/// pub fn max_range(&self, unit: NSCalendarUnit) -> NSRange {
/// unsafe { msg_send![self, maximumRangeOfUnit: unit] }
/// }
/// }
/// ```
#[macro_export]
macro_rules! extern_methods {
(
$(
$(#[$impl_m:meta])*
unsafe impl<$($t:ident $(: $b:ident $(+ $rest:ident)*)?),*> $type:ty {
$($methods:tt)*
}
)+
) => {
$(
$(#[$impl_m])*
impl<$($t $(: $b $(+ $rest)*)?),*> $type {
$crate::__inner_extern_methods! {
@rewrite_methods
$($methods)*
}
}
)+
};
(
$(
$(#[$impl_m:meta])*
unsafe impl $type:ty {
$($methods:tt)*
}
)+
) => {
$(
$(#[$impl_m])*
impl $type {
$crate::__inner_extern_methods! {
@rewrite_methods
$($methods)*
}
}
)+
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __inner_extern_methods {
{@rewrite_methods} => {};
{
@rewrite_methods
// Unsafe variant
$(#[$($m:tt)*])*
$v:vis unsafe fn $name:ident($($args:tt)*) $(-> $ret:ty)?;
$($rest:tt)*
} => {
// Detect instance vs. class method.
$crate::__rewrite_self_arg! {
($crate::__inner_extern_methods)
($($args)*)
@method_out
@($(#[$($m)*])*)
@($v unsafe fn $name($($args)*) $(-> $ret)?)
// Will add @(kind)
// Will add @(args_start)
// Will add @(args_rest)
}
$crate::__inner_extern_methods! {
@rewrite_methods
$($rest)*
}
};
{
@rewrite_methods
// Safe variant
$(#[$($m:tt)*])*
$v:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)?;
$($rest:tt)*
} => {
$crate::__rewrite_self_arg! {
($crate::__inner_extern_methods)
($($args)*)
@method_out
@($(#[$($m)*])*)
@($v fn $name($($args)*) $(-> $ret)?)
}
$crate::__inner_extern_methods! {
@rewrite_methods
$($rest)*
}
};
{
@rewrite_methods
// Other items that people might want to put here (e.g. functions with
// a body).
$associated_item:item
$($rest:tt)*
} => {
$associated_item
$crate::__inner_extern_methods! {
@rewrite_methods
$($rest)*
}
};
{
@method_out
@($(#[$($m:tt)*])*)
@($($function_start:tt)*)
@($($kind:tt)*)
@($($args_start:tt)*)
@($($args_rest:tt)*)
} => {
$crate::__attribute_helper! {
@strip_sel
$(@[$($m)*])*
($($function_start)* {
#[allow(unused_unsafe)]
unsafe {
$crate::__attribute_helper! {
@extract_sel
($crate::__inner_extern_methods)
($(#[$($m)*])*)
@unsafe_method_body
@($($kind)*)
@($($args_start)*)
@($($args_rest)*)
}
}
})
}
};
{
@unsafe_method_body
@(instance_method)
@(
$self:ident: $self_ty:ty,
_: $sel_ty:ty,
)
@($($args_rest:tt)*)
@($($sel:tt)*)
} => {
$crate::__collect_msg_send!(
$crate::msg_send;
$self;
($($sel)*);
($($args_rest)*);
)
};
{
@unsafe_method_body
@(class_method)
@(
_: $cls_ty:ty,
_: $sel_ty:ty,
)
@($($args_rest:tt)*)
@($($sel:tt)*)
} => {
$crate::__collect_msg_send!(
$crate::msg_send;
Self::class();
($($sel)*);
($($args_rest)*);
)
};
}
/// Zip selector and arguments, and forward to macro.
#[doc(hidden)]
#[macro_export]
macro_rules! __collect_msg_send {
// Selector with no arguments
(
$macro:path;
$obj:expr;
($sel:ident);
();
) => {{
$macro![$obj, $sel]
}};
// Base case
(
$macro:path;
$obj:expr;
();
();
$($output:tt)+
) => {{
$macro![$obj, $($output)+]
}};
// tt-munch each argument
(
$macro:path;
$obj:expr;
($sel:ident : $($sel_rest:tt)*);
($arg:ident: $arg_ty:ty $(, $($args_rest:tt)*)?);
$($output:tt)*
) => {{
$crate::__collect_msg_send!(
$macro;
$obj;
($($sel_rest)*);
($($($args_rest)*)?);
$($output)*
$sel: $arg,
)
}};
// If couldn't zip selector and arguments, show useful error message
($($_any:tt)*) => {{
compile_error!("Number of arguments in function and selector did not match!")
}};
}

View file

@ -0,0 +1,32 @@
use core::mem;
use super::MsgSendFn;
use crate::ffi;
use crate::runtime::Imp;
use crate::{Encode, Encoding};
/// Double-word sized fundamental data types don't use stret, but any
/// composite type larger than 4 bytes does.
///
/// <https://web.archive.org/web/20191016000656/http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf>
/// <https://developer.arm.com/documentation/ihi0042/latest>
unsafe impl<T: Encode> MsgSendFn for T {
const MSG_SEND: Imp = {
if let Encoding::LongLong | Encoding::ULongLong | Encoding::Double = T::ENCODING {
ffi::objc_msgSend
} else if mem::size_of::<T>() <= 4 {
ffi::objc_msgSend
} else {
ffi::objc_msgSend_stret
}
};
const MSG_SEND_SUPER: Imp = {
if let Encoding::LongLong | Encoding::ULongLong | Encoding::Double = T::ENCODING {
ffi::objc_msgSendSuper
} else if mem::size_of::<T>() <= 4 {
ffi::objc_msgSendSuper
} else {
ffi::objc_msgSendSuper_stret
}
};
}

View file

@ -0,0 +1,12 @@
use super::MsgSendFn;
use crate::ffi;
use crate::runtime::Imp;
use crate::Encode;
/// `objc_msgSend_stret` is not even available in arm64.
///
/// <https://twitter.com/gparker/status/378079715824660480>
unsafe impl<T: Encode> MsgSendFn for T {
const MSG_SEND: Imp = ffi::objc_msgSend;
const MSG_SEND_SUPER: Imp = ffi::objc_msgSendSuper;
}

View file

@ -0,0 +1,61 @@
use super::conditional_try;
use crate::encode::Encode;
use crate::ffi;
use crate::runtime::{Class, Imp, Object, Sel};
use crate::MessageArguments;
#[cfg(target_arch = "x86")]
#[path = "x86.rs"]
mod arch;
#[cfg(target_arch = "x86_64")]
#[path = "x86_64.rs"]
mod arch;
#[cfg(target_arch = "arm")]
#[path = "arm.rs"]
mod arch;
#[cfg(target_arch = "aarch64")]
#[path = "arm64.rs"]
mod arch;
/// On the above architectures we can statically find the correct method to
/// call from the return type, by looking at its `Encode` implementation.
#[allow(clippy::missing_safety_doc)]
unsafe trait MsgSendFn: Encode {
const MSG_SEND: Imp;
const MSG_SEND_SUPER: Imp;
}
#[inline]
#[track_caller]
pub(crate) unsafe fn send_unverified<A, R>(receiver: *mut Object, sel: Sel, args: A) -> R
where
A: MessageArguments,
R: Encode,
{
let msg_send_fn = R::MSG_SEND;
unsafe { conditional_try(|| A::__invoke(msg_send_fn, receiver, sel, args)) }
}
#[inline]
#[track_caller]
pub(crate) unsafe fn send_super_unverified<A, R>(
receiver: *mut Object,
superclass: &Class,
sel: Sel,
args: A,
) -> R
where
A: MessageArguments,
R: Encode,
{
let superclass: *const Class = superclass;
let mut sup = ffi::objc_super {
receiver: receiver.cast(),
super_class: superclass.cast(),
};
let receiver: *mut ffi::objc_super = &mut sup;
let receiver = receiver.cast();
let msg_send_fn = R::MSG_SEND_SUPER;
unsafe { conditional_try(|| A::__invoke(msg_send_fn, receiver, sel, args)) }
}

View file

@ -0,0 +1,31 @@
use core::mem;
use super::MsgSendFn;
use crate::ffi;
use crate::runtime::Imp;
use crate::{Encode, Encoding};
/// Structures 1 or 2 bytes in size are placed in EAX.
/// Structures 4 or 8 bytes in size are placed in: EAX and EDX.
/// Structures of other sizes are placed at the address supplied by the caller.
///
/// <https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/130-IA-32_Function_Calling_Conventions/IA32.html>
unsafe impl<T: Encode> MsgSendFn for T {
const MSG_SEND: Imp = {
// See https://github.com/apple-oss-distributions/objc4/blob/objc4-818.2/runtime/message.h#L156-L172
if let Encoding::Float | Encoding::Double | Encoding::LongDouble = T::ENCODING {
ffi::objc_msgSend_fpret
} else if let 0 | 1 | 2 | 4 | 8 = mem::size_of::<T>() {
ffi::objc_msgSend
} else {
ffi::objc_msgSend_stret
}
};
const MSG_SEND_SUPER: Imp = {
if let 0 | 1 | 2 | 4 | 8 = mem::size_of::<T>() {
ffi::objc_msgSendSuper
} else {
ffi::objc_msgSendSuper_stret
}
};
}

View file

@ -0,0 +1,33 @@
use core::mem;
use super::MsgSendFn;
use crate::ffi;
use crate::runtime::Imp;
use crate::{Encode, Encoding};
/// If the size of an object is larger than two eightbytes, it has class
/// MEMORY. If the type has class MEMORY, then the caller provides space for
/// the return value and passes the address of this storage.
///
/// <https://www.uclibc.org/docs/psABI-x86_64.pdf>
unsafe impl<T: Encode> MsgSendFn for T {
const MSG_SEND: Imp = {
// See https://github.com/apple-oss-distributions/objc4/blob/objc4-818.2/runtime/message.h#L156-L172
if let Encoding::LongDouble = T::ENCODING {
ffi::objc_msgSend_fpret
} else if let Encoding::LongDoubleComplex = T::ENCODING {
ffi::objc_msgSend_fp2ret
} else if mem::size_of::<T>() <= 16 {
ffi::objc_msgSend
} else {
ffi::objc_msgSend_stret
}
};
const MSG_SEND_SUPER: Imp = {
if mem::size_of::<T>() <= 16 {
ffi::objc_msgSendSuper
} else {
ffi::objc_msgSendSuper_stret
}
};
}

View file

@ -0,0 +1,69 @@
use core::hint;
use core::mem;
use super::conditional_try;
use crate::encode::Encode;
use crate::ffi;
use crate::runtime::{Class, Imp, Object, Sel};
use crate::MessageArguments;
#[inline]
fn unwrap_msg_send_fn(msg_send_fn: Option<Imp>) -> Imp {
match msg_send_fn {
Some(msg_send_fn) => msg_send_fn,
None => {
// SAFETY: This will never be NULL, even if the selector is not
// found a callable function pointer will still be returned!
//
// `clang` doesn't insert a NULL check here either.
unsafe { hint::unreachable_unchecked() }
}
}
}
#[track_caller]
pub(crate) unsafe fn send_unverified<A, R>(receiver: *mut Object, sel: Sel, args: A) -> R
where
A: MessageArguments,
R: Encode,
{
// If `receiver` is NULL, objc_msg_lookup will return a standard C-method
// taking two arguments, the receiver and the selector. Transmuting and
// calling such a function with multiple parameters is UB, so instead we
// just return NULL directly.
if receiver.is_null() {
// SAFETY: Caller guarantees that messages to NULL-receivers only
// return pointers, and a mem::zeroed pointer is just a NULL-pointer.
return unsafe { mem::zeroed() };
}
let msg_send_fn = unsafe { ffi::objc_msg_lookup(receiver.cast(), sel.as_ptr()) };
let msg_send_fn = unwrap_msg_send_fn(msg_send_fn);
unsafe { conditional_try(|| A::__invoke(msg_send_fn, receiver, sel, args)) }
}
#[track_caller]
pub(crate) unsafe fn send_super_unverified<A, R>(
receiver: *mut Object,
superclass: &Class,
sel: Sel,
args: A,
) -> R
where
A: MessageArguments,
R: Encode,
{
if receiver.is_null() {
// SAFETY: Same as in `send_unverified`.
return unsafe { mem::zeroed() };
}
let superclass: *const Class = superclass;
let sup = ffi::objc_super {
receiver: receiver.cast(),
super_class: superclass.cast(),
};
let msg_send_fn = unsafe { ffi::objc_msg_lookup_super(&sup, sel.as_ptr()) };
let msg_send_fn = unwrap_msg_send_fn(msg_send_fn);
unsafe { conditional_try(|| A::__invoke(msg_send_fn, receiver, sel, args)) }
}

View file

@ -0,0 +1,543 @@
use core::mem;
use core::mem::ManuallyDrop;
use core::ptr::NonNull;
use crate::encode::{Encode, EncodeArguments, EncodeConvert, RefEncode};
use crate::rc::{Id, Owned, Ownership};
use crate::runtime::{Class, Imp, Object, Sel};
use crate::ClassType;
#[cfg(feature = "catch-all")]
#[track_caller]
unsafe fn conditional_try<R: EncodeConvert>(f: impl FnOnce() -> R) -> R {
let f = core::panic::AssertUnwindSafe(f);
match unsafe { crate::exception::catch(f) } {
Ok(r) => r,
Err(exception) => {
if let Some(exception) = exception {
panic!("uncaught {:?}", exception)
} else {
panic!("uncaught exception nil")
}
}
}
}
#[cfg(not(feature = "catch-all"))]
#[inline]
#[track_caller]
unsafe fn conditional_try<R: EncodeConvert>(f: impl FnOnce() -> R) -> R {
f()
}
#[cfg(feature = "verify_message")]
#[track_caller]
fn panic_verify(cls: &Class, sel: Sel, err: crate::VerificationError) -> ! {
panic!(
"invalid message send to {}[{:?} {:?}]: {}",
if cls.is_metaclass() { "+" } else { "-" },
cls,
sel,
err
)
}
#[cfg(feature = "apple")]
#[path = "apple/mod.rs"]
mod platform;
#[cfg(feature = "gnustep-1-7")]
#[path = "gnustep.rs"]
mod platform;
use self::platform::{send_super_unverified, send_unverified};
/// Types that can be sent Objective-C messages.
///
/// Implementing this provides [`MessageReceiver`] implementations for common
/// pointer types and references to the type, which allows using them as the
/// receiver (first argument) in the [`msg_send!`][`crate::msg_send`] macro.
///
/// This trait also allows the object to be used in [`rc::Id`][`Id`].
///
/// This is a subtrait of [`RefEncode`], meaning the type must also implement
/// that, almost always as [`Encoding::Object`].
///
/// [`Encoding::Object`]: crate::Encoding::Object
///
///
/// # Safety
///
/// The type must represent an Objective-C object, meaning it:
/// - Must be valid to reinterpret as [`runtime::Object`][`Object`].
/// - Must be able to be the receiver of an Objective-C message sent with
/// [`objc_msgSend`] or similar.
/// - Must respond to the standard memory management `retain`, `release` and
/// `autorelease` messages.
///
/// [`objc_msgSend`]: https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend
///
///
/// # Example
///
/// ```
/// use objc2::runtime::Object;
/// use objc2::{Encoding, Message, RefEncode};
///
/// #[repr(C)]
/// struct MyObject {
/// // This has the exact same layout as `Object`
/// inner: Object
/// }
///
/// unsafe impl RefEncode for MyObject {
/// const ENCODING_REF: Encoding = Encoding::Object;
/// }
///
/// unsafe impl Message for MyObject {}
///
/// // `*mut MyObject` and other pointer/reference types to the object can
/// // now be used in `msg_send!`
/// //
/// // And `Id<MyObject, O>` can now be constructed.
/// ```
pub unsafe trait Message: RefEncode {}
unsafe impl Message for Object {}
// TODO: Make this fully private
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl<T: Message + ?Sized> Sealed for *const T {}
impl<T: Message + ?Sized> Sealed for *mut T {}
impl<T: Message + ?Sized> Sealed for NonNull<T> {}
impl<'a, T: Message + ?Sized> Sealed for &'a T {}
impl<'a, T: Message + ?Sized> Sealed for &'a mut T {}
impl<'a, T: Message + ?Sized, O: Ownership> Sealed for &'a Id<T, O> {}
impl<'a, T: Message + ?Sized> Sealed for &'a mut Id<T, Owned> {}
impl<T: Message + ?Sized, O: Ownership> Sealed for ManuallyDrop<Id<T, O>> {}
impl Sealed for *const Class {}
impl<'a> Sealed for &'a Class {}
}
/// Types that can directly be used as the receiver of Objective-C messages.
///
/// Examples include objects, classes, and blocks.
///
/// This is a sealed trait (for now) that is automatically implemented for
/// pointers to types implementing [`Message`], so that code can be generic
/// over the message receiver.
///
/// This is mostly an implementation detail; you'll want to implement
/// [`Message`] for your type instead.
///
///
/// # Safety
///
/// This is a sealed trait, and should not need to be implemented. Open an
/// issue if you know a use-case where this restrition should be lifted!
pub unsafe trait MessageReceiver: private::Sealed + Sized {
#[doc(hidden)]
type __Inner: ?Sized;
#[doc(hidden)]
fn __as_raw_receiver(self) -> *mut Object;
/// Sends a message to self with the given selector and arguments.
///
/// The correct version of `objc_msgSend` will be chosen based on the
/// return type. For more information, see the section on "Sending
/// Messages" in Apple's [documentation][runtime].
///
/// If the selector is known at compile-time, it is recommended to use the
/// [`msg_send!`] macro rather than this method.
///
/// [runtime]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
///
///
/// # Safety
///
/// This shares the same safety requirements as [`msg_send!`].
///
/// The added invariant is that the selector must take the same number of
/// arguments as is given.
///
/// [`msg_send!`]: crate::msg_send
#[inline]
#[track_caller]
unsafe fn send_message<A, R>(self, sel: Sel, args: A) -> R
where
A: MessageArguments,
R: EncodeConvert,
{
let this = self.__as_raw_receiver();
// TODO: Always enable this when `debug_assertions` are on.
#[cfg(feature = "verify_message")]
{
// SAFETY: Caller ensures only valid or NULL pointers.
let this = unsafe { this.as_ref() };
let cls = if let Some(this) = this {
this.class()
} else {
panic!("messsaging {:?} to nil", sel);
};
if let Err(err) = cls.verify_sel::<A, R>(sel) {
panic_verify(cls, sel, err);
}
}
unsafe { EncodeConvert::__from_inner(send_unverified(this, sel, args)) }
}
/// Sends a message to a specific superclass with the given selector and
/// arguments.
///
/// The correct version of `objc_msgSend_super` will be chosen based on the
/// return type. For more information, see the section on "Sending
/// Messages" in Apple's [documentation][runtime].
///
/// If the selector is known at compile-time, it is recommended to use the
/// [`msg_send!(super(...), ...)`] macro rather than this method.
///
/// [runtime]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
///
///
/// # Safety
///
/// This shares the same safety requirements as
/// [`msg_send!(super(...), ...)`].
///
/// The added invariant is that the selector must take the same number of
/// arguments as is given.
///
/// [`msg_send!(super(...), ...)`]: crate::msg_send
#[inline]
#[track_caller]
unsafe fn send_super_message<A, R>(self, superclass: &Class, sel: Sel, args: A) -> R
where
A: MessageArguments,
R: EncodeConvert,
{
let this = self.__as_raw_receiver();
#[cfg(feature = "verify_message")]
{
if this.is_null() {
panic!("messsaging {:?} to nil", sel);
}
if let Err(err) = superclass.verify_sel::<A, R>(sel) {
panic_verify(superclass, sel, err);
}
}
unsafe { EncodeConvert::__from_inner(send_super_unverified(this, superclass, sel, args)) }
}
#[inline]
#[track_caller]
#[doc(hidden)]
unsafe fn __send_super_message_static<A, R>(self, sel: Sel, args: A) -> R
where
Self::__Inner: ClassType,
<Self::__Inner as ClassType>::Super: ClassType,
A: MessageArguments,
R: EncodeConvert,
{
unsafe { self.send_super_message(<Self::__Inner as ClassType>::Super::class(), sel, args) }
}
}
// Note that we implement MessageReceiver for unsized types as well, this is
// to support `extern type`s in the future, not because we want to allow DSTs.
unsafe impl<T: Message + ?Sized> MessageReceiver for *const T {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
(self as *mut T).cast()
}
}
unsafe impl<T: Message + ?Sized> MessageReceiver for *mut T {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
self.cast()
}
}
unsafe impl<T: Message + ?Sized> MessageReceiver for NonNull<T> {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
self.as_ptr().cast()
}
}
unsafe impl<'a, T: Message + ?Sized> MessageReceiver for &'a T {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
let ptr: *const T = self;
(ptr as *mut T).cast()
}
}
unsafe impl<'a, T: Message + ?Sized> MessageReceiver for &'a mut T {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
let ptr: *mut T = self;
ptr.cast()
}
}
unsafe impl<'a, T: Message + ?Sized, O: Ownership> MessageReceiver for &'a Id<T, O> {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
(Id::as_ptr(self) as *mut T).cast()
}
}
unsafe impl<'a, T: Message + ?Sized> MessageReceiver for &'a mut Id<T, Owned> {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
Id::as_mut_ptr(self).cast()
}
}
unsafe impl<T: Message + ?Sized, O: Ownership> MessageReceiver for ManuallyDrop<Id<T, O>> {
type __Inner = T;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
Id::consume_as_ptr(self).cast()
}
}
unsafe impl MessageReceiver for *const Class {
type __Inner = Class;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
(self as *mut Class).cast()
}
}
unsafe impl<'a> MessageReceiver for &'a Class {
type __Inner = Class;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
let ptr: *const Class = self;
(ptr as *mut Class).cast()
}
}
/// Types that may be used as the arguments of an Objective-C message.
///
/// This is implemented for tuples of up to 12 arguments, where each argument
/// implements [`Encode`][crate::Encode] (or can be converted from one).
///
///
/// # Safety
///
/// This is a sealed trait, and should not need to be implemented. Open an
/// issue if you know a use-case where this restrition should be lifted!
pub unsafe trait MessageArguments: EncodeArguments {
/// Invoke an [`Imp`] with the given object, selector, and arguments.
///
/// This method is the primitive used when sending messages and should not
/// be called directly; instead, use the `msg_send!` macro or, in cases
/// with a dynamic selector, the [`MessageReceiver::send_message`] method.
#[doc(hidden)]
unsafe fn __invoke<R: Encode>(imp: Imp, obj: *mut Object, sel: Sel, args: Self) -> R;
}
macro_rules! message_args_impl {
($($a:ident: $t:ident),*) => (
unsafe impl<$($t: EncodeConvert),*> MessageArguments for ($($t,)*) {
#[inline]
unsafe fn __invoke<R: Encode>(imp: Imp, obj: *mut Object, sel: Sel, ($($a,)*): Self) -> R {
// The imp must be cast to the appropriate function pointer
// type before being called; the msgSend functions are not
// parametric, but instead "trampolines" to the actual
// method implementations.
#[cfg(not(feature = "unstable-c-unwind"))]
let imp: unsafe extern "C" fn(*mut Object, Sel $(, $t::__Inner)*) -> R = unsafe {
mem::transmute(imp)
};
#[cfg(feature = "unstable-c-unwind")]
let imp: unsafe extern "C-unwind" fn(*mut Object, Sel $(, $t::__Inner)*) -> R = unsafe {
mem::transmute(imp)
};
// TODO: On x86_64 it would be more efficient to use a GOT
// entry here (e.g. adding `nonlazybind` in LLVM).
// Same can be said of e.g. `objc_retain` and `objc_release`.
unsafe { imp(obj, sel $(, EncodeConvert::__into_inner($a))*) }
}
}
);
}
message_args_impl!();
message_args_impl!(a: A);
message_args_impl!(a: A, b: B);
message_args_impl!(a: A, b: B, c: C);
message_args_impl!(a: A, b: B, c: C, d: D);
message_args_impl!(a: A, b: B, c: C, d: D, e: E);
message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F);
message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
message_args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K
);
message_args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K,
l: L
);
#[cfg(test)]
mod tests {
use super::*;
use crate::rc::{Id, Owned};
use crate::test_utils;
use crate::{msg_send, msg_send_id};
#[allow(unused)]
fn test_different_receivers(mut obj: Id<Object, Owned>) {
unsafe {
let x = &mut obj;
let _: () = msg_send![x, mutable1];
// let _: () = msg_send![x, mutable2];
let _: () = msg_send![&mut *obj, mutable1];
let _: () = msg_send![&mut *obj, mutable2];
#[allow(clippy::needless_borrow)]
let obj: NonNull<Object> = (&mut *obj).into();
let _: () = msg_send![obj, mutable1];
let _: () = msg_send![obj, mutable2];
let obj: *mut Object = obj.as_ptr();
let _: () = msg_send![obj, mutable1];
let _: () = msg_send![obj, mutable2];
}
}
#[test]
fn test_send_message() {
let mut obj = test_utils::custom_object();
let result: u32 = unsafe {
let _: () = msg_send![&mut obj, setFoo: 4u32];
msg_send![&obj, foo]
};
assert_eq!(result, 4);
}
#[test]
fn test_send_message_stret() {
let obj = test_utils::custom_object();
let result: test_utils::CustomStruct = unsafe { msg_send![&obj, customStruct] };
let expected = test_utils::CustomStruct {
a: 1,
b: 2,
c: 3,
d: 4,
};
assert_eq!(result, expected);
}
#[test]
#[cfg_attr(
feature = "verify_message",
should_panic = "messsaging description to nil"
)]
fn test_send_message_nil() {
use crate::rc::Shared;
let nil: *mut Object = ::core::ptr::null_mut();
// This result should not be relied on
let result: Option<Id<Object, Shared>> = unsafe { msg_send_id![nil, description] };
assert!(result.is_none());
// This result should not be relied on
let result: usize = unsafe { msg_send![nil, hash] };
assert_eq!(result, 0);
// This result should not be relied on
#[cfg(target_pointer_width = "16")]
let result: f32 = 0.0;
#[cfg(target_pointer_width = "32")]
let result: f32 = unsafe { msg_send![nil, floatValue] };
#[cfg(target_pointer_width = "64")]
let result: f64 = unsafe { msg_send![nil, doubleValue] };
assert_eq!(result, 0.0);
// This result should not be relied on
let result: Option<Id<Object, Shared>> =
unsafe { msg_send_id![nil, multiple: 1u32, arguments: 2i8] };
assert!(result.is_none());
// This result should not be relied on
let result: Option<Id<Object, Shared>> = unsafe { msg_send_id![None, init] };
assert!(result.is_none());
}
#[test]
fn test_send_message_super() {
let mut obj = test_utils::custom_subclass_object();
let superclass = test_utils::custom_class();
unsafe {
let _: () = msg_send![&mut obj, setFoo: 4u32];
let foo: u32 = msg_send![super(&obj, superclass), foo];
assert_eq!(foo, 4);
// The subclass is overriden to return foo + 2
let foo: u32 = msg_send![&obj, foo];
assert_eq!(foo, 6);
}
}
#[test]
fn test_send_message_manuallydrop() {
let obj = ManuallyDrop::new(test_utils::custom_object());
unsafe {
let _: () = msg_send![obj, release];
};
// `obj` is consumed, can't use here
}
}

View file

@ -0,0 +1,15 @@
/// A marker type that can be used within [`Id`] to indicate that the object
/// has been allocated but not initialized.
///
/// The reason we use `Option<Id<Allocated<T>, O>>` instead of just `*mut T`
/// is:
/// - To allow releasing allocated objects, e.g. in the face of panics.
/// - To safely know the object is valid (albeit uninitialized).
/// - To allow specifying ownership.
///
/// [`Id`]: crate::rc::Id
#[repr(transparent)]
#[derive(Debug)]
pub struct Allocated<T: ?Sized>(T);
// Explicitly don't implement `Deref`, `Message` nor `RefEncode`!

View file

@ -0,0 +1,329 @@
use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::fmt;
use core::marker::PhantomData;
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
use std::{cell::RefCell, thread_local, vec::Vec};
use crate::ffi;
/// An Objective-C autorelease pool.
///
/// The pool is drained when dropped.
///
/// This is not [`Send`], since `objc_autoreleasePoolPop` must be called on
/// the same thread.
///
/// And this is not [`Sync`], since you can only autorelease a reference to a
/// pool on the current thread.
#[derive(Debug)]
pub struct AutoreleasePool {
/// This is an opaque handle, and is not guaranteed to be neither a valid
/// nor aligned pointer.
context: *mut c_void,
/// May point to data that is mutated (even though we hold shared access).
p: PhantomData<*mut UnsafeCell<c_void>>,
}
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
thread_local! {
/// We track the thread's pools to verify that object lifetimes are only
/// taken from the innermost pool.
static POOLS: RefCell<Vec<*mut c_void>> = RefCell::new(Vec::new());
}
impl AutoreleasePool {
/// Construct a new autorelease pool.
///
/// Use the [`autoreleasepool`] block for a safe alternative.
///
/// # Safety
///
/// The caller must ensure that when handing out `&'p AutoreleasePool` to
/// functions that this is the innermost pool.
///
/// Additionally, the pools must be dropped in the same order they were
/// created.
#[doc(alias = "objc_autoreleasePoolPush")]
#[inline]
unsafe fn new() -> Self {
// TODO: Make this function pub when we're more certain of the API
let context = unsafe { ffi::objc_autoreleasePoolPush() };
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
POOLS.with(|c| c.borrow_mut().push(context));
Self {
context,
p: PhantomData,
}
}
/// This will be removed in a future version.
#[inline]
#[doc(hidden)]
pub fn __verify_is_inner(&self) {
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
POOLS.with(|c| {
assert_eq!(
c.borrow().last(),
Some(&self.context),
"Tried to use lifetime from pool that was not innermost"
)
});
}
/// Returns a shared reference to the given autoreleased pointer object.
///
/// This is the preferred way to make references from autoreleased
/// objects, since it binds the lifetime of the reference to the pool, and
/// does some extra checks when debug assertions are enabled.
///
/// For the mutable counterpart see [`ptr_as_mut`](#method.ptr_as_mut).
///
/// # Safety
///
/// This is equivalent to `&*ptr`, and shares the unsafety of that, except
/// the lifetime is bound to the pool instead of being unbounded.
#[inline]
#[allow(clippy::needless_lifetimes)]
pub unsafe fn ptr_as_ref<'p, T: ?Sized>(&'p self, ptr: *const T) -> &'p T {
self.__verify_is_inner();
// SAFETY: Checked by the caller
unsafe { ptr.as_ref().unwrap_unchecked() }
}
/// Returns a unique reference to the given autoreleased pointer object.
///
/// This is the preferred way to make mutable references from autoreleased
/// objects, since it binds the lifetime of the reference to the pool, and
/// does some extra checks when debug assertions are enabled.
///
/// For the shared counterpart see [`ptr_as_ref`](#method.ptr_as_ref).
///
/// # Safety
///
/// This is equivalent to `&mut *ptr`, and shares the unsafety of that,
/// except the lifetime is bound to the pool instead of being unbounded.
#[inline]
#[allow(clippy::needless_lifetimes)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn ptr_as_mut<'p, T: ?Sized>(&'p self, ptr: *mut T) -> &'p mut T {
self.__verify_is_inner();
// SAFETY: Checked by the caller
unsafe { ptr.as_mut().unwrap_unchecked() }
}
}
impl Drop for AutoreleasePool {
/// Drains the autoreleasepool.
///
/// The [clang documentation] says that `@autoreleasepool` blocks are not
/// drained when exceptions occur because:
///
/// > Not draining the pool during an unwind is apparently required by the
/// > Objective-C exceptions implementation.
///
/// However, we would like to do this anyway whenever possible, since the
/// unwind is probably caused by Rust, and forgetting to pop the pool will
/// likely leak memory.
///
/// Fortunately, the above statement was true in the past, but since
/// [revision `371`] of objc4 (ships with MacOS 10.5) the exception is now
/// retained when `@throw` is encountered.
///
/// Hence it is safe to drain the pool when unwinding.
///
/// TODO: Verify this claim on 32bit!
///
/// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool
/// [revision `371`]: https://github.com/apple-oss-distributions/objc4/blob/objc4-371/runtime/objc-exception.m#L479-L482
#[doc(alias = "objc_autoreleasePoolPop")]
#[inline]
fn drop(&mut self) {
unsafe { ffi::objc_autoreleasePoolPop(self.context) }
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
POOLS.with(|c| {
assert_eq!(
c.borrow_mut().pop(),
Some(self.context),
"Popped pool that was not the innermost pool"
)
});
}
}
impl fmt::Pointer for AutoreleasePool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.context, f)
}
}
/// We use a macro here so that the documentation is included whether the
/// feature is enabled or not.
#[cfg(not(feature = "unstable-autoreleasesafe"))]
macro_rules! auto_trait {
{$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
$(#[$fn_meta])*
$v unsafe trait AutoreleaseSafe {}
}
}
#[cfg(feature = "unstable-autoreleasesafe")]
macro_rules! auto_trait {
{$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
$(#[$fn_meta])*
$v unsafe auto trait AutoreleaseSafe {}
}
}
auto_trait! {
/// Marks types that are safe to pass across the closure in an
/// [`autoreleasepool`].
///
/// With the `unstable-autoreleasesafe` feature enabled, this is an auto
/// trait that is implemented for all types except [`AutoreleasePool`].
///
/// Otherwise it is just a dummy trait that is implemented for all types;
/// the safety invariants are checked with debug assertions instead.
///
/// You should not normally need to implement this trait yourself.
///
/// # Safety
///
/// Must not be implemented for types that interract with the autorelease
/// pool. So if you reimplement the [`AutoreleasePool`] struct or
/// likewise, this should be negatively implemented for that.
///
/// This can easily be accomplished with an `PhantomData<AutoreleasePool>`
/// if the `unstable-autoreleasesafe` feature is enabled.
pub unsafe trait AutoreleaseSafe {}
}
#[cfg(not(feature = "unstable-autoreleasesafe"))]
unsafe impl<T: ?Sized> AutoreleaseSafe for T {}
#[cfg(feature = "unstable-autoreleasesafe")]
impl !AutoreleaseSafe for AutoreleasePool {}
/// Execute `f` in the context of a new autorelease pool. The pool is
/// drained after the execution of `f` completes.
///
/// This corresponds to `@autoreleasepool` blocks in Objective-C and
/// Swift.
///
/// The pool is passed as a reference to the enclosing function to give it
/// a lifetime parameter that autoreleased objects can refer to.
///
/// The given reference must not be used in an inner `autoreleasepool`,
/// doing so will panic with debug assertions enabled, and be a compile
/// error in a future release. You can test the compile error with the
/// `unstable-autoreleasesafe` crate feature on nightly Rust.
///
/// Note that this is mostly useful for preventing leaks (as any Objective-C
/// method may leak internally). If implementing an interface to an object,
/// you should try to return retained pointers with [`msg_send_id!`] wherever
/// you can instead, since having to use this function can be quite cumbersome
/// for your users!
///
/// [`msg_send_id!`]: crate::msg_send_id
///
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use core::mem::ManuallyDrop;
/// use objc2::{class, msg_send, msg_send_id};
/// use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
/// use objc2::runtime::Object;
///
/// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
/// let obj = ManuallyDrop::new(obj);
/// let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
/// // Lifetime of the returned reference is bounded by the pool
/// unsafe { pool.ptr_as_mut(obj) }
///
/// // Or simply
/// // let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
/// // obj.autorelease(pool)
/// }
///
/// autoreleasepool(|pool| {
/// // Create `obj` and autorelease it to the pool
/// let obj = needs_lifetime_from_pool(pool);
/// // ... use `obj` here
/// // `obj` is deallocated when the pool ends
/// });
/// ```
///
/// Fails to compile because `obj` does not live long enough for us to
/// safely take it out of the pool:
///
/// ```compile_fail
/// # use objc2::{class, msg_send_id};
/// # use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
/// # use objc2::runtime::Object;
/// #
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// # let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
/// # obj.autorelease(pool)
/// # }
/// #
/// let obj = autoreleasepool(|pool| {
/// let obj = needs_lifetime_from_pool(pool);
/// // Use `obj`
/// obj
/// });
/// ```
///
/// Incorrect usage which panics (with debug assertions enabled) because we
/// tried to pass an outer pool to an inner pool:
///
#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail")]
#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")]
/// # use objc2::{class, msg_send_id};
/// # use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
/// # use objc2::runtime::Object;
/// #
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// # let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
/// # obj.autorelease(pool)
/// # }
/// #
/// autoreleasepool(|outer_pool| {
/// let obj = autoreleasepool(|inner_pool| {
/// let obj = needs_lifetime_from_pool(outer_pool);
/// obj
/// });
/// // `obj` could wrongly be used here because its lifetime was
/// // assigned to the outer pool, even though it was released by the
/// // inner pool already.
/// });
/// #
/// # panic!("Does not panic in release mode, so for testing we make it!");
/// ```
#[doc(alias = "@autoreleasepool")]
#[inline]
pub fn autoreleasepool<T, F>(f: F) -> T
where
for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe,
{
let pool = unsafe { AutoreleasePool::new() };
f(&pool)
}
#[cfg(all(test, feature = "unstable-autoreleasesafe"))]
mod tests {
use super::AutoreleaseSafe;
use crate::runtime::Object;
fn requires_autoreleasesafe<T: AutoreleaseSafe>() {}
#[test]
fn test_autoreleasesafe() {
requires_autoreleasesafe::<usize>();
requires_autoreleasesafe::<*mut Object>();
requires_autoreleasesafe::<&mut Object>();
}
}

879
third-party/vendor/objc2/src/rc/id.rs vendored Normal file
View file

@ -0,0 +1,879 @@
use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::ops::{Deref, DerefMut};
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::NonNull;
use super::Allocated;
use super::AutoreleasePool;
use super::{Owned, Ownership, Shared};
use crate::ffi;
use crate::{ClassType, Message};
/// An pointer for Objective-C reference counted objects.
///
/// [`Id`] strongly references or "retains" the given object `T`, and
/// "releases" it again when dropped, thereby ensuring it will be deallocated
/// at the right time.
///
/// An [`Id`] can either be [`Owned`] or [`Shared`], represented with the `O`
/// type parameter.
///
/// If owned, it is guaranteed that there are no other references to the
/// object, and the [`Id`] can therefore be mutably dereferenced.
///
/// If shared, however, it can only be immutably dereferenced because there
/// may be other references to the object, since a shared [`Id`] can be cloned
/// to provide exactly that.
///
/// An [`Id<T, Owned>`] can be safely converted to a [`Id<T, Shared>`] using
/// [`Id::into_shared`] or `From`/`Into`. The opposite is not safely possible,
/// but the unsafe option [`Id::from_shared`] is provided.
///
/// `Option<Id<T, O>>` is guaranteed to have the same size as a pointer to the
/// object.
///
///
/// # Comparison to `std` types
///
/// `Id<T, Owned>` can be thought of as the Objective-C equivalent of [`Box`]
/// from the standard library: It is a unique pointer to some allocated
/// object, and that means you're allowed to get a mutable reference to it.
///
/// Likewise, `Id<T, Shared>` is the Objective-C equivalent of [`Arc`]: It is
/// a reference-counting pointer that, when cloned, increases the reference
/// count.
///
/// [`Box`]: alloc::boxed::Box
/// [`Arc`]: alloc::sync::Arc
///
/// # Caveats
///
/// If the inner type implements [`Drop`], that implementation will not be
/// called, since there is no way to ensure that the Objective-C runtime will
/// do so. If you need to run some code when the object is destroyed,
/// implement the `dealloc` method instead.
///
/// This allows `?Sized` types `T`, but the intention is to only support when
/// `T` is an `extern type` (yet unstable).
///
/// # Examples
///
/// ```no_run
/// use objc2::msg_send_id;
/// use objc2::runtime::{Class, Object};
/// use objc2::rc::{Id, Owned, Shared, WeakId};
///
/// let cls = Class::get("NSObject").unwrap();
/// let obj: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
/// // obj will be released when it goes out of scope
///
/// // share the object so we can clone it
/// let obj: Id<_, Shared> = obj.into();
/// let another_ref = obj.clone();
/// // dropping our other reference will decrement the retain count
/// drop(another_ref);
///
/// let weak = WeakId::new(&obj);
/// assert!(weak.load().is_some());
/// // After the object is deallocated, our weak pointer returns none
/// drop(obj);
/// assert!(weak.load().is_none());
/// ```
///
/// ```no_run
/// # use objc2::{class, msg_send_id};
/// # use objc2::runtime::Object;
/// # use objc2::rc::{Id, Owned, Shared};
/// # type T = Object;
/// let mut owned: Id<T, Owned>;
/// # owned = unsafe { msg_send_id![class!(NSObject), new] };
/// let mut_ref: &mut T = &mut *owned;
/// // Do something with `&mut T` here
///
/// let shared: Id<T, Shared> = owned.into();
/// let cloned: Id<T, Shared> = shared.clone();
/// // Do something with `&T` here
/// ```
#[repr(transparent)]
// TODO: Figure out if `Message` bound on `T` would be better here?
// TODO: Add `ptr::Thin` bound on `T` to allow for only extern types
// TODO: Consider changing the name of Id -> Retain
pub struct Id<T: ?Sized, O: Ownership> {
/// A pointer to the contained object. The pointer is always retained.
///
/// It is important that this is `NonNull`, since we want to dereference
/// it later, and be able to use the null-pointer optimization.
///
/// Additionally, covariance is correct because we're either the unique
/// owner of `T` (O = Owned), or `T` is immutable (O = Shared).
ptr: NonNull<T>,
/// Necessary for dropck even though we never actually run T's destructor,
/// because it might have a `dealloc` that assumes that contained
/// references outlive the type.
///
/// See <https://doc.rust-lang.org/nightly/nomicon/phantom-data.html>
item: PhantomData<T>,
/// To prevent warnings about unused type parameters.
own: PhantomData<O>,
/// Marks the type as !UnwindSafe. Later on we'll re-enable this.
///
/// See <https://github.com/rust-lang/rust/issues/93367> for why this is
/// required.
notunwindsafe: PhantomData<&'static mut ()>,
}
impl<T: ?Sized, O: Ownership> Id<T, O> {
#[inline]
unsafe fn new_nonnull(ptr: NonNull<T>) -> Self {
Self {
ptr,
item: PhantomData,
own: PhantomData,
notunwindsafe: PhantomData,
}
}
}
impl<T: Message + ?Sized, O: Ownership> Id<Allocated<T>, O> {
#[inline]
pub(crate) unsafe fn new_allocated(ptr: *mut T) -> Option<Self> {
// SAFETY: Upheld by the caller
NonNull::new(ptr as *mut Allocated<T>).map(|ptr| unsafe { Self::new_nonnull(ptr) })
}
#[inline]
pub(crate) unsafe fn assume_init(this: Self) -> Id<T, O> {
let ptr = ManuallyDrop::new(this).ptr;
// NonNull::cast
let ptr = ptr.as_ptr() as *mut T;
let ptr = unsafe { NonNull::new_unchecked(ptr) };
// SAFETY: The pointer is valid.
// Caller verifies that the object is allocated.
unsafe { Id::new_nonnull(ptr) }
}
}
impl<T: Message + ?Sized, O: Ownership> Id<T, O> {
/// Constructs an [`Id`] to an object that already has +1 retain count.
///
/// This is useful when you have a retain count that has been handed off
/// from somewhere else, usually Objective-C methods like `init`, `alloc`,
/// `new`, `copy`, or methods with the `ns_returns_retained` attribute.
///
/// Since most of the above methods create new objects, and you therefore
/// hold unique access to the object, you would often set the ownership to
/// be [`Owned`].
///
/// But some immutable objects (like `NSString`) don't always return
/// unique references, so in those case you would use [`Shared`].
///
/// Returns `None` if the pointer was null.
///
///
/// # Safety
///
/// The caller must ensure the given object has +1 retain count, and that
/// the object pointer otherwise follows the same safety requirements as
/// in [`Id::retain`].
///
///
/// # Example
///
/// ```no_run
/// # use objc2::{class, msg_send, msg_send_id};
/// # use objc2::runtime::{Class, Object};
/// # use objc2::rc::{Id, Owned};
/// let cls: &Class;
/// # let cls = class!(NSObject);
/// let obj: &mut Object = unsafe { msg_send![cls, alloc] };
/// let obj: Id<Object, Owned> = unsafe { Id::new(msg_send![obj, init]).unwrap() };
/// // Or utilizing `msg_send_id`:
/// let obj = unsafe { msg_send_id![cls, alloc] };
/// let obj: Id<Object, Owned> = unsafe { msg_send_id![obj, init] };
/// // Or in this case simply just:
/// let obj: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
/// ```
///
/// ```no_run
/// # use objc2::{class, msg_send_id};
/// # use objc2::runtime::Object;
/// # use objc2::rc::{Id, Shared};
/// # type NSString = Object;
/// let cls = class!(NSString);
/// // NSString is immutable, so don't create an owned reference to it
/// let obj: Id<NSString, Shared> = unsafe { msg_send_id![cls, new] };
/// ```
#[inline]
// Note: We don't take a reference as a parameter since it would be too
// easy to accidentally create two aliasing mutable references.
pub unsafe fn new(ptr: *mut T) -> Option<Id<T, O>> {
// Should optimize down to nothing.
// SAFETY: Upheld by the caller
NonNull::new(ptr).map(|ptr| unsafe { Id::new_nonnull(ptr) })
}
/// Returns a raw pointer to the object.
///
/// The pointer is valid for at least as long as the `Id` is held.
///
/// See [`Id::as_mut_ptr`] for the mutable equivalent.
///
/// This is an associated method, and must be called as `Id::as_ptr(obj)`.
#[inline]
pub fn as_ptr(this: &Id<T, O>) -> *const T {
this.ptr.as_ptr()
}
#[inline]
pub(crate) fn consume_as_ptr(this: ManuallyDrop<Self>) -> *mut T {
this.ptr.as_ptr()
}
#[inline]
pub(crate) fn option_into_ptr(obj: Option<Self>) -> *mut T {
// Difficult to write this in an ergonomic way with ?Sized
// So we just hack it with transmute!
// SAFETY: Option<Id<T, _>> has the same size as *mut T
unsafe { mem::transmute::<ManuallyDrop<Option<Self>>, *mut T>(ManuallyDrop::new(obj)) }
}
}
impl<T: Message + ?Sized> Id<T, Owned> {
/// Returns a raw mutable pointer to the object.
///
/// The pointer is valid for at least as long as the `Id` is held.
///
/// See [`Id::as_ptr`] for the immutable equivalent.
///
/// This is an associated method, and must be called as
/// `Id::as_mut_ptr(obj)`.
#[inline]
pub fn as_mut_ptr(this: &mut Id<T, Owned>) -> *mut T {
this.ptr.as_ptr()
}
}
// TODO: Add ?Sized bound
impl<T: Message, O: Ownership> Id<T, O> {
/// Convert the type of the given object to another.
///
/// This is equivalent to a `cast` between two pointers.
///
/// See [`Id::into_super`] for a safe alternative.
///
/// This is common to do when you know that an object is a subclass of
/// a specific class (e.g. casting an instance of `NSString` to `NSObject`
/// is safe because `NSString` is a subclass of `NSObject`).
///
/// All `'static` objects can safely be cast to [`Object`], since that
/// assumes no specific class.
///
/// [`Object`]: crate::runtime::Object
///
///
/// # Safety
///
/// You must ensure that the object can be reinterpreted as the given
/// type.
///
/// If `T` is not `'static`, you must ensure that `U` ensures that the
/// data contained by `T` is kept alive for as long as `U` lives.
///
/// Additionally, you must ensure that any safety invariants that the new
/// type has are upheld.
#[inline]
pub unsafe fn cast<U: Message>(this: Self) -> Id<U, O> {
let ptr = ManuallyDrop::new(this).ptr.cast();
// SAFETY: The object is forgotten, so we have +1 retain count.
//
// Caller verifies that the returned object is of the correct type.
unsafe { Id::new_nonnull(ptr) }
}
/// Retains the given object pointer.
///
/// This is useful when you have been given a pointer to an object from
/// some API, and you would like to ensure that the object stays around
/// so that you can work with it.
///
/// If said API is a normal Objective-C method, you probably want to use
/// [`Id::retain_autoreleased`] instead.
///
/// This is rarely used to construct owned [`Id`]s, see [`Id::new`] for
/// that.
///
/// Returns `None` if the pointer was null.
///
///
/// # Safety
///
/// The caller must ensure that the ownership is correct; that is, there
/// must be no [`Owned`] pointers or mutable references to the same
/// object, and when creating owned [`Id`]s, there must be no other
/// pointers or references to the object.
///
/// Additionally, the pointer must be valid as a reference (aligned,
/// dereferencable and initialized, see the [`std::ptr`] module for more
/// information).
///
/// Finally, if you do not know the concrete type of `T`, it may not be
/// `'static`, and hence you must ensure that the data that `T` references
/// lives for as long as `T`.
///
/// [`std::ptr`]: core::ptr
//
// This would be illegal:
// ```no_run
// let owned: Id<T, Owned>;
// // Lifetime information is discarded
// let retained: Id<T, Shared> = unsafe { Id::retain(&*owned) };
// // Which means we can still mutate `Owned`:
// let x: &mut T = &mut *owned;
// // While we have an immutable reference
// let y: &T = &*retained;
// ```
#[doc(alias = "objc_retain")]
#[inline]
pub unsafe fn retain(ptr: *mut T) -> Option<Id<T, O>> {
// SAFETY: The caller upholds that the pointer is valid
let res: *mut T = unsafe { ffi::objc_retain(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_retain did not return the same pointer");
// SAFETY: We just retained the object, so it has +1 retain count
unsafe { Self::new(res) }
}
/// Retains a previously autoreleased object pointer.
///
/// This is useful when calling Objective-C methods that return
/// autoreleased objects, see [Cocoa's Memory Management Policy][mmRules].
///
/// This has exactly the same semantics as [`Id::retain`], except it can
/// sometimes avoid putting the object into the autorelease pool, possibly
/// yielding increased speed and reducing memory pressure.
///
/// Note: This relies heavily on being inlined right after [`msg_send!`],
/// be careful not accidentally require instructions between these.
///
/// [mmRules]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
/// [`msg_send!`]: crate::msg_send
///
///
/// # Safety
///
/// Same as [`Id::retain`].
#[doc(alias = "objc_retainAutoreleasedReturnValue")]
#[inline]
pub unsafe fn retain_autoreleased(ptr: *mut T) -> Option<Id<T, O>> {
// Add magic nop instruction to participate in the fast autorelease
// scheme.
//
// See `callerAcceptsOptimizedReturn` in `objc-object.h`:
// https://github.com/apple-oss-distributions/objc4/blob/objc4-838/runtime/objc-object.h#L1209-L1377
//
// We will unconditionally emit these instructions, even if they end
// up being unused (for example because we're unlucky with inlining,
// some other work is done between the objc_msgSend and this, or the
// runtime version is too old to support it).
//
// It may seem like there should be a better way to do this, but
// emitting raw assembly is exactly what Clang and Swift does:
// swiftc: https://github.com/apple/swift/blob/swift-5.5.3-RELEASE/lib/IRGen/GenObjC.cpp#L148-L173
// Clang: https://github.com/llvm/llvm-project/blob/889317d47b7f046cf0e68746da8f7f264582fb5b/clang/lib/CodeGen/CGObjC.cpp#L2339-L2373
//
// Resources:
// - https://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html
// - https://www.galloway.me.uk/2012/02/how-does-objc_retainautoreleasedreturnvalue-work/
// - https://github.com/gfx-rs/metal-rs/issues/222
// - https://news.ycombinator.com/item?id=29311736
// - https://stackoverflow.com/a/23765612
//
// SAFETY:
// Based on https://doc.rust-lang.org/stable/reference/inline-assembly.html#rules-for-inline-assembly
//
// We don't care about the value of the register (so it's okay to be
// undefined), and its value is preserved.
//
// nomem: No reads or writes to memory are performed (this `mov`
// operates entirely on registers).
// preserves_flags: `mov` doesn't modify any flags.
// nostack: We don't touch the stack.
// Only worth doing on the Apple runtime.
// Not supported on TARGET_OS_WIN32.
#[cfg(all(feature = "apple", not(target_os = "windows")))]
{
// Supported since macOS 10.7.
#[cfg(target_arch = "x86_64")]
{
// x86_64 looks at the next call instruction.
//
// This is expected to be a PLT entry - if the user specifies
// `-Zplt=no`, a GOT entry will be created instead, and this
// will not work.
}
// Supported since macOS 10.8.
#[cfg(target_arch = "arm")]
unsafe {
core::arch::asm!("mov r7, r7", options(nomem, preserves_flags, nostack))
};
// Supported since macOS 10.10.
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("mov fp, fp", options(nomem, preserves_flags, nostack))
};
// Supported since macOS 10.12.
#[cfg(target_arch = "x86")]
unsafe {
core::arch::asm!("mov ebp, ebp", options(nomem, preserves_flags, nostack))
};
}
// SAFETY: Same as `retain`, this is just an optimization.
let res: *mut T = unsafe { ffi::objc_retainAutoreleasedReturnValue(ptr.cast()) }.cast();
// Ideally, we'd be able to specify that the above call should never
// be tail-call optimized (become a `jmp` instruction instead of a
// `call`); Rust doesn't really have a way of doing this currently, so
// we just emit a simple `nop` to make such tail-call optimizations
// less likely to occur.
//
// This is brittle! We should find a better solution!
#[cfg(all(feature = "apple", not(target_os = "windows"), target_arch = "x86_64"))]
{
// SAFETY: Similar to above.
unsafe { core::arch::asm!("nop", options(nomem, preserves_flags, nostack)) };
// TODO: Possibly more efficient alternative? Also consider PLT.
// #![feature(asm_sym)]
// core::arch::asm!(
// "mov rdi, rax",
// "call {}",
// sym objc2::ffi::objc_retainAutoreleasedReturnValue,
// inout("rax") obj,
// clobber_abi("C"),
// );
}
debug_assert_eq!(
res, ptr,
"objc_retainAutoreleasedReturnValue did not return the same pointer"
);
unsafe { Self::new(res) }
}
#[inline]
fn autorelease_inner(self) -> *mut T {
// Note that this (and the actual `autorelease`) is not an associated
// function. This breaks the guideline that smart pointers shouldn't
// add inherent methods, but since autoreleasing only works on already
// retained objects it is hard to imagine a case where the inner type
// has a method with the same name.
let ptr = ManuallyDrop::new(self).ptr.as_ptr();
// SAFETY: The `ptr` is guaranteed to be valid and have at least one
// retain count.
// And because of the ManuallyDrop, we don't call the Drop
// implementation, so the object won't also be released there.
let res: *mut T = unsafe { ffi::objc_autorelease(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_autorelease did not return the same pointer");
res
}
/// Autoreleases and prepares the [`Id`] to be returned to Objective-C.
///
/// The object is not immediately released, but will be when the innermost
/// autorelease pool is drained.
///
/// This is useful when [declaring your own methods][declare] where you
/// will often find yourself in need of returning autoreleased objects to
/// properly follow [Cocoa's Memory Management Policy][mmRules].
///
/// To that end, you could use [`Id::autorelease`], but that would require
/// you to have an [`AutoreleasePool`] object at hand, which you clearly
/// won't have in such cases. This function doesn't require a `pool`
/// object (but as a downside returns a pointer instead of a reference).
///
/// This is also more efficient than a normal `autorelease`, it makes a
/// best effort attempt to hand off ownership of the retain count to a
/// subsequent call to `objc_retainAutoreleasedReturnValue` /
/// [`Id::retain_autoreleased`] in the enclosing call frame. Note: This
/// optimization relies heavily on this function being tail called, so be
/// careful to call this function at the end of your method.
///
/// [declare]: crate::declare
/// [mmRules]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
///
///
/// # Examples
///
/// ```
/// use objc2::{class, msg_send_id, sel};
/// use objc2::declare::ClassBuilder;
/// use objc2::rc::{Id, Owned};
/// use objc2::runtime::{Class, Object, Sel};
/// #
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut builder = ClassBuilder::new("ExampleObject", class!(NSObject)).unwrap();
///
/// extern "C" fn get(cls: &Class, _cmd: Sel) -> *mut Object {
/// let obj: Id<Object, Owned> = unsafe { msg_send_id![cls, new] };
/// obj.autorelease_return()
/// }
///
/// unsafe {
/// builder.add_class_method(
/// sel!(get),
/// get as extern "C" fn(_, _) -> _,
/// );
/// }
///
/// let cls = builder.register();
/// ```
#[doc(alias = "objc_autoreleaseReturnValue")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
pub fn autorelease_return(self) -> *mut T {
// See `autorelease_inner` for why this is an inherent method
let ptr = ManuallyDrop::new(self).ptr.as_ptr();
// SAFETY: Same as `autorelease_inner`, this is just an optimization.
let res: *mut T = unsafe { ffi::objc_autoreleaseReturnValue(ptr.cast()) }.cast();
debug_assert_eq!(
res, ptr,
"objc_autoreleaseReturnValue did not return the same pointer"
);
res
}
}
// TODO: Consider something like this
// #[cfg(block)]
// impl<T: Block, O> Id<T, O> {
// #[doc(alias = "objc_retainBlock")]
// pub unsafe fn retain_block(block: *mut T) -> Option<Self> {
// todo!()
// }
// }
// TODO: Add ?Sized bound
impl<T: Message> Id<T, Owned> {
/// Autoreleases the owned [`Id`], returning a mutable reference bound to
/// the pool.
///
/// The object is not immediately released, but will be when the innermost
/// / current autorelease pool (given as a parameter) is drained.
#[doc(alias = "objc_autorelease")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
#[allow(clippy::needless_lifetimes)]
#[allow(clippy::mut_from_ref)]
pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p mut T {
let ptr = self.autorelease_inner();
// SAFETY: The pointer is valid as a reference, and we've consumed
// the unique access to the `Id` so mutability is safe.
unsafe { pool.ptr_as_mut(ptr) }
}
/// Promote a shared [`Id`] to an owned one, allowing it to be mutated.
///
///
/// # Safety
///
/// The caller must ensure that there are no other pointers (including
/// [`WeakId`][`super::WeakId`] pointers) to the same object.
///
/// This also means that the given [`Id`] should have a retain count of
/// exactly 1 (except when autoreleases are involved).
///
/// In general, this is wildly unsafe, do see if you can find a different
/// solution!
#[inline]
pub unsafe fn from_shared(obj: Id<T, Shared>) -> Self {
// Note: We can't debug_assert retainCount because of autoreleases
let ptr = ManuallyDrop::new(obj).ptr;
// SAFETY: The pointer is valid
// Ownership rules are upheld by the caller
unsafe { <Id<T, Owned>>::new_nonnull(ptr) }
}
/// Convert an owned to a shared [`Id`], allowing it to be cloned.
///
/// This is also implemented as a `From` conversion, but this name is more
/// explicit, which may be useful in some cases.
#[inline]
pub fn into_shared(obj: Self) -> Id<T, Shared> {
let ptr = ManuallyDrop::new(obj).ptr;
// SAFETY: The pointer is valid, and ownership is simply decreased
unsafe { <Id<T, Shared>>::new_nonnull(ptr) }
}
}
// TODO: Add ?Sized bound
impl<T: Message> Id<T, Shared> {
/// Autoreleases the shared [`Id`], returning an aliased reference bound
/// to the pool.
///
/// The object is not immediately released, but will be when the innermost
/// / current autorelease pool (given as a parameter) is drained.
#[doc(alias = "objc_autorelease")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
#[allow(clippy::needless_lifetimes)]
pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p T {
let ptr = self.autorelease_inner();
// SAFETY: The pointer is valid as a reference
unsafe { pool.ptr_as_ref(ptr) }
}
}
impl<T: ClassType + 'static, O: Ownership> Id<T, O>
where
T::Super: 'static,
{
/// Convert the object into it's superclass.
#[inline]
pub fn into_super(this: Self) -> Id<T::Super, O> {
// SAFETY:
// - The casted-to type is a superclass of the type.
// - Both types are `'static` (this could maybe be relaxed a bit, but
// let's just be on the safe side)!
unsafe { Self::cast::<T::Super>(this) }
}
}
impl<T: Message> From<Id<T, Owned>> for Id<T, Shared> {
/// Convert an owned to a shared [`Id`], allowing it to be cloned.
///
/// Same as [`Id::into_shared`].
#[inline]
fn from(obj: Id<T, Owned>) -> Self {
Id::into_shared(obj)
}
}
// TODO: Add ?Sized bound
impl<T: Message> Clone for Id<T, Shared> {
/// Makes a clone of the shared object.
///
/// This increases the object's reference count.
#[doc(alias = "objc_retain")]
#[doc(alias = "retain")]
#[inline]
fn clone(&self) -> Self {
// SAFETY: The pointer is valid
let obj = unsafe { Id::retain(self.ptr.as_ptr()) };
// SAFETY: `objc_retain` always returns the same object pointer, and
// the pointer is guaranteed non-null by Id.
unsafe { obj.unwrap_unchecked() }
}
}
/// `#[may_dangle]` (see [this][dropck_eyepatch]) doesn't apply here since we
/// don't run `T`'s destructor (rather, we want to discourage having `T`s with
/// a destructor); and even if we did run the destructor, it would not be safe
/// to add since we cannot verify that a `dealloc` method doesn't access
/// borrowed data.
///
/// [dropck_eyepatch]: https://doc.rust-lang.org/nightly/nomicon/dropck.html#an-escape-hatch
impl<T: ?Sized, O: Ownership> Drop for Id<T, O> {
/// Releases the retained object.
///
/// The contained object's destructor (if it has one) is never run!
#[doc(alias = "objc_release")]
#[doc(alias = "release")]
#[inline]
fn drop(&mut self) {
// We could technically run the destructor for `T` when `O = Owned`,
// and when `O = Shared` with (retainCount == 1), but that would be
// confusing and inconsistent since we cannot guarantee that it's run.
// SAFETY: The `ptr` is guaranteed to be valid and have at least one
// retain count
unsafe { ffi::objc_release(self.ptr.as_ptr().cast()) };
}
}
// https://doc.rust-lang.org/nomicon/arc-mutex/arc-base.html#send-and-sync
/// The `Send` implementation requires `T: Sync` because `Id<T, Shared>` give
/// access to `&T`.
///
/// Additiontally, it requires `T: Send` because if `T: !Send`, you could
/// clone a `Id<T, Shared>`, send it to another thread, and drop the clone
/// last, making `dealloc` get called on the other thread, and violate
/// `T: !Send`.
unsafe impl<T: Sync + Send + ?Sized> Send for Id<T, Shared> {}
/// The `Sync` implementation requires `T: Sync` because `&Id<T, Shared>` give
/// access to `&T`.
///
/// Additiontally, it requires `T: Send`, because if `T: !Send`, you could
/// clone a `&Id<T, Shared>` from another thread, and drop the clone last,
/// making `dealloc` get called on the other thread, and violate `T: !Send`.
unsafe impl<T: Sync + Send + ?Sized> Sync for Id<T, Shared> {}
/// `Id<T, Owned>` are `Send` if `T` is `Send` because they give the same
/// access as having a T directly.
unsafe impl<T: Send + ?Sized> Send for Id<T, Owned> {}
/// `Id<T, Owned>` are `Sync` if `T` is `Sync` because they give the same
/// access as having a `T` directly.
unsafe impl<T: Sync + ?Sized> Sync for Id<T, Owned> {}
impl<T: ?Sized, O: Ownership> Deref for Id<T, O> {
type Target = T;
/// Obtain an immutable reference to the object.
// Box doesn't inline, but that's because it's a compiler built-in
#[inline]
fn deref(&self) -> &T {
// SAFETY: The pointer's validity is verified when the type is created
unsafe { self.ptr.as_ref() }
}
}
impl<T: ?Sized> DerefMut for Id<T, Owned> {
/// Obtain a mutable reference to the object.
#[inline]
fn deref_mut(&mut self) -> &mut T {
// SAFETY: The pointer's validity is verified when the type is created
// Additionally, the owned `Id` is the unique owner of the object, so
// mutability is safe.
unsafe { self.ptr.as_mut() }
}
}
impl<T: ?Sized, O: Ownership> fmt::Pointer for Id<T, O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
}
}
// This is valid without `T: Unpin` because we don't implement any projection.
//
// See https://doc.rust-lang.org/1.54.0/src/alloc/boxed.rs.html#1652-1675
// and the `Arc` implementation.
impl<T: ?Sized, O: Ownership> Unpin for Id<T, O> {}
impl<T: RefUnwindSafe + ?Sized, O: Ownership> RefUnwindSafe for Id<T, O> {}
// Same as `Arc<T>`.
impl<T: RefUnwindSafe + ?Sized> UnwindSafe for Id<T, Shared> {}
// Same as `Box<T>`.
impl<T: UnwindSafe + ?Sized> UnwindSafe for Id<T, Owned> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::msg_send;
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
use crate::runtime::Object;
#[track_caller]
fn assert_retain_count(obj: &Object, expected: usize) {
let retain_count: usize = unsafe { msg_send![obj, retainCount] };
assert_eq!(retain_count, expected);
}
#[test]
fn test_drop() {
let mut expected = ThreadTestData::current();
let obj = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_autorelease() {
let obj: Id<_, Shared> = RcTestObject::new().into();
let cloned = obj.clone();
let mut expected = ThreadTestData::current();
autoreleasepool(|pool| {
let _ref = obj.autorelease(pool);
expected.autorelease += 1;
expected.assert_current();
assert_retain_count(&cloned, 2);
});
expected.release += 1;
expected.assert_current();
assert_retain_count(&cloned, 1);
autoreleasepool(|pool| {
let _ref = cloned.autorelease(pool);
expected.autorelease += 1;
expected.assert_current();
});
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_clone() {
let obj: Id<_, Owned> = RcTestObject::new();
assert_retain_count(&obj, 1);
let mut expected = ThreadTestData::current();
let obj: Id<_, Shared> = obj.into();
expected.assert_current();
assert_retain_count(&obj, 1);
let cloned = obj.clone();
expected.retain += 1;
expected.assert_current();
assert_retain_count(&cloned, 2);
assert_retain_count(&obj, 2);
drop(obj);
expected.release += 1;
expected.assert_current();
assert_retain_count(&cloned, 1);
drop(cloned);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_retain_autoreleased_works_as_retain() {
let obj: Id<_, Shared> = RcTestObject::new().into();
let mut expected = ThreadTestData::current();
let ptr = Id::as_ptr(&obj) as *mut RcTestObject;
let _obj2: Id<_, Shared> = unsafe { Id::retain_autoreleased(ptr) }.unwrap();
expected.retain += 1;
expected.assert_current();
}
#[test]
fn test_cast() {
let obj: Id<RcTestObject, _> = RcTestObject::new();
let expected = ThreadTestData::current();
// SAFETY: Any object can be cast to `Object`
let obj: Id<Object, _> = unsafe { Id::cast(obj) };
expected.assert_current();
// SAFETY: The object was originally `RcTestObject`
let _obj: Id<RcTestObject, _> = unsafe { Id::cast(obj) };
expected.assert_current();
}
}

View file

@ -0,0 +1,302 @@
//! Trivial forwarding impls on `Id`.
//!
//! Kept here to keep `id.rs` free from this boilerplate.
//!
//! `#[inline]` is used where the standard library `Box` uses it.
#![forbid(unsafe_code)]
use alloc::borrow;
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::fmt;
use core::future::Future;
use core::hash;
use core::iter::FusedIterator;
use core::ops::{Deref, DerefMut};
use core::pin::Pin;
use core::task::{Context, Poll};
use std::error::Error;
use std::io;
use super::{Id, Owned, Ownership};
impl<T: PartialEq + ?Sized, O: Ownership> PartialEq for Id<T, O> {
#[inline]
fn eq(&self, other: &Self) -> bool {
(**self).eq(&**other)
}
#[inline]
#[allow(clippy::partialeq_ne_impl)]
fn ne(&self, other: &Self) -> bool {
(**self).ne(&**other)
}
}
impl<T: Eq + ?Sized, O: Ownership> Eq for Id<T, O> {}
impl<T: PartialOrd + ?Sized, O: Ownership> PartialOrd for Id<T, O> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(**self).partial_cmp(&**other)
}
#[inline]
fn lt(&self, other: &Self) -> bool {
(**self).lt(&**other)
}
#[inline]
fn le(&self, other: &Self) -> bool {
(**self).le(&**other)
}
#[inline]
fn ge(&self, other: &Self) -> bool {
(**self).ge(&**other)
}
#[inline]
fn gt(&self, other: &Self) -> bool {
(**self).gt(&**other)
}
}
impl<T: Ord + ?Sized, O: Ownership> Ord for Id<T, O> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(**self).cmp(&**other)
}
}
impl<T: hash::Hash + ?Sized, O: Ownership> hash::Hash for Id<T, O> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(**self).hash(state)
}
}
impl<T: hash::Hasher + ?Sized> hash::Hasher for Id<T, Owned> {
fn finish(&self) -> u64 {
(**self).finish()
}
fn write(&mut self, bytes: &[u8]) {
(**self).write(bytes)
}
fn write_u8(&mut self, i: u8) {
(**self).write_u8(i)
}
fn write_u16(&mut self, i: u16) {
(**self).write_u16(i)
}
fn write_u32(&mut self, i: u32) {
(**self).write_u32(i)
}
fn write_u64(&mut self, i: u64) {
(**self).write_u64(i)
}
fn write_u128(&mut self, i: u128) {
(**self).write_u128(i)
}
fn write_usize(&mut self, i: usize) {
(**self).write_usize(i)
}
fn write_i8(&mut self, i: i8) {
(**self).write_i8(i)
}
fn write_i16(&mut self, i: i16) {
(**self).write_i16(i)
}
fn write_i32(&mut self, i: i32) {
(**self).write_i32(i)
}
fn write_i64(&mut self, i: i64) {
(**self).write_i64(i)
}
fn write_i128(&mut self, i: i128) {
(**self).write_i128(i)
}
fn write_isize(&mut self, i: isize) {
(**self).write_isize(i)
}
}
impl<T: fmt::Display + ?Sized, O: Ownership> fmt::Display for Id<T, O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Debug + ?Sized, O: Ownership> fmt::Debug for Id<T, O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<I: Iterator + ?Sized> Iterator for Id<I, Owned> {
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
(**self).next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(**self).size_hint()
}
fn nth(&mut self, n: usize) -> Option<I::Item> {
(**self).nth(n)
}
}
impl<I: DoubleEndedIterator + ?Sized> DoubleEndedIterator for Id<I, Owned> {
fn next_back(&mut self) -> Option<I::Item> {
(**self).next_back()
}
fn nth_back(&mut self, n: usize) -> Option<I::Item> {
(**self).nth_back(n)
}
}
impl<I: ExactSizeIterator + ?Sized> ExactSizeIterator for Id<I, Owned> {
fn len(&self) -> usize {
(**self).len()
}
}
impl<I: FusedIterator + ?Sized> FusedIterator for Id<I, Owned> {}
// TODO: Consider this impl
// impl<'a, T, O: Ownership> IntoIterator for &'a Id<T, O>
// where
// &'a T: IntoIterator,
// {
// type Item = <&'a T as IntoIterator>::Item;
// type IntoIter = <&'a T as IntoIterator>::IntoIter;
//
// fn into_iter(self) -> Self::IntoIter {
// (**self).into_iter()
// }
// }
impl<T, O: Ownership> borrow::Borrow<T> for Id<T, O> {
fn borrow(&self) -> &T {
Deref::deref(self)
}
}
impl<T> borrow::BorrowMut<T> for Id<T, Owned> {
fn borrow_mut(&mut self) -> &mut T {
DerefMut::deref_mut(self)
}
}
impl<T: ?Sized, O: Ownership> AsRef<T> for Id<T, O> {
fn as_ref(&self) -> &T {
Deref::deref(self)
}
}
impl<T: ?Sized> AsMut<T> for Id<T, Owned> {
fn as_mut(&mut self) -> &mut T {
DerefMut::deref_mut(self)
}
}
impl<T: Error + ?Sized, O: Ownership> Error for Id<T, O> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
(**self).source()
}
}
impl<T: io::Read + ?Sized> io::Read for Id<T, Owned> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(**self).read(buf)
}
#[inline]
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
(**self).read_vectored(bufs)
}
#[inline]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
(**self).read_to_end(buf)
}
#[inline]
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
(**self).read_to_string(buf)
}
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
(**self).read_exact(buf)
}
}
impl<T: io::Write + ?Sized> io::Write for Id<T, Owned> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(**self).write(buf)
}
#[inline]
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
(**self).write_vectored(bufs)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
(**self).flush()
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
(**self).write_all(buf)
}
#[inline]
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
(**self).write_fmt(fmt)
}
}
impl<T: io::Seek + ?Sized> io::Seek for Id<T, Owned> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
(**self).seek(pos)
}
#[inline]
fn stream_position(&mut self) -> io::Result<u64> {
(**self).stream_position()
}
}
impl<T: io::BufRead + ?Sized> io::BufRead for Id<T, Owned> {
#[inline]
fn fill_buf(&mut self) -> io::Result<&[u8]> {
(**self).fill_buf()
}
#[inline]
fn consume(&mut self, amt: usize) {
(**self).consume(amt)
}
#[inline]
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> io::Result<usize> {
(**self).read_until(byte, buf)
}
#[inline]
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
(**self).read_line(buf)
}
}
impl<T: Future + Unpin + ?Sized> Future for Id<T, Owned> {
type Output = T::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
T::poll(Pin::new(&mut *self), cx)
}
}
// TODO: impl Fn traits, CoerceUnsized, Stream and so on when stabilized

View file

@ -0,0 +1,72 @@
//! Helper traits for Id.
use super::{Id, Owned, Ownership};
use crate::Message;
/// Helper trait for functionality on slices containing [`Id`]s.
pub trait SliceId {
/// The type of the items in the slice.
type Item: ?Sized;
/// Convert a slice of [`Id`]s into a slice of references.
fn as_slice_ref(&self) -> &[&Self::Item];
/// Convert a mutable slice of [`Id`]s into a mutable slice of references.
fn as_slice_mut(&mut self) -> &mut [&Self::Item];
}
/// Helper trait for functionality on slices containing owned [`Id`]s.
pub trait SliceIdMut: SliceId {
/// Convert a mutable slice of mutable [`Id`]s into a mutable slice of
/// mutable references.
fn as_mut_slice_mut(&mut self) -> &mut [&mut Self::Item];
}
impl<T: Message + ?Sized, O: Ownership> SliceId for [Id<T, O>] {
type Item = T;
fn as_slice_ref(&self) -> &[&T] {
let ptr = self as *const Self as *const [&T];
// SAFETY: Id<T, O> and &T have the same memory layout. Further safety
// follows from `Deref` impl.
unsafe { ptr.as_ref().unwrap_unchecked() }
}
fn as_slice_mut(&mut self) -> &mut [&T] {
let ptr = self as *mut Self as *mut [&T];
// SAFETY: Id<T, O> and &T have the same memory layout. Further safety
// follows from `Deref` impl.
unsafe { ptr.as_mut().unwrap_unchecked() }
}
}
impl<T: Message + ?Sized> SliceIdMut for [Id<T, Owned>] {
fn as_mut_slice_mut(&mut self) -> &mut [&mut T] {
let ptr = self as *mut Self as *mut [&mut T];
// SAFETY: Id<T, O> and &mut T have the same memory layout, and the
// `Id` is `Owned` so we're allowed to hand out mutable references.
// Further safety follows from `DerefMut` impl.
unsafe { ptr.as_mut().unwrap_unchecked() }
}
}
/// Helper trait to implement [`Default`] on types whoose default value is an
/// [`Id`].
// TODO: Maybe make this `unsafe` and provide a default implementation?
pub trait DefaultId {
/// Indicates whether the default value is mutable or immutable.
type Ownership: Ownership;
/// The default [`Id`] for a type.
///
/// On most objects the implementation would just be sending a message to
/// the `new` selector.
fn default_id() -> Id<Self, Self::Ownership>;
}
impl<T: DefaultId + ?Sized> Default for Id<T, T::Ownership> {
#[inline]
fn default() -> Self {
T::default_id()
}
}

132
third-party/vendor/objc2/src/rc/mod.rs vendored Normal file
View file

@ -0,0 +1,132 @@
//! Utilities for reference counting Objective-C objects.
//!
//! These utilities in this module provide ARC-like semantics for working with
//! Objective-C's reference counted objects.
//!
//! A smart pointer [`Id`] is provided to ensure that Objective-C objects are
//! retained and released when created and dropped, respectively.
//!
//! To enforce aliasing rules, an `Id` can be either owned or shared; if it is
//! owned, meaning the `Id` is the only reference to the object, it can be
//! mutably dereferenced. An owned `Id` can be converted to a shared `Id`,
//! which can be cloned to allow multiple references.
//!
//! Weak references may be created using the [`WeakId`] struct; these will not
//! retain the object, but one can attempt to load them and obtain an `Id`, or
//! safely fail if the object has been deallocated.
//!
//! See [the clang documentation][clang-arc] and [the Apple article on memory
//! management][mem-mgmt] (similar document exists [for Core Foundation][cf])
//! for more information on automatic and manual reference counting.
//!
//! It can also be useful to [enable Malloc Debugging][mem-debug] if you're trying
//! to figure out if/where your application has memory errors and leaks.
//!
//! [clang-arc]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html
//! [mem-mgmt]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
//! [cf]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/CFMemoryMgmt.html
//! [mem-debug]: https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html
//!
//!
//! ## Example
//!
#![cfg_attr(feature = "apple", doc = "```")]
#![cfg_attr(not(feature = "apple"), doc = "```no_run")]
//! use objc2::{class, msg_send_id};
//! use objc2::rc::{autoreleasepool, Id, Shared, WeakId};
//! use objc2::runtime::Object;
//!
//! // Id will release the object when dropped
//! let obj: Id<Object, Shared> = unsafe {
//! msg_send_id![class!(NSObject), new]
//! };
//!
//! // Cloning retains the object an additional time
//! let cloned = obj.clone();
//! autoreleasepool(|pool| {
//! // Autorelease consumes the Id, but won't
//! // actually release until the end of an autoreleasepool
//! let obj_ref: &Object = cloned.autorelease(pool);
//! });
//!
//! // Weak references won't retain the object
//! let weak = WeakId::new(&obj);
//! drop(obj);
//! assert!(weak.load().is_none());
//! ```
mod allocated;
mod autorelease;
mod id;
mod id_forwarding_impls;
mod id_traits;
mod ownership;
mod weak_id;
#[cfg(test)]
mod test_object;
pub use self::allocated::Allocated;
pub use self::autorelease::{autoreleasepool, AutoreleasePool, AutoreleaseSafe};
pub use self::id::Id;
pub use self::id_traits::{DefaultId, SliceId, SliceIdMut};
pub use self::ownership::{Owned, Ownership, Shared};
pub use self::weak_id::WeakId;
#[cfg(test)]
pub(crate) use self::test_object::{RcTestObject, ThreadTestData};
#[cfg(test)]
mod tests {
use core::marker::PhantomData;
use core::mem::size_of;
use super::{Id, Owned, Ownership, Shared, WeakId};
use crate::runtime::Object;
#[repr(C)]
struct TestType {
inner: Object,
}
#[repr(C)]
struct MyObject<'a> {
inner: Object,
p: PhantomData<&'a str>,
}
/// Test that `Id<T, O>` is covariant over `T`.
#[allow(unused)]
fn assert_id_variance<'a, 'b, O: Ownership>(
obj: &'a Id<MyObject<'static>, O>,
) -> &'a Id<MyObject<'b>, O> {
obj
}
/// Test that `WeakId<T>` is covariant over `T`.
#[allow(unused)]
fn assert_weak_id_variance<'a, 'b>(
obj: &'a WeakId<MyObject<'static>>,
) -> &'a WeakId<MyObject<'b>> {
obj
}
#[test]
fn test_size_of() {
assert_eq!(size_of::<Id<TestType, Owned>>(), size_of::<&TestType>());
assert_eq!(size_of::<Id<TestType, Shared>>(), size_of::<&TestType>());
assert_eq!(
size_of::<Option<Id<TestType, Owned>>>(),
size_of::<&TestType>()
);
assert_eq!(
size_of::<Option<Id<TestType, Shared>>>(),
size_of::<&TestType>()
);
assert_eq!(
size_of::<Option<WeakId<TestType>>>(),
size_of::<*const ()>()
);
}
}

View file

@ -0,0 +1,82 @@
use core::fmt::Debug;
use core::hash::Hash;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::AutoreleaseSafe;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
enum Never {}
/// A type used to mark that a struct owns the object(s) it contains,
/// so it has the sole references to them.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Owned {
inner: Never,
}
/// A type used to mark that the object(s) a struct contains are shared,
/// so there may be other references to them.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Shared {
inner: Never,
}
mod private {
pub trait Sealed {}
impl Sealed for super::Owned {}
impl Sealed for super::Shared {}
}
/// A type that marks what type of ownership a struct has over the object(s)
/// it contains; specifically, either [`Owned`] or [`Shared`].
///
/// This trait is sealed and not meant to be implemented outside of the this
/// crate.
pub trait Ownership:
private::Sealed
// Special
+ 'static
+ Sized
// Auto-traits
+ Send
+ Sync
+ Unpin
+ UnwindSafe
+ RefUnwindSafe
// Derived
+ Clone
+ Copy
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Hash
+ Debug
// Custom
+ AutoreleaseSafe
{
}
impl Ownership for Owned {}
impl Ownership for Shared {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generic_ownership_traits() {
fn assert_partialeq<T: PartialEq>() {}
assert_partialeq::<Shared>();
assert_partialeq::<Owned>();
fn test_ownership_implies_partialeq<O: Ownership>() {
assert_partialeq::<O>();
}
test_ownership_implies_partialeq::<Shared>();
test_ownership_implies_partialeq::<Owned>();
}
}

View file

@ -0,0 +1,175 @@
use core::cell::RefCell;
use core::mem::ManuallyDrop;
use core::ptr;
use super::{Id, Owned};
use crate::foundation::{NSObject, NSZone};
use crate::{declare_class, msg_send, ClassType};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct ThreadTestData {
pub(crate) alloc: usize,
pub(crate) dealloc: usize,
pub(crate) init: usize,
pub(crate) retain: usize,
pub(crate) copy: usize,
pub(crate) mutable_copy: usize,
pub(crate) release: usize,
pub(crate) autorelease: usize,
pub(crate) try_retain: usize,
pub(crate) try_retain_fail: usize,
}
impl ThreadTestData {
/// Get the amount of method calls performed on the current thread.
pub(crate) fn current() -> ThreadTestData {
TEST_DATA.with(|data| data.borrow().clone())
}
#[track_caller]
pub(crate) fn assert_current(&self) {
let current = Self::current();
let mut expected = self.clone();
if cfg!(feature = "gnustep-1-7") {
// GNUStep doesn't have `tryRetain`, it uses `retain` directly
let retain_diff = expected.try_retain - current.try_retain;
expected.retain += retain_diff;
expected.try_retain -= retain_diff;
// GNUStep doesn't call `autorelease` if it's overridden
expected.autorelease = 0;
}
assert_eq!(current, expected);
}
}
std::thread_local! {
pub(crate) static TEST_DATA: RefCell<ThreadTestData> = RefCell::new(Default::default());
}
declare_class!(
/// A helper object that counts how many times various reference-counting
/// primitives are called.
#[derive(Debug, PartialEq)]
pub(crate) struct RcTestObject {}
unsafe impl ClassType for RcTestObject {
type Super = NSObject;
}
unsafe impl RcTestObject {
#[sel(newReturningNull)]
fn new_returning_null() -> *mut Self {
ptr::null_mut()
}
#[sel(alloc)]
fn alloc() -> *mut Self {
TEST_DATA.with(|data| data.borrow_mut().alloc += 1);
let superclass = NSObject::class().metaclass();
let zone: *const NSZone = ptr::null();
unsafe { msg_send![super(Self::class(), superclass), allocWithZone: zone] }
}
#[sel(allocWithZone:)]
fn alloc_with_zone(zone: *const NSZone) -> *mut Self {
TEST_DATA.with(|data| data.borrow_mut().alloc += 1);
let superclass = NSObject::class().metaclass();
unsafe { msg_send![super(Self::class(), superclass), allocWithZone: zone] }
}
#[sel(allocReturningNull)]
fn alloc_returning_null() -> *mut Self {
ptr::null_mut()
}
#[sel(init)]
fn init(&mut self) -> *mut Self {
TEST_DATA.with(|data| data.borrow_mut().init += 1);
unsafe { msg_send![super(self), init] }
}
#[sel(initReturningNull)]
fn init_returning_null(&mut self) -> *mut Self {
let _: () = unsafe { msg_send![self, release] };
ptr::null_mut()
}
#[sel(retain)]
fn retain(&self) -> *mut Self {
TEST_DATA.with(|data| data.borrow_mut().retain += 1);
unsafe { msg_send![super(self), retain] }
}
#[sel(release)]
fn release(&self) {
TEST_DATA.with(|data| data.borrow_mut().release += 1);
unsafe { msg_send![super(self), release] }
}
#[sel(autorelease)]
fn autorelease(&self) -> *mut Self {
TEST_DATA.with(|data| data.borrow_mut().autorelease += 1);
unsafe { msg_send![super(self), autorelease] }
}
#[sel(dealloc)]
unsafe fn dealloc(&mut self) {
TEST_DATA.with(|data| data.borrow_mut().dealloc += 1);
unsafe { msg_send![super(self), dealloc] }
}
#[sel(_tryRetain)]
unsafe fn try_retain(&self) -> bool {
TEST_DATA.with(|data| data.borrow_mut().try_retain += 1);
let res: bool = unsafe { msg_send![super(self), _tryRetain] };
if !res {
TEST_DATA.with(|data| data.borrow_mut().try_retain -= 1);
TEST_DATA.with(|data| data.borrow_mut().try_retain_fail += 1);
}
res
}
#[sel(copyWithZone:)]
fn copy_with_zone(&self, _zone: *const NSZone) -> *const Self {
TEST_DATA.with(|data| data.borrow_mut().copy += 1);
Id::consume_as_ptr(ManuallyDrop::new(Self::new()))
}
#[sel(mutableCopyWithZone:)]
fn mutable_copy_with_zone(&self, _zone: *const NSZone) -> *const Self {
TEST_DATA.with(|data| data.borrow_mut().mutable_copy += 1);
Id::consume_as_ptr(ManuallyDrop::new(Self::new()))
}
#[sel(copyReturningNull)]
fn copy_returning_null(&self) -> *const Self {
ptr::null()
}
#[sel(methodReturningNull)]
fn method_returning_null(&self) -> *const Self {
ptr::null()
}
}
);
unsafe impl Send for RcTestObject {}
unsafe impl Sync for RcTestObject {}
impl RcTestObject {
pub(crate) fn new() -> Id<Self, Owned> {
// Use msg_send! - msg_send_id! is tested elsewhere!
unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ensure_declared_name() {
assert_eq!(RcTestObject::class().name(), RcTestObject::NAME);
}
}

View file

@ -0,0 +1,221 @@
use alloc::boxed::Box;
use core::cell::UnsafeCell;
use core::fmt;
use core::marker::PhantomData;
use core::ptr;
use std::panic::{RefUnwindSafe, UnwindSafe};
use super::{Id, Shared};
use crate::ffi;
use crate::Message;
/// A pointer type for a weak reference to an Objective-C reference counted
/// object.
///
/// Allows breaking reference cycles and safely checking whether the object
/// has been deallocated.
#[repr(transparent)]
pub struct WeakId<T: ?Sized> {
/// We give the runtime the address to this box, so that it can modify it
/// even if the `WeakId` is moved.
///
/// Loading may modify the pointer through a shared reference, so we use
/// an UnsafeCell to get a *mut without self being mutable.
///
/// Remember that any thread may actually modify the inner value
/// concurrently, but as long as we only use it through the `objc_XXXWeak`
/// methods, all access is behind a lock.
///
/// TODO: Verify the need for UnsafeCell?
/// TODO: Investigate if we can avoid some allocations using `Pin`.
inner: Box<UnsafeCell<*mut ffi::objc_object>>,
/// WeakId inherits variance, dropck and various marker traits from
/// `Id<T, Shared>` because it can be loaded as a shared Id.
item: PhantomData<Id<T, Shared>>,
}
impl<T: Message> WeakId<T> {
/// Construct a new [`WeakId`] referencing the given shared [`Id`].
#[doc(alias = "objc_initWeak")]
#[inline]
pub fn new(obj: &Id<T, Shared>) -> Self {
// Note that taking `&Id<T, Owned>` would not be safe since that would
// allow loading an `Id<T, Shared>` later on.
// SAFETY: `obj` is valid
unsafe { Self::new_inner(Id::as_ptr(obj)) }
}
/// # Safety
///
/// The object must be valid or null.
unsafe fn new_inner(obj: *const T) -> Self {
let inner = Box::new(UnsafeCell::new(ptr::null_mut()));
// SAFETY: `ptr` will never move, and the caller verifies `obj`
let _ = unsafe { ffi::objc_initWeak(inner.get(), (obj as *mut T).cast()) };
Self {
inner,
item: PhantomData,
}
}
/// Load a shared (and retained) [`Id`] if the object still exists.
///
/// Returns [`None`] if the object has been deallocated or was created
/// with [`Default::default`].
#[doc(alias = "retain")]
#[doc(alias = "objc_loadWeak")]
#[doc(alias = "objc_loadWeakRetained")]
#[inline]
pub fn load(&self) -> Option<Id<T, Shared>> {
let ptr = self.inner.get();
let obj = unsafe { ffi::objc_loadWeakRetained(ptr) }.cast();
unsafe { Id::new(obj) }
}
// TODO: Add `autorelease(&self) -> Option<&T>` using `objc_loadWeak`?
}
impl<T: ?Sized> Drop for WeakId<T> {
/// Drops the `WeakId` pointer.
#[doc(alias = "objc_destroyWeak")]
#[inline]
fn drop(&mut self) {
unsafe { ffi::objc_destroyWeak(self.inner.get()) }
}
}
// TODO: Add ?Sized
impl<T> Clone for WeakId<T> {
/// Makes a clone of the `WeakId` that points to the same object.
#[doc(alias = "objc_copyWeak")]
fn clone(&self) -> Self {
let ptr = Box::new(UnsafeCell::new(ptr::null_mut()));
unsafe { ffi::objc_copyWeak(ptr.get(), self.inner.get()) };
Self {
inner: ptr,
item: PhantomData,
}
}
}
// TODO: Add ?Sized
impl<T: Message> Default for WeakId<T> {
/// Constructs a new `WeakId<T>` that doesn't reference any object.
///
/// Calling [`Self::load`] on the return value always gives [`None`].
#[inline]
fn default() -> Self {
// SAFETY: The pointer is null
unsafe { Self::new_inner(ptr::null()) }
}
}
/// This implementation follows the same reasoning as `Id<T, Shared>`.
unsafe impl<T: Sync + Send + ?Sized> Sync for WeakId<T> {}
/// This implementation follows the same reasoning as `Id<T, Shared>`.
unsafe impl<T: Sync + Send + ?Sized> Send for WeakId<T> {}
// Unsure about the Debug bound on T, see std::sync::Weak
impl<T: fmt::Debug + ?Sized> fmt::Debug for WeakId<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(WeakId)")
}
}
// Underneath this is just a `Box`
impl<T: ?Sized> Unpin for WeakId<T> {}
// Same as `Id<T, Shared>`.
impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for WeakId<T> {}
// Same as `Id<T, Shared>`.
impl<T: RefUnwindSafe + ?Sized> UnwindSafe for WeakId<T> {}
impl<T: Message> From<Id<T, Shared>> for WeakId<T> {
#[inline]
fn from(obj: Id<T, Shared>) -> Self {
WeakId::new(&obj)
}
}
impl<T: Message> TryFrom<WeakId<T>> for Id<T, Shared> {
type Error = ();
fn try_from(weak: WeakId<T>) -> Result<Self, ()> {
weak.load().ok_or(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rc::{RcTestObject, ThreadTestData};
use crate::runtime::Object;
#[test]
fn test_weak() {
let obj: Id<_, Shared> = RcTestObject::new().into();
let mut expected = ThreadTestData::current();
let weak = WeakId::new(&obj);
expected.assert_current();
let strong = weak.load().unwrap();
expected.try_retain += 1;
expected.assert_current();
assert!(ptr::eq(&*strong, &*obj));
drop(obj);
drop(strong);
expected.release += 2;
expected.dealloc += 1;
expected.assert_current();
if cfg!(not(feature = "gnustep-1-7")) {
// This loads the object on GNUStep for some reason??
assert!(weak.load().is_none());
expected.assert_current();
}
drop(weak);
expected.assert_current();
}
#[test]
fn test_weak_clone() {
let obj: Id<_, Shared> = RcTestObject::new().into();
let mut expected = ThreadTestData::current();
let weak = WeakId::new(&obj);
expected.assert_current();
let weak2 = weak.clone();
if cfg!(feature = "apple") {
expected.try_retain += 1;
expected.release += 1;
}
expected.assert_current();
let strong = weak.load().unwrap();
expected.try_retain += 1;
expected.assert_current();
assert!(ptr::eq(&*strong, &*obj));
let strong2 = weak2.load().unwrap();
expected.try_retain += 1;
expected.assert_current();
assert!(ptr::eq(&*strong, &*strong2));
drop(weak);
drop(weak2);
expected.assert_current();
}
#[test]
fn test_weak_default() {
let weak: WeakId<Object> = WeakId::default();
assert!(weak.load().is_none());
drop(weak);
}
}

1037
third-party/vendor/objc2/src/runtime.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,247 @@
use core::mem::ManuallyDrop;
use core::ops::{Deref, DerefMut};
use std::os::raw::c_char;
use std::sync::Once;
use crate::declare::{ClassBuilder, ProtocolBuilder};
use crate::runtime::{Class, Object, Protocol, Sel};
use crate::{ffi, Encode, Encoding, MessageReceiver};
use crate::{msg_send, sel};
#[derive(Debug)]
pub(crate) struct CustomObject {
obj: *mut Object,
}
impl CustomObject {
fn new(class: &Class) -> Self {
let ptr: *const Class = class;
let obj = unsafe { ffi::class_createInstance(ptr.cast(), 0) }.cast();
CustomObject { obj }
}
}
// TODO: Remove the need for this hack
impl crate::message::private::Sealed for &CustomObject {}
impl crate::message::private::Sealed for &mut CustomObject {}
impl crate::message::private::Sealed for ManuallyDrop<CustomObject> {}
unsafe impl MessageReceiver for &CustomObject {
type __Inner = Object;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
self.obj
}
}
unsafe impl MessageReceiver for &mut CustomObject {
type __Inner = Object;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
self.obj
}
}
unsafe impl MessageReceiver for ManuallyDrop<CustomObject> {
type __Inner = Object;
#[inline]
fn __as_raw_receiver(self) -> *mut Object {
self.obj
}
}
impl Deref for CustomObject {
type Target = Object;
fn deref(&self) -> &Object {
unsafe { self.obj.as_ref().unwrap_unchecked() }
}
}
impl DerefMut for CustomObject {
fn deref_mut(&mut self) -> &mut Object {
unsafe { self.obj.as_mut().unwrap_unchecked() }
}
}
impl Drop for CustomObject {
fn drop(&mut self) {
unsafe {
#[allow(deprecated)]
ffi::object_dispose(self.obj.cast());
}
}
}
#[derive(Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct CustomStruct {
pub(crate) a: u64,
pub(crate) b: u64,
pub(crate) c: u64,
pub(crate) d: u64,
}
unsafe impl Encode for CustomStruct {
const ENCODING: Encoding = Encoding::Struct(
"CustomStruct",
&[u64::ENCODING, u64::ENCODING, u64::ENCODING, u64::ENCODING],
);
}
pub(crate) fn custom_class() -> &'static Class {
static REGISTER_CUSTOM_CLASS: Once = Once::new();
REGISTER_CUSTOM_CLASS.call_once(|| {
// The runtime will call this method, so it has to be implemented
extern "C" fn custom_obj_class_initialize(_this: &Class, _cmd: Sel) {}
let mut builder = ClassBuilder::root(
"CustomObject",
custom_obj_class_initialize as extern "C" fn(_, _),
)
.unwrap();
let proto = custom_protocol();
builder.add_protocol(proto);
builder.add_ivar::<u32>("_foo");
unsafe extern "C" fn custom_obj_release(this: *mut Object, _cmd: Sel) {
// Drop the value
let _ = CustomObject { obj: this };
}
extern "C" fn custom_obj_set_foo(this: &mut Object, _cmd: Sel, foo: u32) {
unsafe { this.set_ivar::<u32>("_foo", foo) }
}
extern "C" fn custom_obj_get_foo(this: &Object, _cmd: Sel) -> u32 {
unsafe { *this.ivar::<u32>("_foo") }
}
extern "C" fn custom_obj_get_foo_reference(this: &Object, _cmd: Sel) -> &u32 {
unsafe { this.ivar::<u32>("_foo") }
}
extern "C" fn custom_obj_get_struct(_this: &Object, _cmd: Sel) -> CustomStruct {
CustomStruct {
a: 1,
b: 2,
c: 3,
d: 4,
}
}
extern "C" fn custom_obj_class_method(_this: &Class, _cmd: Sel) -> u32 {
7
}
extern "C" fn custom_obj_set_bar(this: &mut Object, _cmd: Sel, bar: u32) {
unsafe {
this.set_ivar::<u32>("_foo", bar);
}
}
extern "C" fn custom_obj_add_number_to_number(
_this: &Class,
_cmd: Sel,
fst: i32,
snd: i32,
) -> i32 {
fst + snd
}
unsafe {
let release: unsafe extern "C" fn(_, _) = custom_obj_release;
builder.add_method(sel!(release), release);
let set_foo: extern "C" fn(_, _, _) = custom_obj_set_foo;
builder.add_method(sel!(setFoo:), set_foo);
let get_foo: extern "C" fn(_, _) -> _ = custom_obj_get_foo;
builder.add_method(sel!(foo), get_foo);
let get_foo_reference: extern "C" fn(_, _) -> _ = custom_obj_get_foo_reference;
builder.add_method(sel!(fooReference), get_foo_reference);
let get_struct: extern "C" fn(_, _) -> CustomStruct = custom_obj_get_struct;
builder.add_method(sel!(customStruct), get_struct);
let class_method: extern "C" fn(_, _) -> _ = custom_obj_class_method;
builder.add_class_method(sel!(classFoo), class_method);
let protocol_instance_method: extern "C" fn(_, _, _) = custom_obj_set_bar;
builder.add_method(sel!(setBar:), protocol_instance_method);
let protocol_class_method: extern "C" fn(_, _, _, _) -> _ =
custom_obj_add_number_to_number;
builder.add_class_method(sel!(addNumber:toNumber:), protocol_class_method);
}
builder.register();
});
// Can't use `class!` here since `CustomObject` is dynamically created.
Class::get("CustomObject").unwrap()
}
pub(crate) fn custom_protocol() -> &'static Protocol {
static REGISTER_CUSTOM_PROTOCOL: Once = Once::new();
REGISTER_CUSTOM_PROTOCOL.call_once(|| {
let mut builder = ProtocolBuilder::new("CustomProtocol").unwrap();
builder.add_method_description::<(i32,), ()>(sel!(setBar:), true);
builder.add_method_description::<(), *const c_char>(sel!(getName), false);
builder.add_class_method_description::<(i32, i32), i32>(sel!(addNumber:toNumber:), true);
builder.register();
});
Protocol::get("CustomProtocol").unwrap()
}
pub(crate) fn custom_subprotocol() -> &'static Protocol {
static REGISTER_CUSTOM_SUBPROTOCOL: Once = Once::new();
REGISTER_CUSTOM_SUBPROTOCOL.call_once(|| {
let super_proto = custom_protocol();
let mut builder = ProtocolBuilder::new("CustomSubProtocol").unwrap();
builder.add_protocol(super_proto);
builder.add_method_description::<(u32,), u32>(sel!(calculateFoo:), true);
builder.register();
});
Protocol::get("CustomSubProtocol").unwrap()
}
pub(crate) fn custom_object() -> CustomObject {
CustomObject::new(custom_class())
}
pub(crate) fn custom_subclass() -> &'static Class {
static REGISTER_CUSTOM_SUBCLASS: Once = Once::new();
REGISTER_CUSTOM_SUBCLASS.call_once(|| {
let superclass = custom_class();
let mut builder = ClassBuilder::new("CustomSubclassObject", superclass).unwrap();
extern "C" fn custom_subclass_get_foo(this: &Object, _cmd: Sel) -> u32 {
let foo: u32 = unsafe { msg_send![super(this, custom_class()), foo] };
foo + 2
}
unsafe {
let get_foo: extern "C" fn(_, _) -> _ = custom_subclass_get_foo;
builder.add_method(sel!(foo), get_foo);
}
builder.register();
});
Class::get("CustomSubclassObject").unwrap()
}
pub(crate) fn custom_subclass_object() -> CustomObject {
CustomObject::new(custom_subclass())
}

216
third-party/vendor/objc2/src/verify.rs vendored Normal file
View file

@ -0,0 +1,216 @@
use core::fmt;
use core::hash::{Hash, Hasher};
use malloc_buf::Malloc;
use std::error::Error;
use crate::encode::{Encode, EncodeArguments, EncodeConvert, Encoding};
use crate::runtime::{Class, Object, Sel};
/// Workaround for `Malloc<str>` not implementing common traits
#[derive(Debug)]
struct MallocEncoding(Malloc<str>);
// SAFETY: `char*` strings can safely be free'd on other threads.
unsafe impl Send for MallocEncoding {}
unsafe impl Sync for MallocEncoding {}
impl PartialEq for MallocEncoding {
fn eq(&self, other: &Self) -> bool {
*self.0 == *other.0
}
}
impl Eq for MallocEncoding {}
impl Hash for MallocEncoding {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&*self.0, state)
}
}
impl fmt::Display for MallocEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.0, f)
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum Inner {
MethodNotFound,
MismatchedReturn(MallocEncoding, Encoding),
MismatchedArgumentsCount(usize, usize),
MismatchedArgument(usize, MallocEncoding, Encoding),
}
impl fmt::Display for Inner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MethodNotFound => {
write!(f, "method not found")
}
Self::MismatchedReturn(expected, actual) => {
write!(
f,
"expected return to have type code {}, but found {}",
expected, actual
)
}
Self::MismatchedArgumentsCount(expected, actual) => {
write!(
f,
"expected {} arguments, but {} were given",
expected, actual
)
}
Self::MismatchedArgument(i, expected, actual) => {
write!(
f,
"expected argument at index {} to have type code {}, but found {}",
i, expected, actual
)
}
}
}
}
/// Failed verifying selector on a class.
///
/// This is returned in the error case of [`Class::verify_sel`], see that for
/// details.
///
/// This implements [`Error`], and a description of the error can be retrieved
/// using [`fmt::Display`].
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct VerificationError(Inner);
impl From<Inner> for VerificationError {
fn from(inner: Inner) -> Self {
Self(inner)
}
}
impl fmt::Display for VerificationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Delegate to inner
fmt::Display::fmt(&self.0, f)
}
}
impl Error for VerificationError {}
pub(crate) fn verify_message_signature<A, R>(cls: &Class, sel: Sel) -> Result<(), VerificationError>
where
A: EncodeArguments,
R: EncodeConvert,
{
let method = match cls.instance_method(sel) {
Some(method) => method,
None => return Err(Inner::MethodNotFound.into()),
};
let actual = R::__Inner::ENCODING;
let expected = method.return_type();
if !actual.equivalent_to_str(&*expected) {
return Err(Inner::MismatchedReturn(MallocEncoding(expected), actual).into());
}
let self_and_cmd = [<*mut Object>::ENCODING, Sel::ENCODING];
let args = A::ENCODINGS;
let actual = self_and_cmd.len() + args.len();
let expected = method.arguments_count();
if actual != expected {
return Err(Inner::MismatchedArgumentsCount(expected, actual).into());
}
for (i, actual) in self_and_cmd.iter().chain(args).copied().enumerate() {
let expected = method.argument_type(i).unwrap();
if !actual.equivalent_to_str(&*expected) {
return Err(Inner::MismatchedArgument(i, MallocEncoding(expected), actual).into());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sel;
use crate::test_utils;
use alloc::string::ToString;
use core::panic::{RefUnwindSafe, UnwindSafe};
#[test]
fn test_verify_message() {
let cls = test_utils::custom_class();
assert!(cls.verify_sel::<(), u32>(sel!(foo)).is_ok());
assert!(cls.verify_sel::<(u32,), ()>(sel!(setFoo:)).is_ok());
let metaclass = cls.metaclass();
metaclass
.verify_sel::<(i32, i32), i32>(sel!(addNumber:toNumber:))
.unwrap();
}
#[test]
fn test_verify_message_errors() {
let cls = test_utils::custom_class();
// Unimplemented selector (missing colon)
let err = cls.verify_sel::<(), ()>(sel!(setFoo)).unwrap_err();
assert_eq!(err.to_string(), "method not found");
// Incorrect return type
let err = cls.verify_sel::<(u32,), u64>(sel!(setFoo:)).unwrap_err();
assert_eq!(
err.to_string(),
"expected return to have type code v, but found Q"
);
// Too many arguments
let err = cls.verify_sel::<(u32, i8), ()>(sel!(setFoo:)).unwrap_err();
assert_eq!(err.to_string(), "expected 3 arguments, but 4 were given");
// Too few arguments
let err = cls.verify_sel::<(), ()>(sel!(setFoo:)).unwrap_err();
assert_eq!(err.to_string(), "expected 3 arguments, but 2 were given");
// Incorrect argument type
let err = cls.verify_sel::<(Sel,), ()>(sel!(setFoo:)).unwrap_err();
assert_eq!(
err.to_string(),
"expected argument at index 2 to have type code I, but found :"
);
// Metaclass
let metaclass = cls.metaclass();
let err = metaclass
.verify_sel::<(i32, i32, i32), i32>(sel!(addNumber:toNumber:))
.unwrap_err();
assert_eq!(err.to_string(), "expected 4 arguments, but 5 were given");
}
#[test]
#[cfg(feature = "verify_message")]
#[should_panic = "invalid message send to -[CustomObject foo]: expected return to have type code I, but found i"]
fn test_send_message_verified() {
let obj = test_utils::custom_object();
let _: i32 = unsafe { crate::msg_send![&obj, foo] };
}
#[test]
#[cfg(feature = "verify_message")]
#[should_panic = "invalid message send to +[CustomObject abcDef]: method not found"]
fn test_send_message_verified_to_class() {
let cls = test_utils::custom_class();
let _: i32 = unsafe { crate::msg_send![cls, abcDef] };
}
#[test]
fn test_marker_traits() {
fn assert_marker_traits<T: Send + Sync + UnwindSafe + RefUnwindSafe + Unpin>() {}
assert_marker_traits::<VerificationError>();
}
}

View file

@ -0,0 +1,76 @@
use std::ffi::c_void;
use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::runtime::Object;
use objc2::{class, msg_send};
#[cfg(feature = "gnustep-1-7")]
#[test]
fn ensure_linkage() {
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
}
fn retain_count(obj: &Object) -> usize {
unsafe { msg_send![obj, retainCount] }
}
fn create_data(bytes: &[u8]) -> Id<Object, Shared> {
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
unsafe {
// let obj: *mut Object = msg_send![
// class!(NSMutableData),
// dataWithBytes: bytes_ptr,
// length: bytes.len(),
// ];
//
// On x86 (and perhaps others), dataWithBytes does not tail call
// `autorelease` and hence the return address points into that instead
// of our code, making the fast autorelease scheme fail.
//
// So instead, we call `autorelease` manually here.
let obj: *mut Object = msg_send![class!(NSMutableData), alloc];
let obj: *mut Object = msg_send![
obj,
initWithBytes: bytes_ptr,
length: bytes.len(),
];
let obj: *mut Object = msg_send![obj, autorelease];
// All code between the `msg_send!` and the `retain_autoreleased` must
// be able to be optimized away for this to work.
Id::retain_autoreleased(obj).unwrap()
}
}
#[test]
fn test_retain_autoreleased() {
autoreleasepool(|_| {
// Run once to allow DYLD to resolve the symbol stubs.
// Required for making `retain_autoreleased` work on x86_64.
let _data = create_data(b"12");
// When compiled in release mode / with optimizations enabled,
// subsequent usage of `retain_autoreleased` will succeed in retaining
// the autoreleased value!
let expected = if cfg!(feature = "gnustep-1-7") {
1
} else if cfg!(any(
debug_assertions,
feature = "exception",
feature = "verify_message"
)) {
2
} else {
1
};
let data = create_data(b"34");
assert_eq!(retain_count(&data), expected);
let data = create_data(b"56");
assert_eq!(retain_count(&data), expected);
// Here we manually clean up the autorelease, so it will always be 1.
let data = autoreleasepool(|_| create_data(b"78"));
assert_eq!(retain_count(&data), 1);
});
}

View file

@ -0,0 +1,116 @@
//! Tests macros in a hostile environment (`#![no_implicit_prelude]` and all
//! functions, modules, traits, and types replaced with custom bogus user
//! replacements).
//!
//! Heavy inspiration for this file taken from `objrs`:
//! https://gitlab.com/objrs/objrs/-/blob/b4f6598696b3fa622e6fddce7aff281770b0a8c2/test/src/no_prelude.rs
#![no_implicit_prelude]
#![allow(dead_code, non_camel_case_types)]
extern crate objc2 as new_objc2;
#[cfg(feature = "gnustep-1-7")]
#[test]
fn ensure_linkage() {
unsafe { new_objc2::__gnustep_hack::get_class_to_force_linkage() };
}
mod core {}
mod std {}
mod libc {}
mod objc2 {}
enum BogusType {}
type u8 = BogusType;
type u16 = BogusType;
type u32 = BogusType;
type u64 = BogusType;
type u128 = BogusType;
type usize = BogusType;
type i8 = BogusType;
type i16 = BogusType;
type i32 = BogusType;
type i64 = BogusType;
type i128 = BogusType;
type isize = BogusType;
type bool = BogusType;
type char = BogusType;
type str = BogusType;
type f32 = BogusType;
type f64 = BogusType;
type Option = BogusType;
type Some = BogusType;
type None = BogusType;
type Result = BogusType;
type Ok = BogusType;
type Err = BogusType;
type Box = BogusType;
type String = BogusType;
type Vec = BogusType;
type drop = BogusType;
type Copy = BogusType;
type Send = BogusType;
type Sized = BogusType;
type Sync = BogusType;
type Drop = BogusType;
type Fn = BogusType;
type FnMut = BogusType;
type FnOnce = BogusType;
type ToOwned = BogusType;
type Clone = BogusType;
type PartialEq = BogusType;
type PartialOrd = BogusType;
type Eq = BogusType;
type Ord = BogusType;
type AsRef = BogusType;
type AsMut = BogusType;
type Into = BogusType;
type From = BogusType;
type Default = BogusType;
type Hash = BogusType;
type Debug = BogusType;
type Iterator = BogusType;
type Extend = BogusType;
type IntoIterator = BogusType;
type DoubleEndedIterator = BogusType;
type ExactSizeIterator = BogusType;
type SliceConcatExt = BogusType;
type ToString = BogusType;
pub fn test_selector() {
let _sel = new_objc2::sel!(abc);
let _sel = new_objc2::sel!(abc:def:);
}
pub fn test_class() {
let _class = new_objc2::class!(NSObject);
}
pub fn test_msg_send(obj: &new_objc2::foundation::NSString) {
let superclass = obj.class().superclass().unwrap();
let _: () = unsafe { new_objc2::msg_send![obj, a] };
let _: () = unsafe { new_objc2::msg_send![obj, a: obj, b: obj] };
let _: () = unsafe { new_objc2::msg_send![super(obj), a] };
let _: () = unsafe { new_objc2::msg_send![super(obj), a: obj, b: obj] };
let _: () = unsafe { new_objc2::msg_send![super(obj, superclass), a] };
let _: () = unsafe { new_objc2::msg_send![super(obj, superclass), a: obj, b: obj] };
}
pub fn test_msg_send_id(obj: &new_objc2::runtime::Object) {
let _: new_objc2::rc::Id<new_objc2::runtime::Object, new_objc2::rc::Shared> =
unsafe { new_objc2::msg_send_id![obj, a] };
let _: new_objc2::__macro_helpers::Option<
new_objc2::rc::Id<new_objc2::runtime::Object, new_objc2::rc::Shared>,
> = unsafe { new_objc2::msg_send_id![obj, a] };
let _: new_objc2::rc::Id<new_objc2::runtime::Object, new_objc2::rc::Shared> =
unsafe { new_objc2::msg_send_id![obj, a: obj, b: obj] };
}

View file

@ -0,0 +1,59 @@
use objc2::foundation::NSString;
use objc2::runtime::{Class, Object};
use objc2::{class, msg_send, sel};
#[cfg(feature = "gnustep-1-7")]
#[test]
fn ensure_linkage() {
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
}
#[test]
fn use_class_and_msg_send() {
unsafe {
let cls = class!(NSObject);
let obj: *mut Object = msg_send![cls, new];
let _hash: usize = msg_send![obj, hash];
let _: () = msg_send![obj, release];
}
}
#[test]
fn use_sel() {
let _sel = sel!(description);
let _sel = sel!(setObject:forKey:);
}
#[allow(unused)]
fn test_msg_send_comma_handling(obj: &NSString, superclass: &Class) {
unsafe {
let _: () = msg_send![obj, a];
let _: () = msg_send![obj, a,];
let _: () = msg_send![obj, a: 32i32];
let _: () = msg_send![obj, a: 32i32,];
let _: () = msg_send![obj, a: 32i32 b: 32i32];
let _: () = msg_send![obj, a: 32i32 b: 32i32,];
let _: () = msg_send![obj, a: 32i32, b: 32i32];
let _: () = msg_send![obj, a: 32i32, b: 32i32,];
}
unsafe {
let _: () = msg_send![super(obj, superclass), a];
let _: () = msg_send![super(obj, superclass), a,];
let _: () = msg_send![super(obj, superclass), a: 32i32];
let _: () = msg_send![super(obj, superclass), a: 32i32,];
let _: () = msg_send![super(obj, superclass), a: 32i32 b: 32i32];
let _: () = msg_send![super(obj, superclass), a: 32i32 b: 32i32,];
let _: () = msg_send![super(obj, superclass), a: 32i32, b: 32i32];
let _: () = msg_send![super(obj, superclass), a: 32i32, b: 32i32,];
}
unsafe {
let _: () = msg_send![super(obj), a];
let _: () = msg_send![super(obj), a,];
let _: () = msg_send![super(obj), a: 32i32];
let _: () = msg_send![super(obj), a: 32i32,];
let _: () = msg_send![super(obj), a: 32i32, b: 32i32];
let _: () = msg_send![super(obj), a: 32i32, b: 32i32,];
}
}