From 352038cb58469f41df7f5f8aabbb49275ad6bf41 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 27 Oct 2022 16:21:37 +0200 Subject: [PATCH] Add ability to nicely handle NSError** parameters --- objc2/CHANGELOG.md | 8 +- objc2/src/__macro_helpers.rs | 202 ++++++++---- objc2/src/macros.rs | 231 ++++++++++---- objc2/src/macros/__msg_send_parse.rs | 100 ++++++ objc2/src/macros/extern_methods.rs | 41 ++- objc2/src/message/mod.rs | 104 +++++- objc2/src/rc/id.rs | 2 +- objc2/src/rc/test_object.rs | 297 +++++++++++++++++- objc2/tests/use_macros.rs | 2 - test-ui/ui/extern_methods_wrong_arguments.rs | 7 + .../ui/extern_methods_wrong_arguments.stderr | 60 +++- test-ui/ui/invalid_msg_send.rs | 4 + test-ui/ui/invalid_msg_send.stderr | 30 +- test-ui/ui/msg_send_id_invalid_return.stderr | 6 +- test-ui/ui/msg_send_id_underspecified.stderr | 2 +- test-ui/ui/msg_send_invalid_error.rs | 21 ++ test-ui/ui/msg_send_invalid_error.stderr | 97 ++++++ test-ui/ui/msg_send_mutable.stderr | 8 +- test-ui/ui/msg_send_no_return_type.stderr | 2 +- test-ui/ui/msg_send_not_encode.stderr | 4 +- test-ui/ui/msg_send_underspecified_error.rs | 13 + .../ui/msg_send_underspecified_error.stderr | 84 +++++ test-ui/ui/msg_send_underspecified_error2.rs | 9 + .../ui/msg_send_underspecified_error2.stderr | 12 + 24 files changed, 1172 insertions(+), 174 deletions(-) create mode 100644 objc2/src/macros/__msg_send_parse.rs create mode 100644 test-ui/ui/msg_send_invalid_error.rs create mode 100644 test-ui/ui/msg_send_invalid_error.stderr create mode 100644 test-ui/ui/msg_send_underspecified_error.rs create mode 100644 test-ui/ui/msg_send_underspecified_error.stderr create mode 100644 test-ui/ui/msg_send_underspecified_error2.rs create mode 100644 test-ui/ui/msg_send_underspecified_error2.stderr diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index eb3cc22b9..d3f02903e 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -23,7 +23,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). msg_send_id![NSObject::alloc(), init] }; ``` -* Add `Class::class_method`. +* Added `Class::class_method`. +* Added the ability to specify `error: _`, `somethingReturningError: _` and + so on at the end of `msg_send!`/`msg_send_id!`, and have it automatically + return a `Result<..., Id>`. +* Added the ability to specify an extra parameter at the end of the selector + in methods declared with `extern_methods!`, and let that be the `NSError**` + parameter. ### Changed * Allow other types than `&Class` as the receiver in `msg_send_id!` methods diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 3de840cb4..3de982292 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -1,12 +1,15 @@ #[cfg(feature = "verify_message")] use alloc::vec::Vec; +use core::ptr; #[cfg(feature = "verify_message")] use std::collections::HashSet; use crate::declare::ClassBuilder; #[cfg(feature = "verify_message")] use crate::declare::MethodImplementation; -use crate::rc::{Allocated, Id, Ownership}; +use crate::encode::Encode; +use crate::message::__TupleExtender; +use crate::rc::{Allocated, Id, Ownership, Shared}; #[cfg(feature = "verify_message")] use crate::runtime::MethodDescription; use crate::runtime::{Class, Object, Protocol, Sel}; @@ -103,6 +106,56 @@ pub trait MsgSendId { sel: Sel, args: A, ) -> R; + + /// Add an extra error argument to the argument list, call + /// `send_message_id` with that, and return an error if one occurred. + #[inline] + #[track_caller] + unsafe fn send_message_id_error(obj: T, sel: Sel, args: A) -> Result> + where + *mut *mut E: Encode, + A: __TupleExtender<*mut *mut E>, + >::PlusOneArgument: MessageArguments, + E: Message, + Option: MaybeUnwrap, + { + let mut err: *mut E = ptr::null_mut(); + let args = args.add_argument(&mut err); + let res: Option = unsafe { Self::send_message_id(obj, sel, args) }; + // As per the Cocoa documentation: + // > Success or failure is indicated by the return value of the + // > method. Although Cocoa methods that indirectly return error + // > objects in the Cocoa error domain are guaranteed to return such + // > objects if the method indicates failure by directly returning + // > `nil` or `NO`, you should always check that the return value is + // > `nil` or `NO` before attempting to do anything with the `NSError` + // > object. + if let Some(res) = res { + // In this case, the error is likely not created. If it is, it is + // autoreleased anyhow, so it would be a waste to retain and + // release it. + Ok(res) + } else { + // In this case, the error has very likely been created, but has + // been autoreleased (as is common for "out parameters"). Hence we + // need to retain it if we want it to live across autorelease + // pools. + // + // SAFETY: The closure `f` is guaranteed to populate the error + // object, or leave it as NULL. The error is shared, and all + // holders of the error know this, so is safe to retain. + Err(unsafe { encountered_error(err) }) + } + } +} + +// Marked `cold` to tell the optimizer that errors are comparatively rare. +// And intentionally not inlined, for much the same reason. +#[cold] +#[track_caller] +unsafe fn encountered_error(err: *mut E) -> Id { + // SAFETY: Ensured by caller + unsafe { Id::retain(err) }.expect("error parameter should be set if the method returns NULL") } impl MsgSendId> for New { @@ -555,6 +608,7 @@ impl Drop for ClassProtocolMethodsBuilder<'_, '_> { mod tests { use super::*; + use alloc::string::ToString; use alloc::vec; use core::ptr; @@ -777,80 +831,92 @@ mod tests { #[test] fn test_in_selector_family() { + 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())); + } + + 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"; + assert!(!in_selector_family(selector.as_bytes(), family.as_bytes())); + } + // Common cases - assert!(in_selector_family(b"alloc", b"alloc")); - assert!(in_selector_family(b"allocWithZone:", b"alloc")); - assert!(!in_selector_family(b"dealloc", b"alloc")); - assert!(!in_selector_family(b"initialize", b"init")); - assert!(!in_selector_family(b"decimalNumberWithDecimal:", b"init")); - assert!(in_selector_family(b"initWithCapacity:", b"init")); - assert!(in_selector_family(b"_initButPrivate:withParam:", b"init")); - assert!(!in_selector_family(b"description", b"init")); - assert!(!in_selector_family(b"inIT", b"init")); - - assert!(!in_selector_family(b"init", b"copy")); - assert!(!in_selector_family(b"copyingStuff:", b"copy")); - assert!(in_selector_family(b"copyWithZone:", b"copy")); - assert!(!in_selector_family(b"initWithArray:copyItems:", b"copy")); - assert!(in_selector_family(b"copyItemAtURL:toURL:error:", b"copy")); - - assert!(!in_selector_family(b"mutableCopying", b"mutableCopy")); - assert!(in_selector_family(b"mutableCopyWithZone:", b"mutableCopy")); - assert!(in_selector_family(b"mutableCopyWithZone:", b"mutableCopy")); - - assert!(in_selector_family( - b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", - b"new" - )); - assert!(in_selector_family( - b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", - b"new" - )); - assert!(!in_selector_family(b"newsstandAssetDownload", b"new")); + assert_in_family("alloc", "alloc"); + assert_in_family("allocWithZone:", "alloc"); + assert_not_in_family("dealloc", "alloc"); + assert_not_in_family("initialize", "init"); + assert_not_in_family("decimalNumberWithDecimal:", "init"); + assert_in_family("initWithCapacity:", "init"); + assert_in_family("_initButPrivate:withParam:", "init"); + assert_not_in_family("description", "init"); + assert_not_in_family("inIT", "init"); + + assert_not_in_family("init", "copy"); + assert_not_in_family("copyingStuff:", "copy"); + assert_in_family("copyWithZone:", "copy"); + assert_not_in_family("initWithArray:copyItems:", "copy"); + assert_in_family("copyItemAtURL:toURL:error:", "copy"); + + assert_not_in_family("mutableCopying", "mutableCopy"); + assert_in_family("mutableCopyWithZone:", "mutableCopy"); + assert_in_family("mutableCopyWithZone:", "mutableCopy"); + + assert_in_family( + "newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", + "new", + ); + assert_in_family( + "newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", + "new", + ); + assert_not_in_family("newsstandAssetDownload", "new"); // Trying to weed out edge-cases: - assert!(in_selector_family(b"__abcDef", b"abc")); - assert!(in_selector_family(b"_abcDef", b"abc")); - assert!(in_selector_family(b"abcDef", b"abc")); - assert!(in_selector_family(b"___a", b"a")); - assert!(in_selector_family(b"__a", b"a")); - assert!(in_selector_family(b"_a", b"a")); - assert!(in_selector_family(b"a", b"a")); - - assert!(!in_selector_family(b"_abcdef", b"abc")); - assert!(!in_selector_family(b"_abcdef", b"def")); - assert!(!in_selector_family(b"_bcdef", b"abc")); - assert!(!in_selector_family(b"a_bc", b"abc")); - assert!(!in_selector_family(b"abcdef", b"abc")); - assert!(!in_selector_family(b"abcdef", b"def")); - assert!(!in_selector_family(b"abcdef", b"abb")); - assert!(!in_selector_family(b"___", b"a")); - assert!(!in_selector_family(b"_", b"a")); - assert!(!in_selector_family(b"", b"a")); - - assert!(in_selector_family(b"copy", b"copy")); - assert!(in_selector_family(b"copy:", b"copy")); - assert!(in_selector_family(b"copyMe", b"copy")); - assert!(in_selector_family(b"_copy", b"copy")); - assert!(in_selector_family(b"_copy:", b"copy")); - assert!(in_selector_family(b"_copyMe", b"copy")); - assert!(!in_selector_family(b"copying", b"copy")); - assert!(!in_selector_family(b"copying:", b"copy")); - assert!(!in_selector_family(b"_copying", b"copy")); - assert!(!in_selector_family(b"Copy", b"copy")); - assert!(!in_selector_family(b"COPY", b"copy")); + assert_in_family("__abcDef", "abc"); + assert_in_family("_abcDef", "abc"); + assert_in_family("abcDef", "abc"); + assert_in_family("___a", "a"); + assert_in_family("__a", "a"); + assert_in_family("_a", "a"); + assert_in_family("a", "a"); + + assert_not_in_family("_abcdef", "abc"); + assert_not_in_family("_abcdef", "def"); + assert_not_in_family("_bcdef", "abc"); + assert_not_in_family("a_bc", "abc"); + assert_not_in_family("abcdef", "abc"); + assert_not_in_family("abcdef", "def"); + assert_not_in_family("abcdef", "abb"); + assert_not_in_family("___", "a"); + assert_not_in_family("_", "a"); + assert_not_in_family("", "a"); + + assert_in_family("copy", "copy"); + assert_in_family("copy:", "copy"); + assert_in_family("copyMe", "copy"); + assert_in_family("_copy", "copy"); + assert_in_family("_copy:", "copy"); + assert_in_family("_copyMe", "copy"); + assert_not_in_family("copying", "copy"); + assert_not_in_family("copying:", "copy"); + assert_not_in_family("_copying", "copy"); + assert_not_in_family("Copy", "copy"); + assert_not_in_family("COPY", "copy"); // Empty family (not supported) - assert!(in_selector_family(b"___", b"")); - assert!(in_selector_family(b"__", b"")); - assert!(in_selector_family(b"_", b"")); - assert!(in_selector_family(b"", b"")); - assert!(!in_selector_family(b"_a", b"")); - assert!(!in_selector_family(b"a", b"")); - assert!(in_selector_family(b"_A", b"")); - assert!(in_selector_family(b"A", b"")); + assert_in_family("___", ""); + assert_in_family("__", ""); + assert_in_family("_", ""); + assert_in_family("", ""); + assert_not_in_family("_a", ""); + assert_not_in_family("a", ""); + assert_in_family("_A", ""); + assert_in_family("A", ""); } mod test_trait_disambugated { diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 368efb0a8..b01ff1963 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -1,3 +1,4 @@ +mod __msg_send_parse; mod __rewrite_self_arg; mod declare_class; mod extern_class; @@ -641,6 +642,9 @@ macro_rules! __class_inner { /// /// All arguments, and the return type, must implement [`Encode`]. /// +/// If the last argument is the special marker `_`, the macro will return a +/// `Result<(), Id>`, see below. +/// /// This macro translates into a call to [`sel!`], and afterwards a fully /// qualified call to [`MessageReceiver::send_message`]. Note that this means /// that auto-dereferencing of the receiver is not supported, and that the @@ -675,6 +679,33 @@ macro_rules! __class_inner { /// [`runtime::Bool`]: crate::runtime::Bool /// /// +/// # Errors +/// +/// Many methods take an `NSError**` as their last parameter, which is used to +/// communicate errors to the caller, see [Error Handling Programming Guide +/// For Cocoa][cocoa-error]. +/// +/// Similar to Swift's [importing of error parameters][swift-error], this +/// macro supports transforming methods whose last parameter is `NSError**` +/// and returns `BOOL`, into the Rust equivalent, the [`Result`] type. +/// +/// In particular, you can make the last argument the special marker `_`, and +/// then the macro will return a `Result<(), Id>` (where you must +/// specify `E` yourself, usually you'd use [`foundation::NSError`]). +/// +/// At runtime, a temporary error variable is created on the stack and sent as +/// the last parameter. If the message send then returns `NO`/`false` (or in +/// the case of `msg_send_id!`, `NULL`), the error variable is loaded and +/// returned in [`Err`]. +/// +/// Do beware that this is only valid on methods that return `BOOL`, see +/// [`msg_send_id!`] for methods that return instance types. +/// +/// [cocoa-error]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorHandling/ErrorHandling.html +/// [swift-error]: https://developer.apple.com/documentation/swift/about-imported-cocoa-error-parameters +/// [`foundation::NSError`]: crate::foundation::NSError +/// +/// /// # Panics /// /// Panics if the `"catch-all"` feature is enabled and the Objective-C method @@ -685,6 +716,9 @@ macro_rules! __class_inner { /// method's argument's encoding does not match the encoding of the given /// arguments. This is highly recommended to enable while testing! /// +/// And panics if the `NSError**` handling functionality described above is +/// used, and the error object was unexpectedly `NULL`. +/// /// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html /// /// @@ -770,45 +804,79 @@ macro_rules! __class_inner { /// # superclass = class!(NSObject); /// let arg3: u32 = unsafe { msg_send![super(obj, superclass), getArg3] }; /// ``` +/// +/// Sending a message with automatic error handling. +/// +/// ```no_run +/// use objc2::msg_send; +/// use objc2::runtime::Object; +/// use objc2::rc::{Id, Shared}; +/// +/// let obj: *mut Object; // Let's assume an instance of `NSBundle` +/// # obj = 0 as *mut Object; +/// // The `_` tells the macro that the return type should be `Result`. +/// let res: Result<(), Id> = unsafe { +/// msg_send![obj, preflightAndReturnError: _] +/// }; +/// ``` #[macro_export] macro_rules! msg_send { - [super($obj:expr), $selector:ident $(,)?] => ({ - let sel = $crate::sel!($selector); - let result; - // Note: `sel` and `result` can be accessed from the `obj` and - // `superclass` expressions - we won't (yet) bother with preventing - // that though. - result = $crate::MessageReceiver::__send_super_message_static($obj, sel, ()); - result - }); - [super($obj:expr), $($selector:ident : $argument:expr),+ $(,)?] => ({ - let sel = $crate::sel!($($selector :)+); - let result; - result = $crate::MessageReceiver::__send_super_message_static($obj, sel, ($($argument,)+)); - result - }); - [super($obj:expr, $superclass:expr), $selector:ident $(,)?] => ({ - let sel = $crate::sel!($selector); - let result; - result = $crate::MessageReceiver::send_super_message($obj, $superclass, sel, ()); - result - }); - [super($obj:expr, $superclass:expr), $($selector:ident : $argument:expr $(,)?)+] => ({ - let sel = $crate::sel!($($selector :)+); - let result; - result = $crate::MessageReceiver::send_super_message($obj, $superclass, sel, ($($argument,)+)); - result - }); - [$obj:expr, $selector:ident $(,)?] => ({ - let sel = $crate::sel!($selector); - let result; - result = $crate::MessageReceiver::send_message($obj, sel, ()); - result - }); - [$obj:expr, $($selector:ident : $argument:expr $(,)?)+] => ({ - let sel = $crate::sel!($($selector :)+); + [super($obj:expr), $($selector_and_arguments:tt)+] => { + $crate::__msg_send_parse! { + ($crate::__msg_send_helper) + @(__send_super_message_static_error) + @() + @() + @($($selector_and_arguments)+) + @(__send_super_message_static) + + @($obj) + } + }; + [super($obj:expr, $superclass:expr), $($selector_and_arguments:tt)+] => { + $crate::__msg_send_parse! { + ($crate::__msg_send_helper) + @(__send_super_message_error) + @() + @() + @($($selector_and_arguments)+) + @(send_super_message) + + @($obj, $superclass) + } + }; + [$obj:expr, $($selector_and_arguments:tt)+] => { + $crate::__msg_send_parse! { + ($crate::__msg_send_helper) + @(__send_message_error) + @() + @() + @($($selector_and_arguments)+) + @(send_message) + + @($obj) + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __msg_send_helper { + { + @($fn:ident) + @($($fn_args:tt)+) + @($($selector:tt)*) + @($($argument:expr,)*) + } => ({ + // Assign to intermediary variable for better UI, and to prevent + // miscompilation on older Rust versions. + // + // Note: This can be accessed from any expression in `fn_args` and + // `arguments` - we won't (yet) bother with preventing that though. let result; - result = $crate::MessageReceiver::send_message($obj, sel, ($($argument,)+)); + // Always add trailing comma after each argument, so that we get a + // 1-tuple if there is only one. + result = $crate::MessageReceiver::$fn($($fn_args)+, $crate::sel!($($selector)*), ($($argument,)*)); result }); } @@ -910,6 +978,9 @@ macro_rules! msg_send_bool { /// `Id / Allocated` this macro will automatically unwrap the object, or panic /// with an error message if it couldn't be retrieved. /// +/// Though as a special case, if the last argument is the marker `_`, the +/// macro will return a `Result, Id>`, see below. +/// /// This macro doesn't support super methods yet, see [#173]. /// The `retain`, `release` and `autorelease` selectors are not supported, use /// [`Id::retain`], [`Id::drop`] and [`Id::autorelease`] for that. @@ -924,6 +995,19 @@ macro_rules! msg_send_bool { /// [`Id::autorelease`]: crate::rc::Id::autorelease /// /// +/// # Errors +/// +/// Very similarly to [`msg_send!`], this macro supports transforming the +/// return type of methods whose last parameter is `NSError**` into the Rust +/// equivalent, the [`Result`] type. +/// +/// In particular, you can make the last argument the special marker `_`, and +/// then the macro will return a `Result, Id>` (where you +/// must specify `E` yourself, usually you'd use [`foundation::NSError`]). +/// +/// [`foundation::NSError`]: crate::foundation::NSError +/// +/// /// # Panics /// /// Panics if the return type is specified as `Id<_, _>` and the method @@ -992,46 +1076,73 @@ macro_rules! msg_send_id { result = <$crate::__macro_helpers::Init as $crate::__macro_helpers::MsgSendId<_, _>>::send_message_id($obj, sel, ()); result }); - [$obj:expr, $selector:ident $(,)?] => ({ - $crate::__msg_send_id_helper!(@verify $selector); - let sel = $crate::sel!($selector); - const NAME: &$crate::__macro_helpers::str = $crate::__macro_helpers::stringify!($selector); - let result; - result = <$crate::__macro_helpers::RetainSemantics<{ - $crate::__macro_helpers::retain_semantics(NAME) - }> as $crate::__macro_helpers::MsgSendId<_, _>>::send_message_id($obj, sel, ()); - result - }); - [$obj:expr, $($selector:ident : $argument:expr),+ $(,)?] => ({ - let sel = $crate::sel!($($selector:)+); - const NAME: &$crate::__macro_helpers::str = - $crate::__macro_helpers::concat!($($crate::__macro_helpers::stringify!($selector), ':'),+); - let result; - result = <$crate::__macro_helpers::RetainSemantics<{ - $crate::__macro_helpers::retain_semantics(NAME) - }> as $crate::__macro_helpers::MsgSendId<_, _>>::send_message_id($obj, sel, ($($argument,)+)); - result - }); + [$obj:expr, $($selector_and_arguments:tt)+] => { + $crate::__msg_send_parse! { + ($crate::__msg_send_id_helper) + @(send_message_id_error) + @() + @() + @($($selector_and_arguments)+) + @(send_message_id) + + @($obj) + } + }; } /// Helper macro to avoid exposing these in the docs for [`msg_send_id!`]. #[doc(hidden)] #[macro_export] macro_rules! __msg_send_id_helper { - (@verify retain) => {{ + { + @($fn:ident) + @($obj:expr) + @(retain) + @() + } => {{ $crate::__macro_helpers::compile_error!( "msg_send_id![obj, retain] is not supported. Use `Id::retain` instead" ) }}; - (@verify release) => {{ + { + @($fn:ident) + @($obj:expr) + @(release) + @() + } => {{ $crate::__macro_helpers::compile_error!( "msg_send_id![obj, release] is not supported. Drop an `Id` instead" ) }}; - (@verify autorelease) => {{ + { + @($fn:ident) + @($obj:expr) + @(autorelease) + @() + } => {{ $crate::__macro_helpers::compile_error!( "msg_send_id![obj, autorelease] is not supported. Use `Id::autorelease`" ) }}; - (@verify $selector:ident) => {{}}; + { + @($fn:ident) + @($obj:expr) + @($sel_first:ident $(: $($sel_rest:ident :)*)?) + @($($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 :)*)?); + 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)*)?) + ), + ($($argument,)*), + ); + result + }); } diff --git a/objc2/src/macros/__msg_send_parse.rs b/objc2/src/macros/__msg_send_parse.rs new file mode 100644 index 000000000..7b4657159 --- /dev/null +++ b/objc2/src/macros/__msg_send_parse.rs @@ -0,0 +1,100 @@ +#[doc(hidden)] +#[macro_export] +macro_rules! __msg_send_parse { + // No arguments + { + ($out_macro:path) + @($error_fn:ident) + // Intentionally empty + @() + @() + @($selector:ident $(,)?) + $($macro_args:tt)* + } => { + $crate::__msg_send_parse! { + ($out_macro) + @($error_fn) + @($selector) + @() + @() + $($macro_args)* + } + }; + + // tt-munch remaining `selector: argument` pairs, looking for a pattern + // that ends with `sel: _`. + { + ($out_macro:path) + @($_error_fn:ident) + @($($selector_output:tt)*) + @($($argument_output:tt)*) + @() + $($macro_args:tt)* + } => ({ + $out_macro! { + $($macro_args)* + @($($selector_output)*) + @($($argument_output)*) + } + }); + { + ($out_macro:path) + @($error_fn:ident) + @($($selector_output:tt)*) + @($($argument_output:tt)*) + @($selector:ident: _ $(,)?) + @($fn:ident) + $($macro_args:tt)* + } => { + $crate::__msg_send_parse! { + ($out_macro) + @($error_fn) + @($($selector_output)* $selector:) + // Don't pass an argument + @($($argument_output)*) + @() + + // Instead, we change the called function to the error function. + @($error_fn) + $($macro_args)* + } + }; + { + ($out_macro:path) + @($error_fn:ident) + @($($selector_output:tt)*) + @($($argument_output:tt)*) + @($selector:ident : $argument:expr $(, $($rest:tt)*)?) + $($macro_args:tt)* + } => { + $crate::__msg_send_parse! { + ($out_macro) + @($error_fn) + @($($selector_output)* $selector:) + @($($argument_output)* $argument,) + @($($($rest)*)?) + $($macro_args)* + } + }; + + // Handle calls without comma between `selector: argument` pair. + // TODO: Deprecate this + { + ($out_macro:path) + @($error_fn:ident) + // Intentionally empty + @() + @() + @($($selector:ident : $argument:expr)*) + $($macro_args:tt)* + } => { + $crate::__msg_send_parse! { + ($out_macro) + @($error_fn) + @() + @() + @($($selector : $argument),*) + $($macro_args)* + } + }; +} diff --git a/objc2/src/macros/extern_methods.rs b/objc2/src/macros/extern_methods.rs index 45c32050f..07249fdb8 100644 --- a/objc2/src/macros/extern_methods.rs +++ b/objc2/src/macros/extern_methods.rs @@ -37,8 +37,9 @@ /// [`NSCalendar`]: https://developer.apple.com/documentation/foundation/nscalendar?language=objc /// /// ``` -/// use objc2::foundation::{NSObject, NSRange, NSString, NSUInteger}; +/// use objc2::foundation::{NSError, NSObject, NSRange, NSString, NSUInteger}; /// use objc2::rc::{Id, Shared}; +/// use objc2::runtime::Object; /// use objc2::{extern_class, extern_methods, msg_send_id, Encode, Encoding, ClassType}; /// # /// # #[cfg(feature = "gnustep-1-7")] @@ -103,6 +104,16 @@ /// /// #[sel(maximumRangeOfUnit:)] /// pub fn max_range(&self, unit: NSCalendarUnit) -> NSRange; +/// +/// // From `NSKeyValueCoding` +/// #[sel(validateValue:forKey:error:)] +/// pub unsafe fn validate_value_for_key( +/// &self, +/// value: &mut *mut Object, +/// key: &NSString, +/// // Since the selector specifies one more argument than we +/// // have, the return type is assumed to be `Result`. +/// ) -> Result<(), Id>; /// } /// ); /// ``` @@ -110,8 +121,9 @@ /// The `extern_methods!` declaration then becomes: /// /// ``` -/// # use objc2::foundation::{NSObject, NSRange, NSString, NSUInteger}; +/// # use objc2::foundation::{NSError, NSObject, NSRange, NSString, NSUInteger}; /// # use objc2::rc::{Id, Shared}; +/// # use objc2::runtime::Object; /// # use objc2::{extern_class, extern_methods, msg_send_id, Encode, Encoding, ClassType}; /// # /// # #[cfg(feature = "gnustep-1-7")] @@ -174,6 +186,14 @@ /// pub fn max_range(&self, unit: NSCalendarUnit) -> NSRange { /// unsafe { msg_send![self, maximumRangeOfUnit: unit] } /// } +/// +/// pub unsafe fn validate_value_for_key( +/// &self, +/// value: &mut *mut Object, +/// key: &NSString, +/// ) -> Result<(), Id> { +/// unsafe { msg_send![self, validateValue: value, forKey: key, error: _] } +/// } /// } /// ``` #[macro_export] @@ -370,6 +390,23 @@ macro_rules! __collect_msg_send { $macro![$obj, $($output)+] }}; + // Allow trailing `sel:` without a corresponding argument (for errors) + ( + $macro:path; + $obj:expr; + ($sel:ident:); + ($(,)?); + $($output:tt)* + ) => { + $crate::__collect_msg_send! { + $macro; + $obj; + (); + (); + $($output)* $sel: _, + } + }; + // tt-munch each argument ( $macro:path; diff --git a/objc2/src/message/mod.rs b/objc2/src/message/mod.rs index 7753300a6..768af743f 100644 --- a/objc2/src/message/mod.rs +++ b/objc2/src/message/mod.rs @@ -1,9 +1,9 @@ use core::mem; use core::mem::ManuallyDrop; -use core::ptr::NonNull; +use core::ptr::{self, NonNull}; use crate::encode::{Encode, EncodeArguments, EncodeConvert, RefEncode}; -use crate::rc::{Id, Owned, Ownership}; +use crate::rc::{Id, Owned, Ownership, Shared}; use crate::runtime::{Class, Imp, Object, Sel}; use crate::ClassType; @@ -249,6 +249,90 @@ pub unsafe trait MessageReceiver: private::Sealed + Sized { { unsafe { self.send_super_message(::Super::class(), sel, args) } } + + // Error functions below. See MsgSendId::send_message_id_error for further + // details. + // + // Some of this could be abstracted away using closures, but that would + // interfere with `#[track_caller]`, so we avoid doing that. + + #[inline] + #[track_caller] + #[doc(hidden)] + unsafe fn __send_message_error(self, sel: Sel, args: A) -> Result<(), Id> + where + *mut *mut E: Encode, + A: __TupleExtender<*mut *mut E>, + >::PlusOneArgument: MessageArguments, + E: Message, + { + let mut err: *mut E = ptr::null_mut(); + let args = args.add_argument(&mut err); + let res: bool = unsafe { self.send_message(sel, args) }; + if res { + Ok(()) + } else { + Err(unsafe { encountered_error(err) }) + } + } + + #[inline] + #[track_caller] + #[doc(hidden)] + unsafe fn __send_super_message_error( + self, + superclass: &Class, + sel: Sel, + args: A, + ) -> Result<(), Id> + where + *mut *mut E: Encode, + A: __TupleExtender<*mut *mut E>, + >::PlusOneArgument: MessageArguments, + E: Message, + { + let mut err: *mut E = ptr::null_mut(); + let args = args.add_argument(&mut err); + let res: bool = unsafe { self.send_super_message(superclass, sel, args) }; + if res { + Ok(()) + } else { + Err(unsafe { encountered_error(err) }) + } + } + + #[inline] + #[track_caller] + #[doc(hidden)] + unsafe fn __send_super_message_static_error( + self, + sel: Sel, + args: A, + ) -> Result<(), Id> + where + Self::__Inner: ClassType, + ::Super: ClassType, + *mut *mut E: Encode, + A: __TupleExtender<*mut *mut E>, + >::PlusOneArgument: MessageArguments, + E: Message, + { + let mut err: *mut E = ptr::null_mut(); + let args = args.add_argument(&mut err); + let res: bool = unsafe { self.__send_super_message_static(sel, args) }; + if res { + Ok(()) + } else { + Err(unsafe { encountered_error(err) }) + } + } +} + +#[cold] +#[track_caller] +unsafe fn encountered_error(err: *mut E) -> Id { + // SAFETY: Ensured by caller + unsafe { Id::retain(err) }.expect("error parameter should be set if the method returns NO") } // Note that we implement MessageReceiver for unsized types as well, this is @@ -367,6 +451,13 @@ pub unsafe trait MessageArguments: EncodeArguments { unsafe fn __invoke(imp: Imp, obj: *mut Object, sel: Sel, args: Self) -> R; } +pub trait __TupleExtender { + #[doc(hidden)] + type PlusOneArgument; + #[doc(hidden)] + fn add_argument(self, arg: T) -> Self::PlusOneArgument; +} + macro_rules! message_args_impl { ($($a:ident: $t:ident),*) => ( unsafe impl<$($t: EncodeConvert),*> MessageArguments for ($($t,)*) { @@ -390,6 +481,15 @@ macro_rules! message_args_impl { unsafe { imp(obj, sel $(, EncodeConvert::__into_inner($a))*) } } } + + impl<$($t,)* T> __TupleExtender for ($($t,)*) { + type PlusOneArgument = ($($t,)* T,); + + fn add_argument(self, arg: T) -> Self::PlusOneArgument { + let ($($a,)*) = self; + ($($a,)* arg,) + } + } ); } diff --git a/objc2/src/rc/id.rs b/objc2/src/rc/id.rs index 244489df0..6b3fc3b02 100644 --- a/objc2/src/rc/id.rs +++ b/objc2/src/rc/id.rs @@ -438,7 +438,7 @@ impl Id { } #[inline] - fn autorelease_inner(self) -> *mut T { + pub(super) fn autorelease_inner(self) -> *mut T { // Note that this (and the actual `autorelease`) is not an associated // function. This breaks the guideline that smart pointers shouldn't // add inherent methods, but since autoreleasing only works on already diff --git a/objc2/src/rc/test_object.rs b/objc2/src/rc/test_object.rs index 859db74a5..82181d5d0 100644 --- a/objc2/src/rc/test_object.rs +++ b/objc2/src/rc/test_object.rs @@ -4,7 +4,7 @@ use core::ptr; use super::{Id, Owned}; use crate::foundation::{NSObject, NSZone}; -use crate::{declare_class, msg_send, ClassType}; +use crate::{declare_class, msg_send, msg_send_id, ClassType}; #[derive(Debug, Clone, Default, PartialEq, Eq)] pub(crate) struct ThreadTestData { @@ -74,7 +74,7 @@ declare_class!( } #[sel(alloc)] - fn alloc() -> *mut Self { + fn alloc_() -> *mut Self { TEST_DATA.with(|data| data.borrow_mut().alloc += 1); let superclass = NSObject::class().metaclass(); let zone: *const NSZone = ptr::null(); @@ -161,6 +161,106 @@ declare_class!( fn method_returning_null(&self) -> *const Self { ptr::null() } + + #[sel(boolAndShouldError:error:)] + fn class_error_bool(should_error: bool, err: Option<&mut *mut RcTestObject>) -> bool { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + false + } else { + true + } + } + + #[sel(boolAndShouldError:error:)] + fn instance_error_bool( + &self, + should_error: bool, + err: Option<&mut *mut RcTestObject>, + ) -> bool { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + false + } else { + true + } + } + + #[sel(idAndShouldError:error:)] + fn class_error_id( + should_error: bool, + err: Option<&mut *mut RcTestObject>, + ) -> *mut RcTestObject { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + ptr::null_mut() + } else { + RcTestObject::new().autorelease_return() + } + } + + #[sel(idAndShouldError:error:)] + fn instance_error_id( + &self, + should_error: bool, + err: Option<&mut *mut RcTestObject>, + ) -> *mut RcTestObject { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + ptr::null_mut() + } else { + RcTestObject::new().autorelease_return() + } + } + + #[sel(newAndShouldError:error:)] + fn new_error(should_error: bool, err: Option<&mut *mut RcTestObject>) -> *mut Self { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + ptr::null_mut() + } else { + unsafe { msg_send![Self::class(), new] } + } + } + + #[sel(allocAndShouldError:error:)] + fn alloc_error(should_error: bool, err: Option<&mut *mut RcTestObject>) -> *mut Self { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + ptr::null_mut() + } else { + unsafe { msg_send![Self::class(), alloc] } + } + } + + #[sel(initAndShouldError:error:)] + fn init_error( + &mut self, + should_error: bool, + err: Option<&mut *mut RcTestObject>, + ) -> *mut Self { + if should_error { + if let Some(err) = err { + *err = RcTestObject::new().autorelease_inner(); + } + let _: () = unsafe { msg_send![self, release] }; + ptr::null_mut() + } else { + unsafe { msg_send![self, init] } + } + } } ); @@ -174,12 +274,205 @@ impl RcTestObject { } } +declare_class!( + #[derive(Debug, PartialEq)] + pub(crate) struct RcTestObjectSubclass {} + + unsafe impl ClassType for RcTestObjectSubclass { + #[inherits(NSObject)] + type Super = RcTestObject; + } +); + +impl RcTestObjectSubclass { + pub(crate) fn new() -> Id { + unsafe { msg_send_id![Self::class(), new] } + } +} + #[cfg(test)] mod tests { use super::*; + use crate::rc::{autoreleasepool, Allocated, Shared}; #[test] fn ensure_declared_name() { assert_eq!(RcTestObject::class().name(), RcTestObject::NAME); } + + macro_rules! test_error_bool { + ($expected:expr, $($obj:tt)*) => { + // Succeeds + let res: Result<(), Id> = unsafe { + msg_send![$($obj)*, boolAndShouldError: false, error: _] + }; + assert_eq!(res, Ok(())); + $expected.assert_current(); + + // Errors + let res = autoreleasepool(|_pool| { + // `Ok` type is inferred to be `()` + let res: Id = unsafe { + msg_send![$($obj)*, boolAndShouldError: true, error: _] + }.expect_err("not err"); + $expected.alloc += 1; + $expected.init += 1; + $expected.autorelease += 1; + $expected.retain += 1; + $expected.assert_current(); + res + }); + $expected.release += 1; + $expected.assert_current(); + + drop(res); + $expected.release += 1; + $expected.dealloc += 1; + $expected.assert_current(); + } + } + + #[test] + fn test_error_bool() { + let mut expected = ThreadTestData::current(); + + let cls = RcTestObject::class(); + test_error_bool!(expected, cls); + + let obj = RcTestObject::new(); + expected.alloc += 1; + expected.init += 1; + test_error_bool!(expected, &obj); + + let obj = RcTestObjectSubclass::new(); + expected.alloc += 1; + expected.init += 1; + test_error_bool!(expected, &obj); + test_error_bool!(expected, super(&obj)); + test_error_bool!(expected, super(&obj, RcTestObjectSubclass::class())); + test_error_bool!(expected, super(&obj, RcTestObject::class())); + } + + // This is imperfect, but will do for now. + // See also `tests/id_retain_autoreleased.rs`. + #[allow(clippy::bool_to_int_with_if)] + const IF_AUTORELEASE_NOT_SKIPPED: usize = if cfg!(feature = "gnustep-1-7") { + 0 + } else if cfg!(all( + not(feature = "gnustep-1-7"), + any( + debug_assertions, + feature = "exception", + feature = "verify_message" + ) + )) { + 1 + } else { + 0 + }; + + macro_rules! test_error_id { + ($expected:expr, $if_autorelease_not_skipped:expr, $sel:ident, $($obj:tt)*) => { + // Succeeds + let res = autoreleasepool(|_pool| { + let res: Result, Id> = unsafe { + msg_send_id![$($obj)*, $sel: false, error: _] + }; + let res = res.expect("not ok"); + $expected.alloc += 1; + $expected.init += 1; + $expected.autorelease += $if_autorelease_not_skipped; + $expected.retain += $if_autorelease_not_skipped; + $expected.assert_current(); + res + }); + $expected.release += $if_autorelease_not_skipped; + $expected.assert_current(); + + drop(res); + $expected.release += 1; + $expected.dealloc += 1; + $expected.assert_current(); + + // Errors + let res = autoreleasepool(|_pool| { + let res: Result, Id> = unsafe { + msg_send_id![$($obj)*, $sel: true, error: _] + }; + $expected.alloc += 1; + $expected.init += 1; + $expected.autorelease += 1; + $expected.retain += 1; + $expected.assert_current(); + res.expect_err("not err") + }); + $expected.release += 1; + $expected.assert_current(); + + drop(res); + $expected.release += 1; + $expected.dealloc += 1; + $expected.assert_current(); + } + } + + #[test] + fn test_error_id() { + let mut expected = ThreadTestData::current(); + + let cls = RcTestObject::class(); + test_error_id!(expected, IF_AUTORELEASE_NOT_SKIPPED, idAndShouldError, cls); + test_error_id!(expected, 0, newAndShouldError, cls); + + let obj = RcTestObject::new(); + expected.alloc += 1; + expected.init += 1; + test_error_id!(expected, IF_AUTORELEASE_NOT_SKIPPED, idAndShouldError, &obj); + + expected.alloc -= 1; + expected.release -= 1; + expected.dealloc -= 1; + test_error_id!(expected, 0, initAndShouldError, { + expected.alloc += 1; + expected.release += 1; + expected.dealloc += 1; + RcTestObject::alloc() + }); + } + + #[test] + fn test_error_alloc() { + let mut expected = ThreadTestData::current(); + + // Succeeds + let res: Result, Id> = + unsafe { msg_send_id![RcTestObject::class(), allocAndShouldError: false, error: _] }; + let res = res.expect("not ok"); + expected.alloc += 1; + expected.assert_current(); + + drop(res); + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); + + // Errors + let res = autoreleasepool(|_pool| { + let res: Result, Id> = + unsafe { msg_send_id![RcTestObject::class(), allocAndShouldError: true, error: _] }; + expected.alloc += 1; + expected.init += 1; + expected.autorelease += 1; + expected.retain += 1; + expected.assert_current(); + res.expect_err("not err") + }); + expected.release += 1; + expected.assert_current(); + + drop(res); + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); + } } diff --git a/objc2/tests/use_macros.rs b/objc2/tests/use_macros.rs index 3f7c6418d..cb1fdf6f3 100644 --- a/objc2/tests/use_macros.rs +++ b/objc2/tests/use_macros.rs @@ -32,7 +32,6 @@ fn test_msg_send_comma_handling(obj: &NSString, superclass: &Class) { let _: () = msg_send![obj, a: 32i32]; let _: () = msg_send![obj, a: 32i32,]; let _: () = msg_send![obj, a: 32i32 b: 32i32]; - let _: () = msg_send![obj, a: 32i32 b: 32i32,]; let _: () = msg_send![obj, a: 32i32, b: 32i32]; let _: () = msg_send![obj, a: 32i32, b: 32i32,]; } @@ -43,7 +42,6 @@ fn test_msg_send_comma_handling(obj: &NSString, superclass: &Class) { let _: () = msg_send![super(obj, superclass), a: 32i32]; let _: () = msg_send![super(obj, superclass), a: 32i32,]; let _: () = msg_send![super(obj, superclass), a: 32i32 b: 32i32]; - let _: () = msg_send![super(obj, superclass), a: 32i32 b: 32i32,]; let _: () = msg_send![super(obj, superclass), a: 32i32, b: 32i32]; let _: () = msg_send![super(obj, superclass), a: 32i32, b: 32i32,]; } diff --git a/test-ui/ui/extern_methods_wrong_arguments.rs b/test-ui/ui/extern_methods_wrong_arguments.rs index 39c1df128..50379a8a6 100644 --- a/test-ui/ui/extern_methods_wrong_arguments.rs +++ b/test-ui/ui/extern_methods_wrong_arguments.rs @@ -30,6 +30,13 @@ extern_methods!( } ); +extern_methods!( + unsafe impl MyObject { + #[sel(f:g:)] + fn f(); + } +); + extern_methods!( unsafe impl MyObject { #[sel(x:)] diff --git a/test-ui/ui/extern_methods_wrong_arguments.stderr b/test-ui/ui/extern_methods_wrong_arguments.stderr index 48b8121b8..47339e0a8 100644 --- a/test-ui/ui/extern_methods_wrong_arguments.stderr +++ b/test-ui/ui/extern_methods_wrong_arguments.stderr @@ -3,8 +3,8 @@ error: Number of arguments in function and selector did not match! | | / extern_methods!( | | unsafe impl MyObject { - | | #[sel(a:)] - | | fn a(); + | | #[sel(b)] + | | fn b(arg: i32); | | } | | ); | |_^ @@ -16,8 +16,8 @@ error: Number of arguments in function and selector did not match! | | / extern_methods!( | | unsafe impl MyObject { - | | #[sel(b)] - | | fn b(arg: i32); + | | #[sel(f:g:)] + | | fn f(); | | } | | ); | |_^ @@ -29,36 +29,64 @@ error: Number of arguments in function and selector did not match! | | / extern_methods!( | | unsafe impl MyObject { - | | #[sel(c:d:e:)] - | | fn c(arg1: i32, arg2: u32); + | | #[sel(y)] + | | fn y(&self, arg: i32); | | } | | ); | |_^ | = 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) -error: Number of arguments in function and selector did not match! +error[E0308]: mismatched types --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( | | unsafe impl MyObject { - | | #[sel(x:)] - | | fn x(&self); + | | #[sel(a:)] + | | fn a(); | | } | | ); - | |_^ + | | ^ + | | | + | |_expected `()`, found enum `Result` + | expected `()` because of default return type | - = 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: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` 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[E0308]: mismatched types --> ui/extern_methods_wrong_arguments.rs | | / extern_methods!( | | unsafe impl MyObject { - | | #[sel(y)] - | | fn y(&self, arg: i32); + | | #[sel(c:d:e:)] + | | fn c(arg1: i32, arg2: u32); | | } | | ); - | |_^ + | | ^ + | | | + | |_expected `()`, found enum `Result` + | expected `()` because of default return type | - = 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: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/extern_methods_wrong_arguments.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[sel(x:)] + | | fn x(&self); + | | } + | | ); + | | ^ + | | | + | |_expected `()`, found enum `Result` + | expected `()` because of default return type + | + = note: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test-ui/ui/invalid_msg_send.rs b/test-ui/ui/invalid_msg_send.rs index 21e493c3c..7cb244783 100644 --- a/test-ui/ui/invalid_msg_send.rs +++ b/test-ui/ui/invalid_msg_send.rs @@ -1,5 +1,6 @@ //! Test invalid msg_send syntax use objc2::msg_send; +use objc2::rc::{Id, Shared}; use objc2::runtime::Object; fn main() { @@ -13,4 +14,7 @@ fn main() { let _: () = unsafe { msg_send![obj, a: b: c] }; let _: () = unsafe { msg_send![obj, a: b, c d] }; let _: () = unsafe { msg_send![obj, a: b: c] }; + let _: () = unsafe { msg_send![obj, a: b c: d,] }; + + let _: Result<(), Id> = unsafe { msg_send![obj, a: _, b: _] }; } diff --git a/test-ui/ui/invalid_msg_send.stderr b/test-ui/ui/invalid_msg_send.stderr index 3a9e1b22e..7d2874ebd 100644 --- a/test-ui/ui/invalid_msg_send.stderr +++ b/test-ui/ui/invalid_msg_send.stderr @@ -10,23 +10,41 @@ error: unexpected end of macro invocation | let _: () = unsafe { msg_send![obj,] }; | ^ missing tokens in macro arguments -error: unexpected end of macro invocation +error: no rules expected the token `)` --> ui/invalid_msg_send.rs | | let _: () = unsafe { msg_send![obj, a:] }; - | ^ missing tokens in macro arguments + | ^^^^^^^^^^^^^^^^^^ no rules expected this token in macro call + | + = note: this error originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) -error: unexpected end of macro invocation +error: no rules expected the token `)` --> ui/invalid_msg_send.rs | | let _: () = unsafe { msg_send![obj, a: b c] }; - | ^ missing tokens in macro arguments + | ^^^^^^^^^^^^^^^^^^^^^^ no rules expected this token in macro call + | + = note: this error originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) -error: no rules expected the token `d` +error: no rules expected the token `a` --> ui/invalid_msg_send.rs | | let _: () = unsafe { msg_send![obj, a: b, c d] }; - | ^ no rules expected this token in macro call + | ^^^^^^^^^^^^^^^^^^^^^^^^^ no rules expected this token in macro call + | + = note: this error originates in the macro `$crate::__msg_send_parse` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: no rules expected the token `,` + --> ui/invalid_msg_send.rs + | + | let _: () = unsafe { msg_send![obj, a: b c: d,] }; + | ^ no rules expected this token in macro call + +error: no rules expected the token `b` + --> ui/invalid_msg_send.rs + | + | let _: Result<(), Id> = unsafe { msg_send![obj, a: _, b: _] }; + | ^ no rules expected this token in macro call error[E0412]: cannot find type `c` in this scope --> ui/invalid_msg_send.rs diff --git a/test-ui/ui/msg_send_id_invalid_return.stderr b/test-ui/ui/msg_send_id_invalid_return.stderr index 3eb7d0e29..e5bc0c6c9 100644 --- a/test-ui/ui/msg_send_id_invalid_return.stderr +++ b/test-ui/ui/msg_send_id_invalid_return.stderr @@ -191,7 +191,7 @@ note: required by a bound in `send_message_id` | | unsafe fn send_message_id>( | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `send_message_id` - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `&objc2::runtime::Object: MaybeUnwrap` is not satisfied --> ui/msg_send_id_invalid_return.rs @@ -209,7 +209,7 @@ note: required by a bound in `send_message_id` | | unsafe fn send_message_id>( | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `send_message_id` - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Option<&objc2::runtime::Object>: MaybeUnwrap` is not satisfied --> ui/msg_send_id_invalid_return.rs @@ -225,4 +225,4 @@ note: required by a bound in `send_message_id` | | unsafe fn send_message_id>( | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `send_message_id` - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test-ui/ui/msg_send_id_underspecified.stderr b/test-ui/ui/msg_send_id_underspecified.stderr index c1fa4056a..bbbdd20da 100644 --- a/test-ui/ui/msg_send_id_underspecified.stderr +++ b/test-ui/ui/msg_send_id_underspecified.stderr @@ -4,7 +4,7 @@ error[E0282]: type annotations needed | let _: &Object = &*unsafe { msg_send_id![obj, description] }; | ----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- type must be known at this point | - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider giving `result` an explicit type --> $WORKSPACE/objc2/src/macros.rs | diff --git a/test-ui/ui/msg_send_invalid_error.rs b/test-ui/ui/msg_send_invalid_error.rs new file mode 100644 index 000000000..0ee6b0470 --- /dev/null +++ b/test-ui/ui/msg_send_invalid_error.rs @@ -0,0 +1,21 @@ +//! Test that msg_send! error handling works correctly. +use objc2::{msg_send, msg_send_id}; +use objc2::ClassType; +use objc2::rc::{Id, Shared}; +use objc2::foundation::NSString; + +fn main() { + let obj: &NSString; + + // Wrong type + let _: () = unsafe { msg_send![obj, a: _] }; + let _: Result = unsafe { msg_send![obj, b: _] }; + let _: Result<(), i32> = unsafe { msg_send![obj, c: _] }; + let _: Result<(), Id> = unsafe { msg_send![obj, d: _] }; + + // Different calls + let _: () = unsafe { msg_send![obj, e: obj, f: _] }; + let _: () = unsafe { msg_send![super(obj), g: _] }; + let _: () = unsafe { msg_send![super(obj, NSString::class()), h: _] }; + let _: () = unsafe { msg_send_id![obj, i: _] }; +} diff --git a/test-ui/ui/msg_send_invalid_error.stderr b/test-ui/ui/msg_send_invalid_error.stderr new file mode 100644 index 000000000..440f170f7 --- /dev/null +++ b/test-ui/ui/msg_send_invalid_error.stderr @@ -0,0 +1,97 @@ +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: () = unsafe { msg_send![obj, a: _] }; + | ^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: Result = unsafe { msg_send![obj, b: _] }; + | ^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `()` + | + = note: expected enum `Result` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) +help: try wrapping the expression in `Err` + --> $WORKSPACE/objc2/src/macros.rs + | + | Err(result) + | ++++ + + +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: Result<(), i32> = unsafe { msg_send![obj, c: _] }; + | ^^^^^^^^^^^^^^^^^^^^ expected `i32`, found struct `Id` + | + = note: expected enum `Result<_, i32>` + found enum `Result<_, Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `i32: Message` is not satisfied + --> ui/msg_send_invalid_error.rs + | + | let _: Result<(), Id> = unsafe { msg_send![obj, d: _] }; + | ^^^^^^^^^^^^^^^^^^^^ the trait `Message` is not implemented for `i32` + | + = help: the following other types implement trait `Message`: + Exception + NSArray + NSAttributedString + NSBundle + NSData + NSDictionary + NSError + NSException + and $N others +note: required by a bound in `__send_message_error` + --> $WORKSPACE/objc2/src/message/mod.rs + | + | E: Message, + | ^^^^^^^ required by this bound in `__send_message_error` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: () = unsafe { msg_send![obj, e: obj, f: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: () = unsafe { msg_send![super(obj), g: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: () = unsafe { msg_send![super(obj, NSString::class()), h: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_invalid_error.rs + | + | let _: () = unsafe { msg_send_id![obj, i: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result, Id<_, Shared>>` + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test-ui/ui/msg_send_mutable.stderr b/test-ui/ui/msg_send_mutable.stderr index cab18e9fb..21930eaf7 100644 --- a/test-ui/ui/msg_send_mutable.stderr +++ b/test-ui/ui/msg_send_mutable.stderr @@ -5,13 +5,7 @@ error[E0382]: use of moved value: `obj` | --- move occurs because `obj` has type `&mut objc2::runtime::Object`, which does not implement the `Copy` trait | | let _: () = unsafe { msg_send![obj, selector] }; - | ------------------------ `obj` moved due to this method call + | --- value moved here | // Could be solved with a reborrow | let _: () = unsafe { msg_send![obj, selector] }; | ^^^ value used here after move - | -note: this function takes ownership of the receiver `self`, which moves `obj` - --> $WORKSPACE/objc2/src/message/mod.rs - | - | unsafe fn send_message(self, sel: Sel, args: A) -> R - | ^^^^ diff --git a/test-ui/ui/msg_send_no_return_type.stderr b/test-ui/ui/msg_send_no_return_type.stderr index ffe6ed567..9bcd9a14b 100644 --- a/test-ui/ui/msg_send_no_return_type.stderr +++ b/test-ui/ui/msg_send_no_return_type.stderr @@ -4,7 +4,7 @@ error[E0282]: type annotations needed | msg_send![cls, new]; | ^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider giving `result` an explicit type --> $WORKSPACE/objc2/src/macros.rs | diff --git a/test-ui/ui/msg_send_not_encode.stderr b/test-ui/ui/msg_send_not_encode.stderr index c549e346b..9b4e497f4 100644 --- a/test-ui/ui/msg_send_not_encode.stderr +++ b/test-ui/ui/msg_send_not_encode.stderr @@ -20,7 +20,7 @@ note: required by a bound in `send_message` | | R: EncodeConvert, | ^^^^^^^^^^^^^ required by this bound in `send_message` - = note: this error originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Vec: Encode` is not satisfied --> ui/msg_send_not_encode.rs @@ -48,4 +48,4 @@ note: required by a bound in `send_message` | | A: MessageArguments, | ^^^^^^^^^^^^^^^^ required by this bound in `send_message` - = note: this error originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test-ui/ui/msg_send_underspecified_error.rs b/test-ui/ui/msg_send_underspecified_error.rs new file mode 100644 index 000000000..0af2a1045 --- /dev/null +++ b/test-ui/ui/msg_send_underspecified_error.rs @@ -0,0 +1,13 @@ +//! Test underspecified msg_send! errors. +use objc2::{msg_send, msg_send_id}; +use objc2::rc::{Id, Shared}; +use objc2::foundation::NSString; + +fn main() { + let obj: &NSString; + let _: Result<(), _> = unsafe { msg_send![obj, a: _] }; + + let _: Result<_, _> = unsafe { msg_send_id![obj, b: _] }; + let _: Result, _> = unsafe { msg_send_id![obj, c: _] }; + let _: Result, Id<_, Shared>> = unsafe { msg_send_id![obj, d: _] }; +} diff --git a/test-ui/ui/msg_send_underspecified_error.stderr b/test-ui/ui/msg_send_underspecified_error.stderr new file mode 100644 index 000000000..e852982ca --- /dev/null +++ b/test-ui/ui/msg_send_underspecified_error.stderr @@ -0,0 +1,84 @@ +error[E0282]: type annotations needed + --> ui/msg_send_underspecified_error.rs + | + | let _: Result<(), _> = unsafe { msg_send![obj, a: _] }; + | ^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `E` declared on the associated function `__send_message_error` + | + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider specifying the generic arguments + --> $WORKSPACE/objc2/src/macros.rs + | + | @(__send_message_error::<(), E>) + | +++++++++ + +error[E0283]: type annotations needed + --> ui/msg_send_underspecified_error.rs + | + | let _: Result<(), _> = unsafe { msg_send![obj, a: _] }; + | ^^^^^^^^^^^^^^^^^^^^ cannot infer type for raw pointer `*mut _` + | + = note: multiple `impl`s satisfying `*mut _: RefEncode` found in the `objc2_encode` crate: + - impl RefEncode for *mut c_void; + - impl RefEncode for *mut T + where T: RefEncode, T: ?Sized; + = note: required for `*mut *mut _` to implement `Encode` +note: required by a bound in `__send_message_error` + --> $WORKSPACE/objc2/src/message/mod.rs + | + | *mut *mut E: Encode, + | ^^^^^^ required by this bound in `__send_message_error` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0283]: type annotations needed + --> ui/msg_send_underspecified_error.rs + | + | let _: Result<_, _> = unsafe { msg_send_id![obj, b: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for raw pointer `*mut _` + | + = note: multiple `impl`s satisfying `*mut _: RefEncode` found in the `objc2_encode` crate: + - impl RefEncode for *mut c_void; + - impl RefEncode for *mut T + where T: RefEncode, T: ?Sized; + = note: required for `*mut *mut _` to implement `Encode` +note: required by a bound in `send_message_id_error` + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | *mut *mut E: Encode, + | ^^^^^^ required by this bound in `send_message_id_error` + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0283]: type annotations needed + --> ui/msg_send_underspecified_error.rs + | + | let _: Result, _> = unsafe { msg_send_id![obj, c: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for raw pointer `*mut _` + | + = note: multiple `impl`s satisfying `*mut _: RefEncode` found in the `objc2_encode` crate: + - impl RefEncode for *mut c_void; + - impl RefEncode for *mut T + where T: RefEncode, T: ?Sized; + = note: required for `*mut *mut _` to implement `Encode` +note: required by a bound in `send_message_id_error` + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | *mut *mut E: Encode, + | ^^^^^^ required by this bound in `send_message_id_error` + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0283]: type annotations needed + --> ui/msg_send_underspecified_error.rs + | + | let _: Result, Id<_, Shared>> = unsafe { msg_send_id![obj, d: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for raw pointer `*mut _` + | + = note: multiple `impl`s satisfying `*mut _: RefEncode` found in the `objc2_encode` crate: + - impl RefEncode for *mut c_void; + - impl RefEncode for *mut T + where T: RefEncode, T: ?Sized; + = note: required for `*mut *mut _` to implement `Encode` +note: required by a bound in `send_message_id_error` + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | *mut *mut E: Encode, + | ^^^^^^ required by this bound in `send_message_id_error` + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test-ui/ui/msg_send_underspecified_error2.rs b/test-ui/ui/msg_send_underspecified_error2.rs new file mode 100644 index 000000000..ab9548232 --- /dev/null +++ b/test-ui/ui/msg_send_underspecified_error2.rs @@ -0,0 +1,9 @@ +//! Test underspecified msg_send! errors. +use objc2::msg_send_id; +use objc2::rc::{Id, Shared}; +use objc2::foundation::{NSError, NSString}; + +fn main() { + let obj: &NSString; + let _: Result, Id> = unsafe { msg_send_id![obj, a: _] }; +} diff --git a/test-ui/ui/msg_send_underspecified_error2.stderr b/test-ui/ui/msg_send_underspecified_error2.stderr new file mode 100644 index 000000000..3464603be --- /dev/null +++ b/test-ui/ui/msg_send_underspecified_error2.stderr @@ -0,0 +1,12 @@ +error[E0282]: type annotations needed + --> ui/msg_send_underspecified_error2.rs + | + | let _: Result, Id> = unsafe { msg_send_id![obj, a: _] }; + | ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `MsgSendId` + | + = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider specifying the generic arguments + --> $WORKSPACE/objc2/src/macros.rs + | + | }> as $crate::__macro_helpers::MsgSendId::<&NSString, Id>>::$fn( + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~