diff --git a/crates/objc2/src/__macro_helpers/declare_class.rs b/crates/objc2/src/__macro_helpers/declare_class.rs index 14b6c94c9..371fb63c4 100644 --- a/crates/objc2/src/__macro_helpers/declare_class.rs +++ b/crates/objc2/src/__macro_helpers/declare_class.rs @@ -3,9 +3,10 @@ use core::ptr; use crate::declare::__IdReturnValue; use crate::rc::{Allocated, Id}; -use crate::{Message, MessageReceiver}; +use crate::{ClassType, Message, MessageReceiver}; use super::{CopyOrMutCopy, Init, MaybeUnwrap, New, Other}; +use crate::mutability; // One could imagine a different design where we simply had a method like // `fn convert_receiver()`, but that won't work in `declare_class!` since we @@ -110,3 +111,70 @@ impl MaybeOptionId for Option> { __IdReturnValue(ptr.cast()) } } + +/// Helper for ensuring that `ClassType::Mutability` is implemented correctly +/// for subclasses. +pub trait ValidSubclassMutability {} + +// Root +impl ValidSubclassMutability for mutability::Root {} +impl ValidSubclassMutability for mutability::Root {} +impl ValidSubclassMutability> + for mutability::Root +where + MS: ?Sized + ClassType>, + IS: ?Sized + ClassType>, +{ +} +impl ValidSubclassMutability for mutability::Root {} +impl ValidSubclassMutability for mutability::Root {} + +// Immutable +impl ValidSubclassMutability for mutability::Immutable {} + +// Mutable +impl ValidSubclassMutability for mutability::Mutable {} + +// ImmutableWithMutableSubclass +impl ValidSubclassMutability> + for mutability::ImmutableWithMutableSubclass +where + MS: ?Sized + ClassType>, + IS: ?Sized + ClassType>, +{ +} +// Only valid when `NSCopying`/`NSMutableCopying` is not implemented! +impl ValidSubclassMutability + for mutability::ImmutableWithMutableSubclass +{ +} + +// MutableWithImmutableSuperclass +// Only valid when `NSCopying`/`NSMutableCopying` is not implemented! +impl ValidSubclassMutability + for mutability::MutableWithImmutableSuperclass +{ +} + +// InteriorMutable +impl ValidSubclassMutability for mutability::InteriorMutable {} +impl ValidSubclassMutability for mutability::InteriorMutable {} + +// MainThreadOnly +impl ValidSubclassMutability for mutability::MainThreadOnly {} + +/// Ensure that: +/// 1. The type is not a root class (it's superclass implements `ClassType`, +/// and it's mutability is not `Root`), and therefore also implements basic +/// memory management methods, as required by `unsafe impl Message`. +/// 2. The mutability is valid according to the superclass' mutability. +#[inline] +pub fn assert_mutability_matches_superclass_mutability() +where + T: ?Sized + ClassType, + T::Super: ClassType, + T::Mutability: mutability::Mutability, + ::Mutability: ValidSubclassMutability, +{ + // Noop +} diff --git a/crates/objc2/src/__macro_helpers/mod.rs b/crates/objc2/src/__macro_helpers/mod.rs index a772a2904..0cda0d4ce 100644 --- a/crates/objc2/src/__macro_helpers/mod.rs +++ b/crates/objc2/src/__macro_helpers/mod.rs @@ -32,7 +32,10 @@ mod cache; mod declare_class; pub use self::cache::{CachedClass, CachedSel}; -pub use self::declare_class::{MaybeOptionId, MessageRecieveId}; +pub use self::declare_class::{ + assert_mutability_matches_superclass_mutability, MaybeOptionId, MessageRecieveId, + ValidSubclassMutability, +}; // Common selectors. // diff --git a/crates/objc2/src/macros/declare_class.rs b/crates/objc2/src/macros/declare_class.rs index 0d674a077..5133993ad 100644 --- a/crates/objc2/src/macros/declare_class.rs +++ b/crates/objc2/src/macros/declare_class.rs @@ -533,6 +533,8 @@ macro_rules! __inner_declare_class { const NAME: &'static $crate::__macro_helpers::str = $name_const; fn class() -> &'static $crate::runtime::Class { + $crate::__macro_helpers::assert_mutability_matches_superclass_mutability::(); + // TODO: Use `core::cell::LazyCell` static REGISTER_CLASS: $crate::__macro_helpers::Once = $crate::__macro_helpers::Once::new(); diff --git a/crates/objc2/src/macros/extern_class.rs b/crates/objc2/src/macros/extern_class.rs index 62cb9f9ac..4a98b219b 100644 --- a/crates/objc2/src/macros/extern_class.rs +++ b/crates/objc2/src/macros/extern_class.rs @@ -359,6 +359,8 @@ macro_rules! __inner_extern_class { #[inline] fn class() -> &'static $crate::runtime::Class { + $crate::__macro_helpers::assert_mutability_matches_superclass_mutability::(); + $crate::__class_inner!( $crate::__select_name!($name; $($name_const)?), $crate::__hash_idents!($name), @@ -410,8 +412,11 @@ macro_rules! __extern_class_impl_traits { // (we even ensure that `Object` is always last in our inheritance // tree), so it is always safe to reinterpret as that. // - // That the object must work with standard memory management is upheld - // by the caller. + // That the object must work with standard memory management is + // properly upheld by the fact that the superclass is required to + // implement `ClassType`, and hence must be a subclass of one of + // `NSObject`, `NSProxy` or some other class that ensures this (e.g. + // the object itself is not a root class). $(#[$impl_m])* unsafe impl<$($t)*> $crate::Message for $for {} diff --git a/crates/test-ui/ui/extern_class_root.rs b/crates/test-ui/ui/extern_class_root.rs new file mode 100644 index 000000000..4b329fedd --- /dev/null +++ b/crates/test-ui/ui/extern_class_root.rs @@ -0,0 +1,39 @@ +use core::ops::{Deref, DerefMut}; + +use objc2::encode::{Encoding, RefEncode}; +use objc2::runtime::Object; +use objc2::{extern_class, mutability, ClassType, Message}; + +#[repr(transparent)] +struct MyObject(Object); + +unsafe impl RefEncode for MyObject { + const ENCODING_REF: Encoding = Encoding::Object; +} + +unsafe impl Message for MyObject {} + +impl Deref for MyObject { + type Target = Object; + + fn deref(&self) -> &Object { + &self.0 + } +} + +impl DerefMut for MyObject { + fn deref_mut(&mut self) -> &mut Object { + &mut self.0 + } +} + +extern_class!( + pub struct MyRootClass; + + unsafe impl ClassType for MyRootClass { + type Super = MyObject; + type Mutability = mutability::InteriorMutable; + } +); + +fn main() {} diff --git a/crates/test-ui/ui/extern_class_root.stderr b/crates/test-ui/ui/extern_class_root.stderr new file mode 100644 index 000000000..08d99b345 --- /dev/null +++ b/crates/test-ui/ui/extern_class_root.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `MyObject: ClassType` is not satisfied + --> ui/extern_class_root.rs + | + | / extern_class!( + | | pub struct MyRootClass; + | | + | | unsafe impl ClassType for MyRootClass { +... | + | | } + | | ); + | |_^ the trait `ClassType` is not implemented for `MyObject` + | + = help: the following other types implement trait `ClassType`: + MyRootClass + NSObject + __NSProxy + __RcTestObject + = note: this error originates in the macro `$crate::__inner_extern_class` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/test-ui/ui/extern_class_subclass_object.rs b/crates/test-ui/ui/extern_class_subclass_object.rs new file mode 100644 index 000000000..74f3f8d3d --- /dev/null +++ b/crates/test-ui/ui/extern_class_subclass_object.rs @@ -0,0 +1,13 @@ +use objc2::runtime::Object; +use objc2::{extern_class, mutability, ClassType}; + +extern_class!( + pub struct MyRootClass; + + unsafe impl ClassType for MyRootClass { + type Super = Object; + type Mutability = mutability::InteriorMutable; + } +); + +fn main() {} diff --git a/crates/test-ui/ui/extern_class_subclass_object.stderr b/crates/test-ui/ui/extern_class_subclass_object.stderr new file mode 100644 index 000000000..80c06386c --- /dev/null +++ b/crates/test-ui/ui/extern_class_subclass_object.stderr @@ -0,0 +1,67 @@ +error[E0119]: conflicting implementations of trait `AsRef` for type `MyRootClass` + --> ui/extern_class_subclass_object.rs + | + | / extern_class!( + | | pub struct MyRootClass; + | | + | | unsafe impl ClassType for MyRootClass { +... | + | | } + | | ); + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `MyRootClass` + | + = note: this error originates in the macro `$crate::__impl_as_ref_borrow` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0119]: conflicting implementations of trait `AsMut` for type `MyRootClass` + --> ui/extern_class_subclass_object.rs + | + | / extern_class!( + | | pub struct MyRootClass; + | | + | | unsafe impl ClassType for MyRootClass { +... | + | | } + | | ); + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `MyRootClass` + | + = note: this error originates in the macro `$crate::__impl_as_ref_borrow` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0119]: conflicting implementations of trait `Borrow` for type `MyRootClass` + --> ui/extern_class_subclass_object.rs + | + | / extern_class!( + | | pub struct MyRootClass; + | | + | | unsafe impl ClassType for MyRootClass { +... | + | | } + | | ); + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `MyRootClass` + | + = note: this error originates in the macro `$crate::__impl_as_ref_borrow` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0119]: conflicting implementations of trait `BorrowMut` for type `MyRootClass` + --> ui/extern_class_subclass_object.rs + | + | / extern_class!( + | | pub struct MyRootClass; + | | + | | unsafe impl ClassType for MyRootClass { +... | + | | } + | | ); + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `MyRootClass` + | + = note: this error originates in the macro `$crate::__impl_as_ref_borrow` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/test-ui/ui/extern_class_wrong_mutability.rs b/crates/test-ui/ui/extern_class_wrong_mutability.rs new file mode 100644 index 000000000..51ad94d8f --- /dev/null +++ b/crates/test-ui/ui/extern_class_wrong_mutability.rs @@ -0,0 +1,58 @@ +use objc2::runtime::NSObject; +use objc2::{extern_class, mutability, ClassType}; + +extern_class!( + pub struct MyMainThreadClass; + + unsafe impl ClassType for MyMainThreadClass { + type Super = NSObject; + type Mutability = mutability::MainThreadOnly; + } +); + +extern_class!( + pub struct MyAnyThreadClass; + + unsafe impl ClassType for MyAnyThreadClass { + type Super = MyMainThreadClass; + type Mutability = mutability::InteriorMutable; + } +); + +extern_class!( + pub struct MyImmutableClass1; + + unsafe impl ClassType for MyImmutableClass1 { + type Super = NSObject; + type Mutability = mutability::ImmutableWithMutableSubclass; + } +); + +extern_class!( + pub struct MyMutableClass1; + + unsafe impl ClassType for MyMutableClass1 { + type Super = MyImmutableClass1; + type Mutability = mutability::MutableWithImmutableSuperclass; + } +); + +extern_class!( + pub struct MyImmutableClass2; + + unsafe impl ClassType for MyImmutableClass2 { + type Super = NSObject; + type Mutability = mutability::ImmutableWithMutableSubclass; + } +); + +extern_class!( + pub struct MyMutableClass2; + + unsafe impl ClassType for MyMutableClass2 { + type Super = MyImmutableClass2; + type Mutability = mutability::MutableWithImmutableSuperclass; + } +); + +fn main() {} diff --git a/crates/test-ui/ui/extern_class_wrong_mutability.stderr b/crates/test-ui/ui/extern_class_wrong_mutability.stderr new file mode 100644 index 000000000..7636783fa --- /dev/null +++ b/crates/test-ui/ui/extern_class_wrong_mutability.stderr @@ -0,0 +1,107 @@ +error[E0277]: the trait bound `MainThreadOnly: ValidSubclassMutability` is not satisfied + --> ui/extern_class_wrong_mutability.rs + | + | / extern_class!( + | | pub struct MyAnyThreadClass; + | | + | | unsafe impl ClassType for MyAnyThreadClass { +... | + | | } + | | ); + | |_^ the trait `ValidSubclassMutability` is not implemented for `MainThreadOnly` + | + = help: the trait `ValidSubclassMutability` is implemented for `MainThreadOnly` +note: required by a bound in `assert_mutability_matches_superclass_mutability` + --> $WORKSPACE/crates/objc2/src/__macro_helpers/declare_class.rs + | + | ::Mutability: ValidSubclassMutability, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_mutability_matches_superclass_mutability` + = note: this error originates in the macro `$crate::__inner_extern_class` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0271]: type mismatch resolving `::Mutability == ImmutableWithMutableSubclass` + --> ui/extern_class_wrong_mutability.rs + | + | / extern_class!( + | | pub struct MyImmutableClass1; + | | + | | unsafe impl ClassType for MyImmutableClass1 { +... | + | | } + | | ); + | |_^ expected `ImmutableWithMutableSubclass`, found `Root` + | + = note: expected struct `ImmutableWithMutableSubclass` + found struct `Root` + = note: required for `Root` to implement `ValidSubclassMutability>` +note: required by a bound in `assert_mutability_matches_superclass_mutability` + --> $WORKSPACE/crates/objc2/src/__macro_helpers/declare_class.rs + | + | ::Mutability: ValidSubclassMutability, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_mutability_matches_superclass_mutability` + = note: this error originates in the macro `$crate::__inner_extern_class` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0271]: type mismatch resolving `::Mutability == ImmutableWithMutableSubclass` + --> ui/extern_class_wrong_mutability.rs + | + | / extern_class!( + | | pub struct MyMutableClass1; + | | + | | unsafe impl ClassType for MyMutableClass1 { +... | + | | } + | | ); + | |_^ expected `ImmutableWithMutableSubclass`, found `Root` + | + = note: expected struct `ImmutableWithMutableSubclass` + found struct `Root` + = note: required for `ImmutableWithMutableSubclass` to implement `ValidSubclassMutability>` +note: required by a bound in `assert_mutability_matches_superclass_mutability` + --> $WORKSPACE/crates/objc2/src/__macro_helpers/declare_class.rs + | + | ::Mutability: ValidSubclassMutability, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_mutability_matches_superclass_mutability` + = note: this error originates in the macro `$crate::__inner_extern_class` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0271]: type mismatch resolving `::Mutability == MutableWithImmutableSuperclass<_>` + --> ui/extern_class_wrong_mutability.rs + | + | / extern_class!( + | | pub struct MyImmutableClass2; + | | + | | unsafe impl ClassType for MyImmutableClass2 { +... | + | | } + | | ); + | |_^ expected `MutableWithImmutableSuperclass<_>`, found `Root` + | + = note: expected struct `MutableWithImmutableSuperclass<_>` + found struct `Root` + = note: required for `Root` to implement `ValidSubclassMutability>` +note: required by a bound in `assert_mutability_matches_superclass_mutability` + --> $WORKSPACE/crates/objc2/src/__macro_helpers/declare_class.rs + | + | ::Mutability: ValidSubclassMutability, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_mutability_matches_superclass_mutability` + = note: this error originates in the macro `$crate::__inner_extern_class` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0271]: type mismatch resolving `::Mutability == MutableWithImmutableSuperclass` + --> ui/extern_class_wrong_mutability.rs + | + | / extern_class!( + | | pub struct MyMutableClass2; + | | + | | unsafe impl ClassType for MyMutableClass2 { +... | + | | } + | | ); + | |_^ expected `MutableWithImmutableSuperclass`, found `Root` + | + = note: expected struct `MutableWithImmutableSuperclass` + found struct `Root` + = note: required for `ImmutableWithMutableSubclass` to implement `ValidSubclassMutability>` +note: required by a bound in `assert_mutability_matches_superclass_mutability` + --> $WORKSPACE/crates/objc2/src/__macro_helpers/declare_class.rs + | + | ::Mutability: ValidSubclassMutability, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_mutability_matches_superclass_mutability` + = note: this error originates in the macro `$crate::__inner_extern_class` which comes from the expansion of the macro `extern_class` (in Nightly builds, run with -Z macro-backtrace for more info)