Skip to content

Commit

Permalink
Merge pull request #336 from madsmtm/multiple-colons-in-selectors
Browse files Browse the repository at this point in the history
Allow multiple colons in selectors
  • Loading branch information
madsmtm authored Jan 13, 2023
2 parents e08964a + 9cc6288 commit 09d117d
Show file tree
Hide file tree
Showing 24 changed files with 922 additions and 215 deletions.
5 changes: 0 additions & 5 deletions crates/header-translator/translation-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -774,11 +774,6 @@ skipped = true
[class.NSMutableData.methods.mutableBytes]
skipped = true

# Selector contains multiple colons after each other, like `::::`
[class.CAMediaTimingFunction.methods]
functionWithControlPoints = { skipped = true }
initWithControlPoints = { skipped = true }

# Uses __autoreleasing in a typedef, which I'm unsure how to handle
[typedef.MTLAutoreleasedArgument]
skipped = true
Expand Down
2 changes: 1 addition & 1 deletion crates/icrate/src/generated
3 changes: 3 additions & 0 deletions crates/objc2-proc-macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased - YYYY-MM-DD

### Fixed
* Allow all types of tokens in internal macro.


## 0.1.0 - 2022-07-19

Expand Down
28 changes: 10 additions & 18 deletions crates/objc2-proc-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,19 @@ use proc_macro::Literal;
use proc_macro::TokenStream;
use proc_macro::TokenTree;

/// Quick n' dirty way to extract the idents given by the `sel!` macro.
/// Extract all identifiers in the given tokenstream.
fn get_idents(input: TokenStream) -> impl Iterator<Item = Ident> {
input.into_iter().flat_map(|token| {
if let TokenTree::Group(group) = token {
group
.stream()
.into_iter()
.map(|token| {
if let TokenTree::Ident(ident) = token {
ident
} else {
panic!("Expected ident, got {token:?}")
}
})
.collect::<Vec<_>>()
.into_iter()
} else if let TokenTree::Ident(ident) = token {
vec![ident].into_iter()
} else {
panic!("Expected group or ident, got {token:?}")
match token {
TokenTree::Group(group) => get_idents(group.stream()).collect::<Vec<_>>(),
TokenTree::Ident(ident) => {
vec![ident]
}
TokenTree::Punct(_) | TokenTree::Literal(_) => {
vec![]
}
}
.into_iter()
})
}

Expand Down
2 changes: 2 additions & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added
* Support `#[cfg(...)]` attributes in `extern_class!` macro.
* Added support for selectors with multiple colons like `abc::` in the `sel!`,
`extern_class!`, `extern_protocol!` and `declare_class!` macros.

### Changed
* **BREAKING**: Using the automatic `NSError**`-to-`Result` functionality in
Expand Down
9 changes: 9 additions & 0 deletions crates/objc2/src/__macro_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,12 +860,14 @@ mod tests {

#[test]
fn test_in_selector_family() {
#[track_caller]
fn assert_in_family(selector: &str, family: &str) {
assert!(in_selector_family(selector.as_bytes(), family.as_bytes()));
let selector = selector.to_string() + "\0";
assert!(in_selector_family(selector.as_bytes(), family.as_bytes()));
}

#[track_caller]
fn assert_not_in_family(selector: &str, family: &str) {
assert!(!in_selector_family(selector.as_bytes(), family.as_bytes()));
let selector = selector.to_string() + "\0";
Expand Down Expand Up @@ -946,6 +948,13 @@ mod tests {
assert_not_in_family("a", "");
assert_in_family("_A", "");
assert_in_family("A", "");

// Double-colon selectors
assert_in_family("abc::abc::", "abc");
assert_in_family("abc:::", "abc");
assert_in_family("abcDef::xyz:", "abc");
// Invalid selector (probably)
assert_not_in_family("::abc:", "abc");
}

mod test_trait_disambugated {
Expand Down
10 changes: 3 additions & 7 deletions crates/objc2/src/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,6 @@ 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);
Expand Down Expand Up @@ -360,7 +356,7 @@ impl ClassBuilder {
let enc_args = F::Args::ENCODINGS;
let enc_ret = F::Ret::ENCODING;

let sel_args = count_args(sel);
let sel_args = sel.number_of_arguments();
assert_eq!(
sel_args,
enc_args.len(),
Expand Down Expand Up @@ -417,7 +413,7 @@ impl ClassBuilder {
let enc_args = F::Args::ENCODINGS;
let enc_ret = F::Ret::ENCODING;

let sel_args = count_args(sel);
let sel_args = sel.number_of_arguments();
assert_eq!(
sel_args,
enc_args.len(),
Expand Down Expand Up @@ -575,7 +571,7 @@ impl ProtocolBuilder {
Ret: Encode,
{
let encs = Args::ENCODINGS;
let sel_args = count_args(sel);
let sel_args = sel.number_of_arguments();
assert_eq!(
sel_args,
encs.len(),
Expand Down
75 changes: 74 additions & 1 deletion crates/objc2/src/declare/declare_class_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![deny(deprecated)]
use crate::rc::{Id, Owned};
use core::ptr;

use crate::rc::{Id, Owned, Shared};
use crate::runtime::NSObject;
use crate::{declare_class, extern_methods, sel, ClassType};

Expand Down Expand Up @@ -173,3 +175,74 @@ fn test_method_that_is_never_available() {
assert!(!cls.responds_to(sel!(never)));
assert!(!metacls.responds_to(sel!(never)));
}

declare_class!(
struct TestMultipleColonSelector;

unsafe impl ClassType for TestMultipleColonSelector {
type Super = NSObject;
}

unsafe impl TestMultipleColonSelector {
#[method(test::arg3:)]
fn _test_class(arg1: i32, arg2: i32, arg3: i32) -> i32 {
arg1 + arg2 + arg3
}

#[method(test::arg3:)]
fn _test_instance(&self, arg1: i32, arg2: i32, arg3: i32) -> i32 {
arg1 * arg2 * arg3
}

#[method(test::error:)]
fn _test_error(&self, _arg1: i32, _arg2: i32, _arg3: *mut *mut NSObject) -> bool {
true
}

#[method(test:::withObject:)]
fn _test_object(
&self,
_arg1: i32,
_arg2: i32,
_arg3: i32,
_obj: *const Self,
) -> *const Self {
ptr::null()
}
}
);

extern_methods!(
unsafe impl TestMultipleColonSelector {
#[method_id(new)]
fn new() -> Id<Self, Owned>;

#[method(test::arg3:)]
fn test_class(arg1: i32, arg2: i32, arg3: i32) -> i32;

#[method(test::arg3:)]
fn test_instance(&self, arg1: i32, arg2: i32, arg3: i32) -> i32;

#[method(test::error:_)]
fn test_error(&self, arg1: i32, arg2: i32) -> Result<(), Id<NSObject, Shared>>;

#[method_id(test:::withObject:)]
fn test_object(
&self,
arg1: i32,
arg2: i32,
arg3: i32,
obj: *const Self,
) -> Option<Id<Self, Shared>>;
}
);

#[test]
fn test_multiple_colon_selector() {
assert_eq!(TestMultipleColonSelector::test_class(2, 3, 4), 9);

let obj = TestMultipleColonSelector::new();
assert_eq!(obj.test_instance(1, 2, 3), 6);
assert!(obj.test_error(1, 2).is_ok());
assert!(obj.test_object(1, 2, 3, ptr::null()).is_none());
}
4 changes: 3 additions & 1 deletion crates/objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ pub use objc2_proc_macros::__hash_idents;
#[macro_export]
macro_rules! __hash_idents {
// Noop; used to make our other macros a bit easier to read
($($x:tt)*) => {$($x)*};
($($x:tt)*) => {
()
};
}

#[doc(hidden)]
Expand Down
100 changes: 76 additions & 24 deletions crates/objc2/src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod __attribute_helpers;
mod __method_msg_send;
mod __msg_send_parse;
mod __rewrite_self_arg;
mod declare_class;
Expand Down Expand Up @@ -166,6 +167,19 @@ macro_rules! __class_inner {
/// let sel = sel!(aSelector:withoutTrailingColon);
/// ```
///
/// A selector with internal colons:
///
/// ```
/// # use objc2::sel;
/// let sel = sel!(sel::with:::multiple:internal::::colons:::);
///
/// // Yes, that is possible! The following Objective-C would work:
/// //
/// // @interface MyThing: NSObject
/// // + (void)test:(int)a :(int)b arg:(int)c :(int)d;
/// // @end
/// ```
///
/// Unsupported usage that you may run into when using macros - fails to
/// compile when the `"unstable-static-sel"` feature is enabled.
///
Expand Down Expand Up @@ -196,21 +210,71 @@ macro_rules! sel {
(new) => ({
$crate::__macro_helpers::new_sel()
});
($first:ident $(: $($rest:ident :)*)?) => ({
($sel:ident) => ({
$crate::__sel_inner!(
$crate::__sel_data!($first $(: $($rest :)*)?),
$crate::__hash_idents!($first $($($rest)*)?)
$crate::__sel_data!($sel),
$crate::__hash_idents!($sel)
)
});
($($sel:ident :)*) => ({
$crate::__sel_inner!(
$crate::__sel_data!($($sel :)*),
$crate::__hash_idents!($($sel :)*)
)
});
($($sel:tt)*) => {
$crate::__sel_inner!(
$crate::__sel_helper! {
@()
$($sel)*
},
$crate::__hash_idents!($($sel)*)
)
};
}

/// Handle selectors with internal colons.
///
/// Required since `::` is a different token than `:`.
#[doc(hidden)]
#[macro_export]
macro_rules! __sel_helper {
// Base-case
{
@($($parsed_sel:tt)*)
} => ({
$crate::__sel_data!($($parsed_sel)*)
});
// Parse identitifer + colon token
{
@($($parsed_sel:tt)*)
$($ident:ident)? : $($rest:tt)*
} => {
$crate::__sel_helper! {
@($($parsed_sel)* $($ident)? :)
$($rest)*
}
};
// Parse identitifer + path separator token
{
@($($parsed_sel:tt)*)
$($ident:ident)? :: $($rest:tt)*
} => {
$crate::__sel_helper! {
// Notice space between these
@($($parsed_sel)* $($ident)? : :)
$($rest)*
}
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! __sel_data {
($first:ident $(: $($rest:ident :)*)?) => {
($first:ident $(: $($($rest:ident)? :)*)?) => {
$crate::__macro_helpers::concat!(
$crate::__macro_helpers::stringify!($first),
$(':', $($crate::__macro_helpers::stringify!($rest), ':',)*)?
$(':', $($($crate::__macro_helpers::stringify!($rest),)? ':',)*)?
'\0',
)
};
Expand Down Expand Up @@ -1085,20 +1149,6 @@ macro_rules! msg_send_id {
result = <$crate::__macro_helpers::Init as $crate::__macro_helpers::MsgSendId<_, _>>::send_message_id($obj, sel, ());
result
});
[$obj:expr, @__retain_semantics $retain_semantics:ident $($selector_and_arguments:tt)+] => {
$crate::__msg_send_parse! {
($crate::__msg_send_id_helper)
@(send_message_id_error)
@()
@()
@($($selector_and_arguments)+)
@(send_message_id)

@($obj)
@($retain_semantics)
}
// compile_error!(stringify!($($selector_and_arguments)*))
};
[$obj:expr, $($selector_and_arguments:tt)+] => {
$crate::__msg_send_parse! {
($crate::__msg_send_id_helper)
Expand Down Expand Up @@ -1166,32 +1216,34 @@ macro_rules! __msg_send_id_helper {
@($fn:ident)
@($obj:expr)
@($retain_semantics:ident)
@($sel_first:ident $(: $($sel_rest:ident :)*)?)
@($($selector:tt)*)
@($($argument:expr,)*)
} => ({
<$crate::__macro_helpers::$retain_semantics as $crate::__macro_helpers::MsgSendId<_, _>>::$fn::<_, _>(
$obj,
$crate::sel!($sel_first $(: $($sel_rest :)*)?),
$crate::sel!($($selector)*),
($($argument,)*),
)
});
{
@($fn:ident)
@($obj:expr)
@()
@($sel_first:ident $(: $($sel_rest:ident :)*)?)
@($($selector:tt)*)
@($($argument:expr,)*)
} => ({
// Don't use `sel!`, otherwise we'd end up with defining this data twice.
const __SELECTOR_DATA: &$crate::__macro_helpers::str = $crate::__sel_data!($sel_first $(: $($sel_rest :)*)?);
const __SELECTOR_DATA: &$crate::__macro_helpers::str = $crate::__sel_data!(
$($selector)*
);
let result;
result = <$crate::__macro_helpers::RetainSemantics<{
$crate::__macro_helpers::retain_semantics(__SELECTOR_DATA)
}> as $crate::__macro_helpers::MsgSendId<_, _>>::$fn::<_, _>(
$obj,
$crate::__sel_inner!(
__SELECTOR_DATA,
$crate::__hash_idents!($sel_first $($($sel_rest)*)?)
$crate::__hash_idents!($($selector)*)
),
($($argument,)*),
);
Expand Down
Loading

0 comments on commit 09d117d

Please sign in to comment.