Skip to content

Commit

Permalink
Merge pull request #14 from madsmtm/encode-bounds
Browse files Browse the repository at this point in the history
Add `RefEncode`, and make `Encode` bounds required
  • Loading branch information
madsmtm authored Sep 1, 2021
2 parents 66c7ddf + 1600f6e commit d857e90
Show file tree
Hide file tree
Showing 17 changed files with 602 additions and 301 deletions.
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

0 comments on commit d857e90

Please sign in to comment.