diff --git a/objc2-encode/src/encode.rs b/objc2-encode/src/encode.rs index bec6a89ad..81a2765df 100644 --- a/objc2-encode/src/encode.rs +++ b/objc2-encode/src/encode.rs @@ -430,8 +430,7 @@ encode_pointer_impls!( /// /// Ideally we'd implement it for all function pointers, but due to coherence /// issues, see , function -/// pointers that take arguments with "special lifetimes" (don't know the -/// termonology) don't get implemented properly. +/// pointers that are higher-ranked over lifetimes don't get implemented. /// /// We could fix it by adding those impls and allowing `coherence_leak_check`, /// but it would have to be done for _all_ references, `Option<&T>` and such as diff --git a/objc2-foundation/src/array.rs b/objc2-foundation/src/array.rs index c2fa4c703..e9d31187a 100644 --- a/objc2-foundation/src/array.rs +++ b/objc2-foundation/src/array.rs @@ -303,7 +303,10 @@ impl NSMutableArray { NSComparisonResult::from((*closure)(obj1, obj2)) } - let f: extern "C" fn(_, _, _) -> _ = compare_with_closure::; + // We can't name the actual lifetimes in use here, so use `_`. + // See also https://github.com/rust-lang/rust/issues/56105 + let f: extern "C" fn(_, _, *mut c_void) -> NSComparisonResult = + compare_with_closure::; // Grab a type-erased pointer to the closure (a pointer to stack). let mut closure = compare; diff --git a/tests/ui/fn_ptr_reference_encode.rs b/tests/ui/fn_ptr_reference_encode.rs new file mode 100644 index 000000000..377c269e4 --- /dev/null +++ b/tests/ui/fn_ptr_reference_encode.rs @@ -0,0 +1,23 @@ +//! Test that `Encode` is not implemented for function pointers that are +//! higher-ranked over lifetimes. +//! +//! Ideally, they should be, but they can't be right now. +//! +//! (Also test that we can use `_` to work around this). +use objc2::Encode; + +extern "C" fn my_fn(_x: &i32) {} + +fn e(_x: T) {} + +fn main() { + // Works + e(my_fn as extern "C" fn(_)); + // Can't be written: + // let encoding = ::ENCODING; + + // Fails + e(my_fn as extern "C" fn(&i32)); + // Also fails, properly tested in `fn_ptr_reference_encode2` + let encoding = ::ENCODING; +} diff --git a/tests/ui/fn_ptr_reference_encode.stderr b/tests/ui/fn_ptr_reference_encode.stderr new file mode 100644 index 000000000..f43345222 --- /dev/null +++ b/tests/ui/fn_ptr_reference_encode.stderr @@ -0,0 +1,8 @@ +error: implementation of `Encode` is not general enough + --> ui/fn_ptr_reference_encode.rs:20:5 + | +20 | e(my_fn as extern "C" fn(&i32)); + | ^ implementation of `Encode` is not general enough + | + = note: `Encode` would have to be implemented for the type `for<'r> extern "C" fn(&'r i32)` + = note: ...but `Encode` is actually implemented for the type `extern "C" fn(&'0 i32)`, for some specific lifetime `'0` diff --git a/tests/ui/fn_ptr_reference_encode2.rs b/tests/ui/fn_ptr_reference_encode2.rs new file mode 100644 index 000000000..976fd8668 --- /dev/null +++ b/tests/ui/fn_ptr_reference_encode2.rs @@ -0,0 +1,7 @@ +//! Extra test for `fn_ptr_reference_encode` +//! (They fail at different compilation passes). +use objc2::Encode; + +fn main() { + let encoding = ::ENCODING; +} diff --git a/tests/ui/fn_ptr_reference_encode2.stderr b/tests/ui/fn_ptr_reference_encode2.stderr new file mode 100644 index 000000000..450cf8a79 --- /dev/null +++ b/tests/ui/fn_ptr_reference_encode2.stderr @@ -0,0 +1,8 @@ +error: implementation of `Encode` is not general enough + --> ui/fn_ptr_reference_encode2.rs:6:20 + | +6 | let encoding = ::ENCODING; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Encode` is not general enough + | + = note: `Encode` would have to be implemented for the type `for<'r> extern "C" fn(&'r i32)` + = note: ...but `Encode` is actually implemented for the type `extern "C" fn(&'0 i32)`, for some specific lifetime `'0` diff --git a/tests/ui/fn_ptr_reference_method.rs b/tests/ui/fn_ptr_reference_method.rs new file mode 100644 index 000000000..fea09732a --- /dev/null +++ b/tests/ui/fn_ptr_reference_method.rs @@ -0,0 +1,27 @@ +//! Test how `MethodImplementation` is implemented regarding lifetimes in +//! function pointers and whether they're higher-ranked over them. +//! +//! Ideally it should work for all of these, but it can't be right now. +//! +//! (`_` can be used to work around this, by letting the compiler choose an +//! appropriate lifetime '0 that the trait is implemented for). +use objc2::{class, sel}; +use objc2::declare::ClassBuilder; +use objc2::runtime::{Object, Sel}; + +extern "C" fn my_fn(_this: &Object, _cmd: Sel, _x: &Object) {} + +fn main() { + let builder = ClassBuilder::new("SomeTestClass", class!(NSObject)).unwrap(); + unsafe { + // Works + builder.add_method(sel!(first:), my_fn as extern "C" fn(&Object, _, _)); + + // Fails + builder.add_method(sel!(none:), my_fn as extern "C" fn(_, _, _)); + builder.add_method(sel!(third:), my_fn as extern "C" fn(_, _, &Object)); + + // Also fails, properly tested in `fn_ptr_reference_method2` + builder.add_method(sel!(both:), my_fn as extern "C" fn(&Object, _, &Object)); + } +} diff --git a/tests/ui/fn_ptr_reference_method.stderr b/tests/ui/fn_ptr_reference_method.stderr new file mode 100644 index 000000000..ac1f11291 --- /dev/null +++ b/tests/ui/fn_ptr_reference_method.stderr @@ -0,0 +1,47 @@ +error[E0277]: the trait bound `extern "C" fn(_, _, _): MethodImplementation` is not satisfied + --> ui/fn_ptr_reference_method.rs:21:41 + | +21 | builder.add_method(sel!(none:), my_fn as extern "C" fn(_, _, _)); + | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `MethodImplementation` is not implemented for `extern "C" fn(_, _, _)` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `MethodImplementation`: + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D, E) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D, E, F) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D, E, F, G) -> R + and 70 others +note: required by a bound in `ClassBuilder::add_method` + --> $WORKSPACE/objc2/src/declare.rs + | + | F: MethodImplementation, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ClassBuilder::add_method` + +error[E0277]: the trait bound `for<'r> extern "C" fn(_, _, &'r objc2::runtime::Object): MethodImplementation` is not satisfied + --> ui/fn_ptr_reference_method.rs:22:42 + | +22 | builder.add_method(sel!(third:), my_fn as extern "C" fn(_, _, &Object)); + | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `MethodImplementation` is not implemented for `for<'r> extern "C" fn(_, _, &'r objc2::runtime::Object)` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `MethodImplementation`: + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D, E) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D, E, F) -> R + for<'r> extern "C" fn(&'r T, objc2::runtime::Sel, A, B, C, D, E, F, G) -> R + and 70 others +note: required by a bound in `ClassBuilder::add_method` + --> $WORKSPACE/objc2/src/declare.rs + | + | F: MethodImplementation, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ClassBuilder::add_method` diff --git a/tests/ui/fn_ptr_reference_method2.rs b/tests/ui/fn_ptr_reference_method2.rs new file mode 100644 index 000000000..4142b8beb --- /dev/null +++ b/tests/ui/fn_ptr_reference_method2.rs @@ -0,0 +1,14 @@ +//! Extra test for `fn_ptr_reference_method` +//! (They fail at different compilation passes). +use objc2::{class, sel}; +use objc2::declare::ClassBuilder; +use objc2::runtime::{Object, Sel}; + +extern "C" fn my_fn(_this: &Object, _cmd: Sel, _x: &Object) {} + +fn main() { + let builder = ClassBuilder::new("SomeTestClass", class!(NSObject)).unwrap(); + unsafe { + builder.add_method(sel!(both:), my_fn as extern "C" fn(&Object, Sel, &Object)); + } +} diff --git a/tests/ui/fn_ptr_reference_method2.stderr b/tests/ui/fn_ptr_reference_method2.stderr new file mode 100644 index 000000000..5b39229d4 --- /dev/null +++ b/tests/ui/fn_ptr_reference_method2.stderr @@ -0,0 +1,8 @@ +error: implementation of `MethodImplementation` is not general enough + --> ui/fn_ptr_reference_method2.rs:12:17 + | +12 | builder.add_method(sel!(both:), my_fn as extern "C" fn(&Object, Sel, &Object)); + | ^^^^^^^^^^ implementation of `MethodImplementation` is not general enough + | + = note: `MethodImplementation` would have to be implemented for the type `for<'r, 's> extern "C" fn(&'r objc2::runtime::Object, objc2::runtime::Sel, &'s objc2::runtime::Object)` + = note: ...but `MethodImplementation` is actually implemented for the type `for<'r> extern "C" fn(&'r objc2::runtime::Object, objc2::runtime::Sel, &'0 objc2::runtime::Object)`, for some specific lifetime `'0`