From 74d2cc77c70d6e2c8f2f6f7e21fd547b35f713ec Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 12 Jan 2023 05:46:28 +0100 Subject: [PATCH 1/6] Support double-colon selectors in sel! macro --- crates/objc2/CHANGELOG.md | 1 + crates/objc2/src/__macro_helpers.rs | 9 ++++ crates/objc2/src/macros.rs | 79 ++++++++++++++++++++++++++-- crates/objc2/src/runtime.rs | 28 ++++++++++ crates/objc2/src/test_utils.rs | 27 ++++++++++ crates/test-ui/ui/invalid_sel.stderr | 15 +++--- 6 files changed, 147 insertions(+), 12 deletions(-) diff --git a/crates/objc2/CHANGELOG.md b/crates/objc2/CHANGELOG.md index 2700ac6e9..f356b80d5 100644 --- a/crates/objc2/CHANGELOG.md +++ b/crates/objc2/CHANGELOG.md @@ -8,6 +8,7 @@ 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 similar to `abc::def:` in the `sel!` macro. ### Changed * **BREAKING**: Using the automatic `NSError**`-to-`Result` functionality in diff --git a/crates/objc2/src/__macro_helpers.rs b/crates/objc2/src/__macro_helpers.rs index 698b88c05..aa1e07e6d 100644 --- a/crates/objc2/src/__macro_helpers.rs +++ b/crates/objc2/src/__macro_helpers.rs @@ -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"; @@ -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 { diff --git a/crates/objc2/src/macros.rs b/crates/objc2/src/macros.rs index 605643f07..8e583af92 100644 --- a/crates/objc2/src/macros.rs +++ b/crates/objc2/src/macros.rs @@ -166,6 +166,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. /// @@ -196,21 +209,77 @@ macro_rules! sel { (new) => ({ $crate::__macro_helpers::new_sel() }); - ($first:ident $(: $($rest:ident :)*)?) => ({ + ($sel:ident) => ({ + $crate::__sel_inner!( + $crate::__sel_data!($sel), + $crate::__hash_idents!($sel) + ) + }); + ($($sel:ident :)*) => ({ $crate::__sel_inner!( - $crate::__sel_data!($first $(: $($rest :)*)?), - $crate::__hash_idents!($first $($($rest)*)?) + $crate::__sel_data!($($sel :)*), + $crate::__hash_idents!($($sel)*) ) }); + ($($sel:tt)*) => { + $crate::__sel_helper! { + @() + @() + $($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)*) + @($($parsed_idents:tt)*) + } => ({ + $crate::__sel_inner!( + $crate::__sel_data!($($parsed_sel)*), + $crate::__hash_idents!($($parsed_idents)*) + ) + }); + // Parse identitifer + colon token + { + @($($parsed_sel:tt)*) + @($($parsed_idents:tt)*) + $($ident:ident)? : $($rest:tt)* + } => { + $crate::__sel_helper! { + @($($parsed_sel)* $($ident)? :) + @($($parsed_idents)* $($ident)?) + $($rest)* + } + }; + // Parse identitifer + path separator token + { + @($($parsed_sel:tt)*) + @($($parsed_idents:tt)*) + $($ident:ident)? :: $($rest:tt)* + } => { + $crate::__sel_helper! { + // Notice space between these + @($($parsed_sel)* $($ident)? : :) + @($($parsed_idents)* $($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', ) }; diff --git a/crates/objc2/src/runtime.rs b/crates/objc2/src/runtime.rs index a7ce40bc9..eec77ac32 100644 --- a/crates/objc2/src/runtime.rs +++ b/crates/objc2/src/runtime.rs @@ -983,6 +983,7 @@ mod tests { use super::*; use crate::test_utils; + use crate::MessageReceiver; use crate::{msg_send, sel}; #[test] @@ -999,6 +1000,14 @@ mod tests { test_sel!("abc:", abc:); test_sel!("abc:def:", abc:def:); test_sel!("abc:def:ghi:", abc:def:ghi:); + test_sel!("functionWithControlPoints::::", functionWithControlPoints::::); + test_sel!("initWithControlPoints::::", initWithControlPoints::::); + test_sel!("setFloatValue::", setFloatValue::); + test_sel!("isSupported::", isSupported::); + test_sel!("addEventListener:::", addEventListener:::); + test_sel!("test::arg::", test::arg::); + test_sel!("test::::with::spaces::", test : :: : with : : spaces : :); + test_sel!("a::b:", a::b:); } #[test] @@ -1007,6 +1016,8 @@ mod tests { assert_eq!(sel.name(), ""); let sel = Sel::register(":"); assert_eq!(sel.name(), ":"); + let sel = Sel::register("::"); + assert_eq!(sel.name(), "::"); } #[test] @@ -1071,6 +1082,7 @@ mod tests { assert!(cls.responds_to(sel!(foo))); assert!(cls.responds_to(sel!(setBar:))); + assert!(cls.responds_to(sel!(test::test::))); assert!(!cls.responds_to(sel!(abc))); assert!(!cls.responds_to(sel!(addNumber:toNumber:))); @@ -1080,6 +1092,7 @@ mod tests { // The metaclass of a root class is a subclass of the root class assert_eq!(metaclass.superclass().unwrap(), cls); assert!(metaclass.responds_to(sel!(addNumber:toNumber:))); + assert!(metaclass.responds_to(sel!(test::test::))); // TODO: This is unexpected! assert!(metaclass.responds_to(sel!(foo))); @@ -1223,4 +1236,19 @@ mod tests { format!("", &*object) ); } + + #[test] + fn test_multiple_colon() { + let class = test_utils::custom_class(); + let res: i32 = unsafe { + MessageReceiver::send_message(class, sel!(test::test::), (1i32, 2i32, 3i32, 4i32)) + }; + assert_eq!(res, 1 + 2 + 3 + 4); + + let obj = test_utils::custom_object(); + let res: i32 = unsafe { + MessageReceiver::send_message(&obj, sel!(test::test::), (1i32, 2i32, 3i32, 4i32)) + }; + assert_eq!(res, 1 * 2 * 3 * 4); + } } diff --git a/crates/objc2/src/test_utils.rs b/crates/objc2/src/test_utils.rs index a05c99719..82ff56fd8 100644 --- a/crates/objc2/src/test_utils.rs +++ b/crates/objc2/src/test_utils.rs @@ -154,6 +154,28 @@ pub(crate) fn custom_class() -> &'static Class { fst + snd } + extern "C" fn custom_obj_multiple_colon( + _obj: &Object, + _cmd: Sel, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + ) -> i32 { + arg1 * arg2 * arg3 * arg4 + } + + extern "C" fn custom_obj_multiple_colon_class( + _cls: &Class, + _cmd: Sel, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + ) -> i32 { + arg1 + arg2 + arg3 + arg4 + } + unsafe { let release: unsafe extern "C" fn(_, _) = custom_obj_release; builder.add_method(sel!(release), release); @@ -174,6 +196,11 @@ pub(crate) fn custom_class() -> &'static Class { let protocol_class_method: extern "C" fn(_, _, _, _) -> _ = custom_obj_add_number_to_number; builder.add_class_method(sel!(addNumber:toNumber:), protocol_class_method); + + let f: extern "C" fn(_, _, _, _, _, _) -> _ = custom_obj_multiple_colon; + builder.add_method(sel!(test::test::), f); + let f: extern "C" fn(_, _, _, _, _, _) -> _ = custom_obj_multiple_colon_class; + builder.add_class_method(sel!(test::test::), f); } builder.register(); diff --git a/crates/test-ui/ui/invalid_sel.stderr b/crates/test-ui/ui/invalid_sel.stderr index 18868b874..e1b710eb1 100644 --- a/crates/test-ui/ui/invalid_sel.stderr +++ b/crates/test-ui/ui/invalid_sel.stderr @@ -4,11 +4,12 @@ error: unexpected end of macro invocation | sel!(); | ^^^^^^ missing tokens in macro arguments | -note: while trying to match `alloc` +note: while trying to match meta-variable `$first:ident` --> $WORKSPACE/crates/objc2/src/macros.rs | - | (alloc) => ({ - | ^^^^^ + | ($first:ident $(: $($($rest:ident)? :)*)?) => { + | ^^^^^^^^^^^^ + = note: this error originates in the macro `sel` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected end of macro invocation --> ui/invalid_sel.rs @@ -19,8 +20,8 @@ error: unexpected end of macro invocation note: while trying to match `:` --> $WORKSPACE/crates/objc2/src/macros.rs | - | ($first:ident $(: $($rest:ident :)*)?) => ({ - | ^ + | $($ident:ident)? : $($rest:tt)* + | ^ error: unexpected end of macro invocation --> ui/invalid_sel.rs @@ -31,5 +32,5 @@ error: unexpected end of macro invocation note: while trying to match `:` --> $WORKSPACE/crates/objc2/src/macros.rs | - | ($first:ident $(: $($rest:ident :)*)?) => ({ - | ^ + | $($ident:ident)? : $($rest:tt)* + | ^ From d5d898e22ce6d22c3318dd47754da1c51f2a28a6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 12 Jan 2023 03:12:43 +0100 Subject: [PATCH 2/6] Slightly optimize __rewrite_self_arg --- crates/objc2/src/macros/__rewrite_self_arg.rs | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/crates/objc2/src/macros/__rewrite_self_arg.rs b/crates/objc2/src/macros/__rewrite_self_arg.rs index dea89a1c3..d91a94424 100644 --- a/crates/objc2/src/macros/__rewrite_self_arg.rs +++ b/crates/objc2/src/macros/__rewrite_self_arg.rs @@ -4,30 +4,37 @@ macro_rules! __rewrite_self_arg { { ($out_macro:path) ($($args:tt)*) + $($macro_args:tt)* } => { - $crate::__rewrite_self_arg! { + $crate::__rewrite_self_arg_inner! { ($out_macro) // Duplicate args out so that we can match on `self`, while still - // use it as a function argument - @($($args)*) - @($($args)*) + // using it as a function argument + ($($args)*) + ($($args)*) + $($macro_args)* } }; +} +#[doc(hidden)] +#[macro_export] +macro_rules! __rewrite_self_arg_inner { // Instance method { ($out_macro:path) - @(&mut self $($__rest_args:tt)*) - @(&mut $self:ident $(, $($rest:tt)*)?) + (&self $($__rest_args:tt)*) + (&$self:ident $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { $($macro_args)* @(instance_method) @( - $self: &mut Self, + $self: &Self, _: $crate::runtime::Sel, ) @($($($rest)*)?) @@ -35,15 +42,16 @@ macro_rules! __rewrite_self_arg { }; { ($out_macro:path) - @(&self $($__rest_args:tt)*) - @(&$self:ident $(, $($rest:tt)*)?) + (&mut self $($__rest_args:tt)*) + (&mut $self:ident $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { $($macro_args)* @(instance_method) @( - $self: &Self, + $self: &mut Self, _: $crate::runtime::Sel, ) @($($($rest)*)?) @@ -51,15 +59,16 @@ macro_rules! __rewrite_self_arg { }; { ($out_macro:path) - @(mut self: $__self_ty:ty $(, $($__rest_args:tt)*)?) - @(mut $self:ident: $self_ty:ty $(, $($rest:tt)*)?) + (self: $__self_ty:ty $(, $($__rest_args:tt)*)?) + ($self:ident: $self_ty:ty $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { $($macro_args)* @(instance_method) @( - mut $self: $self_ty, + $self: $self_ty, _: $crate::runtime::Sel, ) @($($($rest)*)?) @@ -67,15 +76,16 @@ macro_rules! __rewrite_self_arg { }; { ($out_macro:path) - @(self: $__self_ty:ty $(, $($__rest_args:tt)*)?) - @($self:ident: $self_ty:ty $(, $($rest:tt)*)?) + (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) @( - $self: $self_ty, + mut $self: $self_ty, _: $crate::runtime::Sel, ) @($($($rest)*)?) @@ -87,8 +97,9 @@ macro_rules! __rewrite_self_arg { // https://doc.rust-lang.org/nightly/unstable-book/language-features/arbitrary-self-types.html { ($out_macro:path) - @(mut this: $__self_ty:ty $(, $($__rest_args:tt)*)?) - @(mut $this:ident: $this_ty:ty $(, $($rest:tt)*)?) + (mut this: $__self_ty:ty $(, $($__rest_args:tt)*)?) + (mut $this:ident: $this_ty:ty $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { @@ -103,8 +114,9 @@ macro_rules! __rewrite_self_arg { }; { ($out_macro:path) - @(this: $__self_ty:ty $(, $($__rest_args:tt)*)?) - @($this:ident: $this_ty:ty $(, $($rest:tt)*)?) + (this: $__self_ty:ty $(, $($__rest_args:tt)*)?) + ($this:ident: $this_ty:ty $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { @@ -119,8 +131,9 @@ macro_rules! __rewrite_self_arg { }; { ($out_macro:path) - @(mut _this: $__self_ty:ty $(, $($__rest_args:tt)*)?) - @(mut $this:ident: $this_ty:ty $(, $($rest:tt)*)?) + (mut _this: $__self_ty:ty $(, $($__rest_args:tt)*)?) + (mut $this:ident: $this_ty:ty $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { @@ -135,8 +148,9 @@ macro_rules! __rewrite_self_arg { }; { ($out_macro:path) - @(_this: $__self_ty:ty $(, $($__rest_args:tt)*)?) - @($this:ident: $this_ty:ty $(, $($rest:tt)*)?) + (_this: $__self_ty:ty $(, $($__rest_args:tt)*)?) + ($this:ident: $this_ty:ty $(, $($rest:tt)*)?) + $($macro_args:tt)* } => { $out_macro! { @@ -153,8 +167,9 @@ macro_rules! __rewrite_self_arg { // Class method { ($out_macro:path) - @($($__args:tt)*) - @($($args:tt)*) + ($($__args:tt)*) + ($($args:tt)*) + $($macro_args:tt)* } => { $out_macro! { From dac7b63521c3ee41355f4576a3d3389019704d03 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 12 Jan 2023 05:10:28 +0100 Subject: [PATCH 3/6] Improve extern_methods! internals --- crates/objc2/src/macros.rs | 15 +- crates/objc2/src/macros/__method_msg_send.rs | 244 ++++++++++++++++++ crates/objc2/src/macros/extern_methods.rs | 111 ++------ crates/objc2/src/macros/extern_protocol.rs | 32 ++- .../test-ui/ui/extern_methods_bad_selector.rs | 40 +++ .../ui/extern_methods_bad_selector.stderr | 51 ++++ crates/test-ui/ui/extern_methods_variadic.rs | 41 +++ .../test-ui/ui/extern_methods_variadic.stderr | 141 ++++++++++ .../ui/extern_methods_wrong_arguments.stderr | 32 +-- ...xtern_methods_wrong_arguments_error.stderr | 16 +- 10 files changed, 584 insertions(+), 139 deletions(-) create mode 100644 crates/objc2/src/macros/__method_msg_send.rs create mode 100644 crates/test-ui/ui/extern_methods_bad_selector.rs create mode 100644 crates/test-ui/ui/extern_methods_bad_selector.stderr create mode 100644 crates/test-ui/ui/extern_methods_variadic.rs create mode 100644 crates/test-ui/ui/extern_methods_variadic.stderr diff --git a/crates/objc2/src/macros.rs b/crates/objc2/src/macros.rs index 8e583af92..3a820d8d9 100644 --- a/crates/objc2/src/macros.rs +++ b/crates/objc2/src/macros.rs @@ -1,4 +1,5 @@ mod __attribute_helpers; +mod __method_msg_send; mod __msg_send_parse; mod __rewrite_self_arg; mod declare_class; @@ -1154,20 +1155,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) diff --git a/crates/objc2/src/macros/__method_msg_send.rs b/crates/objc2/src/macros/__method_msg_send.rs new file mode 100644 index 000000000..5076019ed --- /dev/null +++ b/crates/objc2/src/macros/__method_msg_send.rs @@ -0,0 +1,244 @@ +/// Forward selector and arguments to `MessageReceiver::send_message[_error]`. +#[doc(hidden)] +#[macro_export] +macro_rules! __method_msg_send { + // Selector with no arguments + ( + ($receiver:expr) + ($sel:ident) + () + + () + () + ) => { + $crate::__msg_send_helper! { + @(send_message) + @($receiver) + @($sel) + @() + } + }; + + // Parse each argument-selector pair + ( + ($receiver:expr) + ($sel:ident : $($sel_rest:tt)*) + ($arg:ident : $_arg_ty:ty $(, $($args_rest:tt)*)?) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ) => { + $crate::__method_msg_send! { + ($receiver) + ($($sel_rest)*) + ($($($args_rest)*)?) + + ($($sel_parsed)* $sel :) + ($($arg_parsed)* $arg,) + } + }; + + // Normal return + ( + ($receiver:expr) + () + () + + // Notice the "+" here; we must make sure we actually _did_ parse + // a selector, and haven't just gotten an empty `#[method()]`. + ($($sel_parsed:tt)+) + ($($arg_parsed:tt)*) + ) => { + $crate::__msg_send_helper! { + @(send_message) + @($receiver) + @($($sel_parsed)*) + @($($arg_parsed)*) + } + }; + + // Error return + ( + ($receiver:expr) + // `sel:_` without a corresponding argument + ($sel:ident : _) + () + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ) => { + $crate::__msg_send_helper! { + // Use error method + @(__send_message_error) + @($receiver) + @($($sel_parsed)* $sel :) + @($($arg_parsed)*) + } + }; + + // Variadic method + ( + ($receiver:expr) + ($($sel:ident : _)?) + ($($arg:ident :)? ...) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ) => ({ + $crate::__macro_helpers::compile_error!( + "variadic methods are not yet supported" + ) + }); + + // Mismatched selector/argument + ( + ($receiver:expr) + ($($sel_rest:tt)*) + ($($args_rest:tt)*) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ) => ({ + $crate::__macro_helpers::compile_error!( + "number of arguments in function and selector did not match" + ) + }); +} + +/// Same as `__method_msg_send`, just for `msg_send_id!`. +#[doc(hidden)] +#[macro_export] +macro_rules! __method_msg_send_id { + // Selector with no arguments + ( + ($receiver:expr) + ($(@__retain_semantics $retain_semantics:ident)? $sel:ident) + () + + () + () + () + ) => { + $crate::__msg_send_id_helper! { + @(send_message_id) + @($receiver) + @($($retain_semantics)?) + @($sel) + @() + } + }; + + // Parse retain semantics + ( + ($receiver:expr) + (@__retain_semantics $retain_semantics:ident $($sel_rest:tt)*) + ($($args_rest:tt)*) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + () + ) => { + $crate::__method_msg_send_id! { + ($receiver) + ($($sel_rest)*) + ($($args_rest)*) + + ($($sel_parsed)*) + ($($arg_parsed)*) + ($retain_semantics) + } + }; + + // Parse each argument-selector pair + ( + ($receiver:expr) + ($sel:ident : $($sel_rest:tt)*) + ($arg:ident : $_arg_ty:ty $(, $($args_rest:tt)*)?) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => { + $crate::__method_msg_send_id! { + ($receiver) + ($($sel_rest)*) + ($($($args_rest)*)?) + + ($($sel_parsed)* $sel :) + ($($arg_parsed)* $arg,) + ($($retain_semantics)?) + } + }; + + // Normal return + ( + ($receiver:expr) + () + () + + // Notice the "+" here; we must make sure we actually _did_ parse + // a selector, and haven't just gotten an empty `#[method()]`. + ($($sel_parsed:tt)+) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => { + $crate::__msg_send_id_helper! { + @(send_message_id) + @($receiver) + @($($retain_semantics)?) + @($($sel_parsed)*) + @($($arg_parsed)*) + } + }; + + // Error return + ( + ($receiver:expr) + // `sel:_` without a corresponding argument + ($sel:ident : _) + () + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => { + $crate::__msg_send_id_helper! { + // Use error method + @(send_message_id_error) + @($receiver) + @($($retain_semantics)?) + @($($sel_parsed)* $sel :) + @($($arg_parsed)*) + } + }; + + // Variadic method + ( + ($receiver:expr) + ($($sel:ident : _)?) + ($($arg:ident :)? ...) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => ({ + $crate::__macro_helpers::compile_error!( + "variadic methods are not yet supported" + ) + }); + + // Mismatched selector/argument + ( + ($receiver:expr) + ($($sel_rest:tt)*) + ($($args_rest:tt)*) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => ({ + $crate::__macro_helpers::compile_error!( + "number of arguments in function and selector did not match" + ) + }); +} diff --git a/crates/objc2/src/macros/extern_methods.rs b/crates/objc2/src/macros/extern_methods.rs index a22d2da8d..0325737b1 100644 --- a/crates/objc2/src/macros/extern_methods.rs +++ b/crates/objc2/src/macros/extern_methods.rs @@ -286,8 +286,10 @@ macro_rules! __extern_methods_method_out { @($(#[$($m)*])*) @($crate::__extern_methods_unsafe_method_body) @( - @($($kind)*) - @($($args_start)*) + @($crate::__extern_methods_get_receiver!( + @($($kind)*) + @($($args_start)*) + )) @($($args_rest)*) // Macro will add: // @(method attribute) @@ -308,46 +310,42 @@ macro_rules! __extern_methods_method_out { macro_rules! __extern_methods_unsafe_method_body { // #[method(...)] { - @($kind:ident) - @($($args_start:tt)*) + @($receiver:expr) @($($args_rest:tt)*) @(#[method($($sel:tt)*)]) @() // No `optional` } => { - $crate::__collect_msg_send! { - $crate::msg_send; - $crate::__extern_methods_get_receiver!( - @($kind) - @($($args_start)*) - ); - ($($sel)*); - ($($args_rest)*); + $crate::__method_msg_send! { + ($receiver) + ($($sel)*) + ($($args_rest)*) + + () + () } }; // #[method_id(...)] { - @($kind:ident) - @($($args_start:tt)*) + @($receiver:expr) @($($args_rest:tt)*) @(#[method_id($($sel:tt)*)]) @() // No `optional` } => { - $crate::__collect_msg_send! { - $crate::msg_send_id; - $crate::__extern_methods_get_receiver!( - @($kind) - @($($args_start)*) - ); - ($($sel)*); - ($($args_rest)*); + $crate::__method_msg_send_id! { + ($receiver) + ($($sel)*) + ($($args_rest)*) + + () + () + () } }; // #[optional] { - @($kind:ident) - @($($args_start:tt)*) + @($receiver:expr) @($($args_rest:tt)*) @($($m_method:tt)*) @($($m_optional:tt)*) @@ -379,68 +377,3 @@ macro_rules! __extern_methods_get_receiver { ::class() }; } - -/// 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; - ($(@__retain_semantics $retain_semantics:ident )? $sel:ident); - (); - ) => {{ - $macro![$obj, $(@__retain_semantics $retain_semantics )? $sel] - }}; - - // Base case - ( - $macro:path; - $obj:expr; - (); - (); - $($output:tt)+ - ) => {{ - $macro![$obj, $($output)+] - }}; - - // Allow trailing `sel:_` without a corresponding argument (for errors) - ( - $macro:path; - $obj:expr; - ($(@__retain_semantics $retain_semantics:ident )? $sel:ident: _); - ($(,)?); - $($output:tt)* - ) => { - $crate::__collect_msg_send! { - $macro; - $obj; - (); - (); - $($output)* $(@__retain_semantics $retain_semantics )? $sel: _, - } - }; - - // tt-munch each argument - ( - $macro:path; - $obj:expr; - ($(@__retain_semantics $retain_semantics:ident )? $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)* $(@__retain_semantics $retain_semantics )? $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!") - }}; -} diff --git a/crates/objc2/src/macros/extern_protocol.rs b/crates/objc2/src/macros/extern_protocol.rs index 78aa7aead..dc67874fd 100644 --- a/crates/objc2/src/macros/extern_protocol.rs +++ b/crates/objc2/src/macros/extern_protocol.rs @@ -361,30 +361,38 @@ macro_rules! __extern_protocol_method_out { #[doc(hidden)] #[macro_export] macro_rules! __extern_protocol_method_body { + // #[method(...)] { - @($($receiver:tt)*) + @($receiver:expr) @($($args_rest:tt)*) @(#[method($($sel:tt)*)]) @($($m_optional:tt)*) } => { - $crate::__collect_msg_send! { - $crate::msg_send; - $($receiver)*; - ($($sel)*); - ($($args_rest)*); + $crate::__method_msg_send! { + ($receiver) + ($($sel)*) + ($($args_rest)*) + + () + () } }; + + // #[method_id(...)] { - @($($receiver:tt)*) + @($receiver:expr) @($($args_rest:tt)*) @(#[method_id($($sel:tt)*)]) @($($m_optional:tt)*) } => { - $crate::__collect_msg_send! { - $crate::msg_send_id; - $($receiver)*; - ($($sel)*); - ($($args_rest)*); + $crate::__method_msg_send_id! { + ($receiver) + ($($sel)*) + ($($args_rest)*) + + () + () + () } }; } diff --git a/crates/test-ui/ui/extern_methods_bad_selector.rs b/crates/test-ui/ui/extern_methods_bad_selector.rs new file mode 100644 index 000000000..43aed3cb2 --- /dev/null +++ b/crates/test-ui/ui/extern_methods_bad_selector.rs @@ -0,0 +1,40 @@ +use objc2::{extern_class, extern_methods, ClassType}; +use objc2::runtime::NSObject; + +extern_class!( + pub struct MyObject; + + unsafe impl ClassType for MyObject { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl MyObject { + #[method()] + fn no_selector_class(); + } +); + +extern_methods!( + unsafe impl MyObject { + #[method_id()] + fn no_selector_id_self(&self); + } +); + +extern_methods!( + unsafe impl MyObject { + #[method(_)] + fn underscore(&self); + } +); + +extern_methods!( + unsafe impl MyObject { + #[method(ab:c)] + fn missing_colon(&self); + } +); + +fn main() {} diff --git a/crates/test-ui/ui/extern_methods_bad_selector.stderr b/crates/test-ui/ui/extern_methods_bad_selector.stderr new file mode 100644 index 000000000..9fcf4bed6 --- /dev/null +++ b/crates/test-ui/ui/extern_methods_bad_selector.stderr @@ -0,0 +1,51 @@ +error: number of arguments in function and selector did not match + --> ui/extern_methods_bad_selector.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method()] + | | fn no_selector_class(); + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: number of arguments in function and selector did not match + --> ui/extern_methods_bad_selector.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method_id()] + | | fn no_selector_id_self(&self); + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send_id` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: number of arguments in function and selector did not match + --> ui/extern_methods_bad_selector.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(_)] + | | fn underscore(&self); + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: number of arguments in function and selector did not match + --> ui/extern_methods_bad_selector.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(ab:c)] + | | fn missing_colon(&self); + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/test-ui/ui/extern_methods_variadic.rs b/crates/test-ui/ui/extern_methods_variadic.rs new file mode 100644 index 000000000..dc41b8630 --- /dev/null +++ b/crates/test-ui/ui/extern_methods_variadic.rs @@ -0,0 +1,41 @@ +use objc2::{extern_class, extern_methods, ClassType}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::NSObject; + +extern_class!( + pub struct MyObject; + + unsafe impl ClassType for MyObject { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl MyObject { + #[method(a:)] + fn variadic_class(arg: i32, arg2: ...); + } +); + +extern_methods!( + unsafe impl MyObject { + #[method(a:)] + fn variadic_instance(&self, arg: i32, ...); + } +); + +extern_methods!( + unsafe impl MyObject { + #[method_id(a:)] + fn variadic_id(arg: i32, arg2: ...) -> Id; + } +); + +extern_methods!( + unsafe impl MyObject { + #[method(a:_)] + fn variadic_error(arg2: ...) -> Result<(), Id>; + } +); + +fn main() {} diff --git a/crates/test-ui/ui/extern_methods_variadic.stderr b/crates/test-ui/ui/extern_methods_variadic.stderr new file mode 100644 index 000000000..e580f0f79 --- /dev/null +++ b/crates/test-ui/ui/extern_methods_variadic.stderr @@ -0,0 +1,141 @@ +error: variadic methods are not yet supported + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(a:)] + | | fn variadic_class(arg: i32, arg2: ...); + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: variadic methods are not yet supported + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(a:)] + | | fn variadic_instance(&self, arg: i32, ...); + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: variadic methods are not yet supported + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method_id(a:)] + | | fn variadic_id(arg: i32, arg2: ...) -> Id; + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send_id` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: variadic methods are not yet supported + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(a:_)] + | | fn variadic_error(arg2: ...) -> Result<(), Id>; + | | } + | | ); + | |_^ + | + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: only foreign or `unsafe extern "C"` functions may be C-variadic + --> ui/extern_methods_variadic.rs + | + | fn variadic_class(arg: i32, arg2: ...); + | ^^^^^^^^^ + +error: only foreign or `unsafe extern "C"` functions may be C-variadic + --> ui/extern_methods_variadic.rs + | + | fn variadic_instance(&self, arg: i32, ...); + | ^^^ + +error: only foreign or `unsafe extern "C"` functions may be C-variadic + --> ui/extern_methods_variadic.rs + | + | fn variadic_id(arg: i32, arg2: ...) -> Id; + | ^^^^^^^^^ + +error: C-variadic function must be declared with at least one named argument + --> ui/extern_methods_variadic.rs + | + | fn variadic_error(arg2: ...) -> Result<(), Id>; + | ^^^^^^^^^ + +error: only foreign or `unsafe extern "C"` functions may be C-variadic + --> ui/extern_methods_variadic.rs + | + | fn variadic_error(arg2: ...) -> Result<(), Id>; + | ^^^^^^^^^ + +error[E0658]: C-variadic functions are unstable + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(a:)] + | | fn variadic_class(arg: i32, arg2: ...); + | | } + | | ); + | |_^ + | + = note: see issue #44930 for more information + = help: add `#![feature(c_variadic)]` to the crate attributes to enable + = note: this error originates in the macro `$crate::__extern_methods_rewrite_methods` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0658]: C-variadic functions are unstable + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(a:)] + | | fn variadic_instance(&self, arg: i32, ...); + | | } + | | ); + | |_^ + | + = note: see issue #44930 for more information + = help: add `#![feature(c_variadic)]` to the crate attributes to enable + = note: this error originates in the macro `$crate::__extern_methods_rewrite_methods` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0658]: C-variadic functions are unstable + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method_id(a:)] + | | fn variadic_id(arg: i32, arg2: ...) -> Id; + | | } + | | ); + | |_^ + | + = note: see issue #44930 for more information + = help: add `#![feature(c_variadic)]` to the crate attributes to enable + = note: this error originates in the macro `$crate::__extern_methods_rewrite_methods` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0658]: C-variadic functions are unstable + --> ui/extern_methods_variadic.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(a:_)] + | | fn variadic_error(arg2: ...) -> Result<(), Id>; + | | } + | | ); + | |_^ + | + = note: see issue #44930 for more information + = help: add `#![feature(c_variadic)]` to the crate attributes to enable + = note: this error originates in the macro `$crate::__extern_methods_rewrite_methods` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/test-ui/ui/extern_methods_wrong_arguments.stderr b/crates/test-ui/ui/extern_methods_wrong_arguments.stderr index 8c0725f15..22b65e1d1 100644 --- a/crates/test-ui/ui/extern_methods_wrong_arguments.stderr +++ b/crates/test-ui/ui/extern_methods_wrong_arguments.stderr @@ -1,4 +1,4 @@ -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -9,9 +9,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -22,9 +22,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -35,9 +35,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -48,9 +48,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -61,9 +61,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -74,9 +74,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -87,9 +87,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( @@ -100,4 +100,4 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/test-ui/ui/extern_methods_wrong_arguments_error.stderr b/crates/test-ui/ui/extern_methods_wrong_arguments_error.stderr index 151e5d77e..859b61580 100644 --- a/crates/test-ui/ui/extern_methods_wrong_arguments_error.stderr +++ b/crates/test-ui/ui/extern_methods_wrong_arguments_error.stderr @@ -1,4 +1,4 @@ -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments_error.rs | | / extern_methods!( @@ -9,9 +9,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments_error.rs | | / extern_methods!( @@ -22,9 +22,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments_error.rs | | / extern_methods!( @@ -35,9 +35,9 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Number of arguments in function and selector did not match! +error: number of arguments in function and selector did not match --> ui/extern_methods_wrong_arguments_error.rs | | / extern_methods!( @@ -48,4 +48,4 @@ error: Number of arguments in function and selector did not match! | | ); | |_^ | - = note: this error originates in the macro `$crate::__collect_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) From 6d18a56e28132c1236d5d9ce5d8fe2c86df2d41a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 12 Jan 2023 07:03:44 +0100 Subject: [PATCH 4/6] Allow multiple-colon selectors in `extern_methods!` --- crates/objc2-proc-macros/CHANGELOG.md | 3 + crates/objc2-proc-macros/src/lib.rs | 28 +++---- crates/objc2/CHANGELOG.md | 3 +- .../objc2/src/declare/declare_class_tests.rs | 75 ++++++++++++++++++- crates/objc2/src/lib.rs | 4 +- crates/objc2/src/macros.rs | 36 ++++----- crates/objc2/src/macros/__method_msg_send.rs | 49 +++++++++++- crates/objc2/src/runtime.rs | 4 +- 8 files changed, 155 insertions(+), 47 deletions(-) diff --git a/crates/objc2-proc-macros/CHANGELOG.md b/crates/objc2-proc-macros/CHANGELOG.md index 51304abaf..a4b0c6f5c 100644 --- a/crates/objc2-proc-macros/CHANGELOG.md +++ b/crates/objc2-proc-macros/CHANGELOG.md @@ -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 diff --git a/crates/objc2-proc-macros/src/lib.rs b/crates/objc2-proc-macros/src/lib.rs index 4ed170f1e..94300be0f 100644 --- a/crates/objc2-proc-macros/src/lib.rs +++ b/crates/objc2-proc-macros/src/lib.rs @@ -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 { 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::>() - .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::>(), + TokenTree::Ident(ident) => { + vec![ident] + } + TokenTree::Punct(_) | TokenTree::Literal(_) => { + vec![] + } } + .into_iter() }) } diff --git a/crates/objc2/CHANGELOG.md b/crates/objc2/CHANGELOG.md index f356b80d5..1bf3d47e0 100644 --- a/crates/objc2/CHANGELOG.md +++ b/crates/objc2/CHANGELOG.md @@ -8,7 +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 similar to `abc::def:` in the `sel!` 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 diff --git a/crates/objc2/src/declare/declare_class_tests.rs b/crates/objc2/src/declare/declare_class_tests.rs index c1af471f9..38e28adb3 100644 --- a/crates/objc2/src/declare/declare_class_tests.rs +++ b/crates/objc2/src/declare/declare_class_tests.rs @@ -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}; @@ -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; + + #[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>; + + #[method_id(test:::withObject:)] + fn test_object( + &self, + arg1: i32, + arg2: i32, + arg3: i32, + obj: *const Self, + ) -> Option>; + } +); + +#[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()); +} diff --git a/crates/objc2/src/lib.rs b/crates/objc2/src/lib.rs index cc891ed3e..0d3f6767c 100644 --- a/crates/objc2/src/lib.rs +++ b/crates/objc2/src/lib.rs @@ -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)] diff --git a/crates/objc2/src/macros.rs b/crates/objc2/src/macros.rs index 3a820d8d9..7561212dc 100644 --- a/crates/objc2/src/macros.rs +++ b/crates/objc2/src/macros.rs @@ -219,15 +219,17 @@ macro_rules! sel { ($($sel:ident :)*) => ({ $crate::__sel_inner!( $crate::__sel_data!($($sel :)*), - $crate::__hash_idents!($($sel)*) + $crate::__hash_idents!($($sel :)*) ) }); ($($sel:tt)*) => { - $crate::__sel_helper! { - @() - @() - $($sel)* - } + $crate::__sel_inner!( + $crate::__sel_helper! { + @() + $($sel)* + }, + $crate::__hash_idents!($($sel)*) + ) }; } @@ -240,35 +242,27 @@ macro_rules! __sel_helper { // Base-case { @($($parsed_sel:tt)*) - @($($parsed_idents:tt)*) } => ({ - $crate::__sel_inner!( - $crate::__sel_data!($($parsed_sel)*), - $crate::__hash_idents!($($parsed_idents)*) - ) + $crate::__sel_data!($($parsed_sel)*) }); // Parse identitifer + colon token { @($($parsed_sel:tt)*) - @($($parsed_idents:tt)*) $($ident:ident)? : $($rest:tt)* } => { $crate::__sel_helper! { @($($parsed_sel)* $($ident)? :) - @($($parsed_idents)* $($ident)?) $($rest)* } }; // Parse identitifer + path separator token { @($($parsed_sel:tt)*) - @($($parsed_idents:tt)*) $($ident:ident)? :: $($rest:tt)* } => { $crate::__sel_helper! { // Notice space between these @($($parsed_sel)* $($ident)? : :) - @($($parsed_idents)* $($ident)?) $($rest)* } }; @@ -1222,12 +1216,12 @@ 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,)*), ) }); @@ -1235,11 +1229,13 @@ macro_rules! __msg_send_id_helper { @($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) @@ -1247,7 +1243,7 @@ macro_rules! __msg_send_id_helper { $obj, $crate::__sel_inner!( __SELECTOR_DATA, - $crate::__hash_idents!($sel_first $($($sel_rest)*)?) + $crate::__hash_idents!($($selector)*) ), ($($argument,)*), ); diff --git a/crates/objc2/src/macros/__method_msg_send.rs b/crates/objc2/src/macros/__method_msg_send.rs index 5076019ed..bef5c8816 100644 --- a/crates/objc2/src/macros/__method_msg_send.rs +++ b/crates/objc2/src/macros/__method_msg_send.rs @@ -1,4 +1,7 @@ /// Forward selector and arguments to `MessageReceiver::send_message[_error]`. +/// +/// Note: We can't forward to `msg_send!` since that doesn't support selectors +/// with space between. #[doc(hidden)] #[macro_export] macro_rules! __method_msg_send { @@ -22,7 +25,7 @@ macro_rules! __method_msg_send { // Parse each argument-selector pair ( ($receiver:expr) - ($sel:ident : $($sel_rest:tt)*) + ($($sel:ident)? : $($sel_rest:tt)*) ($arg:ident : $_arg_ty:ty $(, $($args_rest:tt)*)?) ($($sel_parsed:tt)*) @@ -33,10 +36,28 @@ macro_rules! __method_msg_send { ($($sel_rest)*) ($($($args_rest)*)?) - ($($sel_parsed)* $sel :) + ($($sel_parsed)* $($sel)? :) ($($arg_parsed)* $arg,) } }; + // Handle path separator token + ( + ($receiver:expr) + ($($sel:ident)? :: $($sel_rest:tt)*) + ($arg1:ident : $_arg_ty1:ty, $arg2:ident : $_arg_ty2:ty $(, $($args_rest:tt)*)?) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ) => { + $crate::__method_msg_send! { + ($receiver) + ($($sel_rest)*) + ($($($args_rest)*)?) + + ($($sel_parsed)* $($sel)? : :) + ($($arg_parsed)* $arg1, $arg2,) + } + }; // Normal return ( @@ -152,7 +173,7 @@ macro_rules! __method_msg_send_id { // Parse each argument-selector pair ( ($receiver:expr) - ($sel:ident : $($sel_rest:tt)*) + ($($sel:ident)? : $($sel_rest:tt)*) ($arg:ident : $_arg_ty:ty $(, $($args_rest:tt)*)?) ($($sel_parsed:tt)*) @@ -164,11 +185,31 @@ macro_rules! __method_msg_send_id { ($($sel_rest)*) ($($($args_rest)*)?) - ($($sel_parsed)* $sel :) + ($($sel_parsed)* $($sel)? :) ($($arg_parsed)* $arg,) ($($retain_semantics)?) } }; + // Handle path separator token + ( + ($receiver:expr) + ($($sel:ident)? :: $($sel_rest:tt)*) + ($arg1:ident : $_arg_ty1:ty, $arg2:ident : $_arg_ty2:ty $(, $($args_rest:tt)*)?) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => { + $crate::__method_msg_send_id! { + ($receiver) + ($($sel_rest)*) + ($($($args_rest)*)?) + + ($($sel_parsed)* $($sel)? : :) + ($($arg_parsed)* $arg1, $arg2,) + ($($retain_semantics)?) + } + }; // Normal return ( diff --git a/crates/objc2/src/runtime.rs b/crates/objc2/src/runtime.rs index eec77ac32..3824522db 100644 --- a/crates/objc2/src/runtime.rs +++ b/crates/objc2/src/runtime.rs @@ -1243,12 +1243,12 @@ mod tests { let res: i32 = unsafe { MessageReceiver::send_message(class, sel!(test::test::), (1i32, 2i32, 3i32, 4i32)) }; - assert_eq!(res, 1 + 2 + 3 + 4); + assert_eq!(res, 10); let obj = test_utils::custom_object(); let res: i32 = unsafe { MessageReceiver::send_message(&obj, sel!(test::test::), (1i32, 2i32, 3i32, 4i32)) }; - assert_eq!(res, 1 * 2 * 3 * 4); + assert_eq!(res, 24); } } From 9e24ccb184b0c310782fc691926bf70d1fcc75b4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 12 Jan 2023 06:41:16 +0100 Subject: [PATCH 5/6] Check selector argument count when verifying method --- crates/objc2/src/declare.rs | 10 +++------- crates/objc2/src/runtime.rs | 8 ++++++++ crates/objc2/src/verify.rs | 5 +++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/objc2/src/declare.rs b/crates/objc2/src/declare.rs index fce196b8e..7b22fca74 100644 --- a/crates/objc2/src/declare.rs +++ b/crates/objc2/src/declare.rs @@ -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); @@ -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(), @@ -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(), @@ -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(), diff --git a/crates/objc2/src/runtime.rs b/crates/objc2/src/runtime.rs index 3824522db..4bcdb1522 100644 --- a/crates/objc2/src/runtime.rs +++ b/crates/objc2/src/runtime.rs @@ -197,6 +197,14 @@ impl Sel { let name = unsafe { CStr::from_ptr(ptr) }; str::from_utf8(name.to_bytes()).unwrap() } + + pub(crate) fn number_of_arguments(self) -> usize { + self.name() + .as_bytes() + .iter() + .filter(|&&b| b == b':') + .count() + } } // `ffi::sel_isEqual` is just pointer comparison on Apple (the documentation diff --git a/crates/objc2/src/verify.rs b/crates/objc2/src/verify.rs index ca85bd638..966cb78a1 100644 --- a/crates/objc2/src/verify.rs +++ b/crates/objc2/src/verify.rs @@ -106,6 +106,11 @@ pub(crate) fn verify_method_signature( return Err(Inner::MismatchedArgumentsCount(actual_count + remaining, actual_count).into()); } + let expected_count = method.name().number_of_arguments(); + if expected_count != actual_count { + return Err(Inner::MismatchedArgumentsCount(expected_count, actual_count).into()); + } + Ok(()) } From 9cc628897edd4d36e9910b1fdd4ce13e35723cb4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 12 Jan 2023 06:14:46 +0100 Subject: [PATCH 6/6] icrate: Include methods with selectors with multiple colons --- crates/header-translator/translation-config.toml | 5 ----- crates/icrate/src/generated | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/header-translator/translation-config.toml b/crates/header-translator/translation-config.toml index 85e49d2e2..a9df2ecb6 100644 --- a/crates/header-translator/translation-config.toml +++ b/crates/header-translator/translation-config.toml @@ -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 diff --git a/crates/icrate/src/generated b/crates/icrate/src/generated index e59158acd..48fb96a2d 160000 --- a/crates/icrate/src/generated +++ b/crates/icrate/src/generated @@ -1 +1 @@ -Subproject commit e59158acdd64687438597dc1a81267db09cfcc07 +Subproject commit 48fb96a2dc408d17f2a298520c132516004398e2