Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RefEncode, and make Encode bounds required #14

Merged
merged 9 commits into from
Sep 1, 2021
14 changes: 10 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: check
args: --verbose
args: --verbose --no-default-features

- name: Install GNUStep libobjc2
if: contains(matrix.platform.os, 'ubuntu')
Expand Down Expand Up @@ -116,10 +116,16 @@ jobs:
echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
echo "CPATH=/usr/local/include:$CPATH" >> $GITHUB_ENV
- name: Build and run tests
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
# TODO: `objc/exception` feature is broken in objc_foundation
# TODO: `objc_foundation/block` feature doesn't work on GNUStep
# TODO: `objc_foundation/block` feature doesn't work
args: --verbose --no-fail-fast --no-default-features

- name: Test w. exception and verify_message features
uses: actions-rs/cargo@v1
with:
command: test
# TODO: `objc_foundation/block` feature doesn't work
args: --verbose --no-fail-fast --no-default-features --features exception,verify_message
51 changes: 6 additions & 45 deletions objc/src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,23 @@
use crate::runtime::{Class, Object, Sel};
use crate::{Encode, Encoding};
use crate::{Encode, Encoding, RefEncode};

unsafe impl Encode for Sel {
const ENCODING: Encoding<'static> = Encoding::Sel;
}

unsafe impl<'a> Encode for &'a Object {
const ENCODING: Encoding<'static> = Encoding::Object;
unsafe impl RefEncode for Object {
const ENCODING_REF: Encoding<'static> = Encoding::Object;
}

unsafe impl<'a> Encode for &'a mut Object {
const ENCODING: Encoding<'static> = Encoding::Object;
unsafe impl RefEncode for Class {
const ENCODING_REF: Encoding<'static> = Encoding::Class;
}

unsafe impl<'a> Encode for &'a Class {
const ENCODING: Encoding<'static> = Encoding::Class;
}

unsafe impl<'a> Encode for &'a mut Class {
const ENCODING: Encoding<'static> = Encoding::Class;
}

/// Types that represent a group of arguments, where each has an Objective-C
/// type-encoding.
pub trait EncodeArguments {
/// The type as which the encodings for Self will be returned.
const ENCODINGS: &'static [Encoding<'static>];
}

macro_rules! encode_args_impl {
($($t:ident),*) => (
impl<$($t: Encode),*> EncodeArguments for ($($t,)*) {
const ENCODINGS: &'static [Encoding<'static>] = &[
$($t::ENCODING),*
];
}
);
}

encode_args_impl!();
encode_args_impl!(A);
encode_args_impl!(A, B);
encode_args_impl!(A, B, C);
encode_args_impl!(A, B, C, D);
encode_args_impl!(A, B, C, D, E);
encode_args_impl!(A, B, C, D, E, F);
encode_args_impl!(A, B, C, D, E, F, G);
encode_args_impl!(A, B, C, D, E, F, G, H);
encode_args_impl!(A, B, C, D, E, F, G, H, I);
encode_args_impl!(A, B, C, D, E, F, G, H, I, J);
encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K);
encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K, L);

#[cfg(test)]
mod tests {
use crate::runtime::{Class, Object, Sel};
use crate::Encode;
use alloc::string::ToString;
use objc_encode::Encode;

#[test]
fn test_encode() {
Expand Down
3 changes: 1 addition & 2 deletions objc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ extern "C" {}
#[doc = include_str!("../../README.md")]
extern "C" {}

pub use objc_encode::{Encode, Encoding};
pub use objc_encode::{Encode, EncodeArguments, Encoding, RefEncode};

pub use crate::encode::EncodeArguments;
pub use crate::message::{Message, MessageArguments, MessageError};

pub use crate::cache::CachedClass as __CachedClass;
Expand Down
88 changes: 26 additions & 62 deletions objc/src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ use core::mem;
use std::error::Error;

use crate::runtime::{Class, Imp, Object, Sel};
use crate::{Encode, EncodeArguments};
use crate::{Encode, EncodeArguments, RefEncode};

#[cfg(feature = "exception")]
macro_rules! objc_try {
($b:block) => {
$crate::exception::catch_exception(|| $b).map_err(|exception| {
use alloc::borrow::ToOwned;
if exception.is_null() {
MessageError("Uncaught exception nil".to_owned())
} else {
MessageError(format!("Uncaught exception {:?}", &**exception))
MessageError(alloc::format!("Uncaught exception {:?}", &**exception))
}
})
};
Expand Down Expand Up @@ -52,18 +53,19 @@ struct Super {
///
/// Examples include objects, classes, and blocks.
///
/// The type should also implement [`Encode`] for `&Self` and `&mut Self`.
/// Implementing this allows using pointers and references to the type as the
/// receiver (first argument) in the [`msg_send!`][`crate::msg_send`] macro.
///
/// # Safety
///
/// The type must implement [`RefEncode`] and adhere to the safety guidelines
/// therein.
///
/// A pointer to the type must be able to be the receiver of an Objective-C
/// message sent with [`objc_msgSend`] or similar.
///
/// The type must also have a C-compatible `repr` (`repr(C)`, `repr(u8)`,
/// `repr(transparent)` where the inner types are C-compatible, and so on).
///
/// [`objc_msgSend`]: https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend
pub unsafe trait Message {
pub unsafe trait Message: RefEncode {
/**
Sends a message to self with the given selector and arguments.
Expand All @@ -74,18 +76,6 @@ pub unsafe trait Message {
If the selector is known at compile-time, it is recommended to use the
`msg_send!` macro rather than this method.
*/
#[cfg(not(feature = "verify_message"))]
unsafe fn send_message<A, R>(&self, sel: Sel, args: A) -> Result<R, MessageError>
where
Self: Sized,
A: MessageArguments,
R: Any,
{
send_message(self, sel, args)
}

#[allow(missing_docs)]
#[cfg(feature = "verify_message")]
unsafe fn send_message<A, R>(&self, sel: Sel, args: A) -> Result<R, MessageError>
where
Self: Sized,
Expand Down Expand Up @@ -226,56 +216,28 @@ impl<'a> From<VerificationError<'a>> for MessageError {
}

#[doc(hidden)]
#[inline(always)]
#[cfg(not(feature = "verify_message"))]
pub unsafe fn send_message<T, A, R>(obj: *const T, sel: Sel, args: A) -> Result<R, MessageError>
where
T: Message,
A: MessageArguments,
R: Any,
{
send_unverified(obj, sel, args)
}

#[doc(hidden)]
#[inline(always)]
#[cfg(feature = "verify_message")]
#[cfg_attr(feature = "verify_message", inline(always))]
pub unsafe fn send_message<T, A, R>(obj: *const T, sel: Sel, args: A) -> Result<R, MessageError>
where
T: Message,
A: MessageArguments + EncodeArguments,
R: Any + Encode,
{
let cls = if obj.is_null() {
return Err(VerificationError::NilReceiver(sel).into());
} else {
(*(obj as *const Object)).class()
};
#[cfg(feature = "verify_message")]
{
let cls = if obj.is_null() {
return Err(VerificationError::NilReceiver(sel).into());
} else {
(*(obj as *const Object)).class()
};

verify_message_signature::<A, R>(cls, sel)?;
verify_message_signature::<A, R>(cls, sel)?;
}
send_unverified(obj, sel, args)
}

#[doc(hidden)]
#[inline(always)]
#[cfg(not(feature = "verify_message"))]
pub unsafe fn send_super_message<T, A, R>(
obj: *const T,
superclass: &Class,
sel: Sel,
args: A,
) -> Result<R, MessageError>
where
T: Message,
A: MessageArguments,
R: Any,
{
send_super_unverified(obj, superclass, sel, args)
}

#[doc(hidden)]
#[inline(always)]
#[cfg(feature = "verify_message")]
#[cfg_attr(feature = "verify_message", inline(always))]
pub unsafe fn send_super_message<T, A, R>(
obj: *const T,
superclass: &Class,
Expand All @@ -287,11 +249,13 @@ where
A: MessageArguments + EncodeArguments,
R: Any + Encode,
{
if obj.is_null() {
return Err(VerificationError::NilReceiver(sel).into());
#[cfg(feature = "verify_message")]
{
if obj.is_null() {
return Err(VerificationError::NilReceiver(sel).into());
}
verify_message_signature::<A, R>(superclass, sel)?;
}

verify_message_signature::<A, R>(superclass, sel)?;
send_super_unverified(obj, superclass, sel, args)
}

Expand Down
5 changes: 3 additions & 2 deletions objc_encode/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[package]
name = "objc-encode"
version = "1.1.0" # Remember to update html_root_url in lib.rs
# Remember to update html_root_url in lib.rs and README.md
version = "1.1.0"
authors = ["Steven Sheldon", "Mads Marquart <[email protected]>"]
edition = "2018"

description = "Objective-C type encoding creation and parsing."
description = "Objective-C type-encodings"
keywords = ["objective-c", "macos", "ios", "encode"]
categories = [
"development-tools::ffi",
Expand Down
81 changes: 50 additions & 31 deletions objc_encode/README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,83 @@
# `objc-encode`
# `objc-encode` - Objective-C type-encoding in Rust

[![Latest version](https://badgen.net/crates/v/objc-encode)](https://crates.io/crates/objc-encode)
[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt)
[![Documentation](https://docs.rs/objc-encode/badge.svg)](https://docs.rs/objc-encode/)
[![CI Status](https://github.com/madsmtm/objc/workflows/CI/badge.svg)](https://github.com/madsmtm/objc/actions)

Objective-C type encoding creation and parsing in Rust.
The Objective-C directive `@encode` encodes types as strings for usage in
various places in the runtime.

The Objective-C compiler encodes types as strings for usage in the runtime.
This crate aims to provide a strongly-typed (rather than stringly-typed) way
to create and describe these type encodings without memory allocation in Rust.
This crate provides the `Encoding` type to describe and compare these
type-encodings without memory allocation.

Additionally it provides traits for annotating types that has a corresponding
Objective-C encoding, respectively `Encode` for structs and `RefEncode` for
references (and `EncodeArguments` for function arguments).

## Implementing Encode
These types are exported under the `objc` crate as well, so usually you would
just use that crate.

This crate declares an `Encode` trait that can be implemented for types that
the Objective-C compiler can encode. Implementing this trait looks like:
# Examples

```rust
use objc::{Encode, Encoding};

#[cfg(target_pointer_width = "32")]
type CGFloat = f32;
Implementing `Encode` and `RefEncode`:

#[cfg(target_pointer_width = "64")]
type CGFloat = f64;
```rust
use objc_encode::{Encode, Encoding, RefEncode};

#[repr(C)]
struct CGPoint {
x: CGFloat,
y: CGFloat,
struct MyObject {
a: f32,
b: bool,
}

unsafe impl Encode for CGPoint {
const ENCODING: Encoding<'static> =
Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFloat::ENCODING]);
unsafe impl Encode for MyObject {
const ENCODING: Encoding<'static> = Encoding::Struct(
"MyObject",
&[f32::ENCODING, bool::ENCODING
]);
}
```

For an example of how this works with more complex types, like structs
containing structs, see the `core_graphics` example.
assert_eq!(&MyObject::ENCODING, "{MyObject=fB}");

## Comparing with encoding strings
unsafe impl RefEncode for MyObject {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}

assert_eq!(&MyObject::ENCODING_REF, "^{MyObject=fB}");
```

An `Encoding` can be compared with an encoding string from the Objective-C
runtime:

```rust
use objc::Encode;

assert!(&i32::ENCODING == "i");
```

## Generating encoding strings

Every `Encoding` implements `Display` as its string representation.
This can be generated conveniently through the `to_string` method:
`Encoding` implements `Display` as its string representation. This can be
generated conveniently through the `to_string` method:

```rust
use objc::Encode;

assert_eq!(i32::ENCODING.to_string(), "i");
```

See the [`examples`] folder for more complex usage.

# Installation

```toml
[dependencies]
objc-encode = "1.1.0"
```

# License

This project is licensed under the MIT license, see [`../LICENSE.txt`].

Work is in progress to make it dual-licensed under the Apache License
(Version 2.0) as well.

[`examples`]: https://github.com/madsmtm/objc/tree/master/objc_encode/examples
[`../LICENSE.txt`]: https://github.com/madsmtm/objc/blob/master/LICENSE.txt
2 changes: 1 addition & 1 deletion objc_encode/examples/core_graphics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use objc_encode::{Encode, Encoding};
use objc::{Encode, Encoding};

#[cfg(target_pointer_width = "32")]
type CGFloat = f32;
Expand Down
Loading