#![deny(deprecated, unreachable_code)] use core::ptr::{self, NonNull}; use objc2::mutability::Immutable; use objc2::rc::Retained; use objc2::runtime::NSObject; use objc2::{declare_class, extern_methods, sel, ClassType, DeclaredClass}; // Test that adding the `deprecated` attribute does not mean that warnings // when using the method internally are output. declare_class!( struct DeclareClassDepreactedMethod; unsafe impl ClassType for DeclareClassDepreactedMethod { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "DeclareClassDepreactedMethod"; } impl DeclaredClass for DeclareClassDepreactedMethod {} #[deprecated] unsafe impl DeclareClassDepreactedMethod { #[method(deprecatedOnImpl)] fn deprecated_on_impl() {} } unsafe impl DeclareClassDepreactedMethod { #[deprecated] #[method(deprecatedOnMethod)] fn deprecated_on_method() {} } ); #[test] fn test_deprecated() { let _cls = DeclareClassDepreactedMethod::class(); } // Test that `cfg` works properly. // // We use `debug_assertions` here because it's something that we know our CI // already tests. declare_class!( struct DeclareClassCfg; unsafe impl ClassType for DeclareClassCfg { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "DeclareClassCfg"; } impl DeclaredClass for DeclareClassCfg {} unsafe impl DeclareClassCfg { #[cfg(debug_assertions)] #[method(changesOnCfg1)] fn _changes_on_cfg1() -> i32 { 1 } #[cfg(not(debug_assertions))] #[method(changesOnCfg1)] fn _changes_on_cfg1() -> i32 { 2 } #[cfg(debug_assertions)] #[method(onlyWhenEnabled1)] fn _only_when_enabled1(&self) {} #[cfg(not(debug_assertions))] #[method(onlyWhenDisabled1)] fn _only_when_disabled1(&self) {} } #[cfg(debug_assertions)] unsafe impl DeclareClassCfg { #[method(changesOnCfg2)] fn _changes_on_cfg2(&self) -> i32 { 1 } #[method(onlyWhenEnabled2)] fn _only_when_enabled2() {} } #[cfg(not(debug_assertions))] unsafe impl DeclareClassCfg { #[method(changesOnCfg2)] fn _changes_on_cfg2(&self) -> i32 { 2 } #[method(onlyWhenDisabled2)] fn _only_when_disabled2() {} } #[cfg(debug_assertions)] unsafe impl DeclareClassCfg { #[cfg(not(debug_assertions))] #[method(never)] fn _never(&self) {} #[cfg(not(debug_assertions))] #[method(never)] fn _never_class() {} } ); extern_methods!( unsafe impl DeclareClassCfg { #[method_id(new)] fn new() -> Retained; } unsafe impl DeclareClassCfg { #[method(changesOnCfg1)] fn changes_on_cfg1() -> i32; #[method(changesOnCfg2)] fn changes_on_cfg2(&self) -> i32; #[cfg(debug_assertions)] #[method(onlyWhenEnabled1)] fn only_when_enabled1(&self); #[cfg(not(debug_assertions))] #[method(onlyWhenDisabled1)] fn only_when_disabled1(&self); } #[cfg(debug_assertions)] unsafe impl DeclareClassCfg { #[method(onlyWhenEnabled2)] fn only_when_enabled2(); } #[cfg(not(debug_assertions))] unsafe impl DeclareClassCfg { #[method(onlyWhenDisabled2)] fn only_when_disabled2(); } ); #[test] fn test_method_that_changes_based_on_cfg() { let expected = if cfg!(debug_assertions) { 1 } else { 2 }; let actual = DeclareClassCfg::changes_on_cfg1(); assert_eq!(expected, actual, "changes_on_cfg1"); let actual = DeclareClassCfg::new().changes_on_cfg2(); assert_eq!(expected, actual, "changes_on_cfg2"); } #[test] fn test_method_that_is_only_available_based_on_cfg() { let cls = DeclareClassCfg::class(); let metacls = cls.metaclass(); let obj = DeclareClassCfg::new(); #[cfg(debug_assertions)] { assert!(!cls.responds_to(sel!(onlyWhenDisabled1))); assert!(!metacls.responds_to(sel!(onlyWhenDisabled2))); obj.only_when_enabled1(); DeclareClassCfg::only_when_enabled2(); } #[cfg(not(debug_assertions))] { assert!(!cls.responds_to(sel!(onlyWhenEnabled1))); assert!(!metacls.responds_to(sel!(onlyWhenEnabled2))); obj.only_when_disabled1(); DeclareClassCfg::only_when_disabled2(); } } #[test] fn test_method_that_is_never_available() { let cls = DeclareClassCfg::class(); let metacls = cls.metaclass(); assert!(!cls.responds_to(sel!(never))); assert!(!metacls.responds_to(sel!(never))); } declare_class!( struct TestMultipleColonSelector; unsafe impl ClassType for TestMultipleColonSelector { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "TestMultipleColonSelector"; } impl DeclaredClass for TestMultipleColonSelector {} 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_id(test:::withObject:)] fn _test_object( &self, _arg1: i32, _arg2: i32, _arg3: i32, _obj: *const Self, ) -> Option> { None } } ); extern_methods!( unsafe impl TestMultipleColonSelector { #[method_id(new)] fn new() -> Retained; #[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<(), Retained>; #[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()); } declare_class!( struct DeclareClassAllTheBool; unsafe impl ClassType for DeclareClassAllTheBool { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "DeclareClassAllTheBool"; } impl DeclaredClass for DeclareClassAllTheBool {} unsafe impl DeclareClassAllTheBool { #[method(returnsBool)] fn returns_bool() -> bool { true } #[method(returnsBoolInstance)] fn returns_bool_instance(&self) -> bool { true } #[method(takesBool:andMut:andUnderscore:)] fn takes_bool(a: bool, mut b: bool, _: bool) -> bool { if b { b = a; } b } #[method(takesBoolInstance:andMut:andUnderscore:)] fn takes_bool_instance(&self, a: bool, mut b: bool, _: bool) -> bool { if b { b = a; } b } #[method(takesReturnsBool:)] fn takes_returns_bool(b: bool) -> bool { b } #[method(takesReturnsBoolInstance:)] fn takes_returns_bool_instance(&self, b: bool) -> bool { b } #[method_id(idTakesBool:)] fn id_takes_bool(_b: bool) -> Option> { None } #[method_id(idTakesBoolInstance:)] fn id_takes_bool_instance(&self, _b: bool) -> Option> { None } } ); #[test] fn test_all_the_bool() { let _ = DeclareClassAllTheBool::class(); } declare_class!( struct DeclareClassUnreachable; unsafe impl ClassType for DeclareClassUnreachable { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "DeclareClassUnreachable"; } impl DeclaredClass for DeclareClassUnreachable {} // Ensure none of these warn unsafe impl DeclareClassUnreachable { #[method(unreachable)] fn unreachable(&self) -> bool { unreachable!() } #[method(unreachableClass)] fn unreachable_class() -> bool { unreachable!() } #[method(unreachableVoid)] fn unreachable_void(&self) { unreachable!() } #[method(unreachableClassVoid)] fn unreachable_class_void() { unreachable!() } #[method_id(unreachableId)] fn unreachable_id(&self) -> Retained { unreachable!() } #[method_id(unreachableClassId)] fn unreachable_class_id() -> Retained { unreachable!() } } ); #[test] fn test_unreachable() { let _ = DeclareClassUnreachable::class(); } declare_class!( #[derive(Debug)] struct OutParam; unsafe impl ClassType for OutParam { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "OutParam"; } impl DeclaredClass for OutParam {} unsafe impl OutParam { #[method(unsupported1:)] fn _unsupported1(_param: &mut Retained) {} #[method(unsupported2:)] fn _unsupported2(_param: Option<&mut Retained>) {} #[method(unsupported3:)] fn _unsupported3(_param: &mut Option>) {} #[method(unsupported4:)] fn _unsupported4(_param: Option<&mut Option>>) {} } ); extern_methods!( unsafe impl OutParam { #[method_id(new)] fn new() -> Retained; #[method(unsupported1:)] fn unsupported1(_param: &mut Retained); #[method(unsupported2:)] fn unsupported2(_param: Option<&mut Retained>); #[method(unsupported3:)] fn unsupported3(_param: &mut Option>); #[method(unsupported4:)] fn unsupported4(_param: Option<&mut Option>>); } ); #[test] #[should_panic = "`&mut Retained<_>` is not supported in `declare_class!` yet"] #[cfg_attr( not(all(target_pointer_width = "64", not(feature = "catch-all"))), ignore = "unwinds through FFI boundary" )] fn out_param1() { let mut param = OutParam::new(); OutParam::unsupported1(&mut param); } #[test] #[should_panic = "`Option<&mut Retained<_>>` is not supported in `declare_class!` yet"] #[cfg_attr( not(all(target_pointer_width = "64", not(feature = "catch-all"))), ignore = "unwinds through FFI boundary" )] fn out_param2() { OutParam::unsupported2(None); } #[test] #[should_panic = "`&mut Option>` is not supported in `declare_class!` yet"] #[cfg_attr( not(all(target_pointer_width = "64", not(feature = "catch-all"))), ignore = "unwinds through FFI boundary" )] fn out_param3() { let mut param = Some(OutParam::new()); OutParam::unsupported3(&mut param); } #[test] #[should_panic = "`Option<&mut Option>>` is not supported in `declare_class!` yet"] #[cfg_attr( not(all(target_pointer_width = "64", not(feature = "catch-all"))), ignore = "unwinds through FFI boundary" )] fn out_param4() { OutParam::unsupported4(None); } #[test] fn test_pointer_receiver_allowed() { declare_class!( #[derive(Debug)] struct PointerReceiver; unsafe impl ClassType for PointerReceiver { type Super = NSObject; type Mutability = Immutable; const NAME: &'static str = "PointerReceiver"; } impl DeclaredClass for PointerReceiver {} unsafe impl PointerReceiver { #[method(constPtr)] fn const_ptr(_this: *const Self) {} #[method(mutPtr)] fn mut_ptr(_this: *mut Self) {} #[method(nonnullPtr)] fn nonnull_ptr(_this: NonNull) {} } ); let _ = PointerReceiver::class(); }