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

Allow multiple colons in selectors #336

Merged
merged 6 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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