diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 291aeb0125a..3795cd408cc 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -2293,7 +2293,21 @@ impl<'a> PyClassImplsBuilder<'a> { } }); + let assertions = if attr.options.unsendable.is_some() { + TokenStream::new() + } else { + quote_spanned! { + cls.span() => + const _: () = { + use #pyo3_path::impl_::pyclass::*; + assert_pyclass_sync::<#cls, { IsSync::<#cls>::VALUE }>(); + }; + } + }; + Ok(quote! { + #assertions + #pyclass_base_type_impl impl #pyo3_path::impl_::pyclass::PyClassImpl for #cls { diff --git a/src/coroutine.rs b/src/coroutine.rs index 82f5460f03e..29bbdcb8140 100644 --- a/src/coroutine.rs +++ b/src/coroutine.rs @@ -35,6 +35,10 @@ pub struct Coroutine { waker: Option>, } +// Safety: `Coroutine` is allowed to be `Sync` even though the future is not, +// because the future is polled with `&mut self` receiver +unsafe impl Sync for Coroutine {} + impl Coroutine { /// Wrap a future into a Python coroutine. /// diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index f116e608d2f..c548ed5b210 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -22,8 +22,13 @@ use std::{ thread, }; +mod assertions; mod lazy_type_object; +mod probes; + +pub use assertions::*; pub use lazy_type_object::LazyTypeObject; +pub use probes::*; /// Gets the offset of the dictionary from the start of the object in bytes. #[inline] @@ -1419,62 +1424,6 @@ impl> } } -/// Trait used to combine with zero-sized types to calculate at compile time -/// some property of a type. -/// -/// The trick uses the fact that an associated constant has higher priority -/// than a trait constant, so we can use the trait to define the false case. -/// -/// The true case is defined in the zero-sized type's impl block, which is -/// gated on some property like trait bound or only being implemented -/// for fixed concrete types. -pub trait Probe { - const VALUE: bool = false; -} - -macro_rules! probe { - ($name:ident) => { - pub struct $name(PhantomData); - impl Probe for $name {} - }; -} - -probe!(IsPyT); - -impl IsPyT> { - pub const VALUE: bool = true; -} - -probe!(IsToPyObject); - -impl IsToPyObject { - pub const VALUE: bool = true; -} - -probe!(IsIntoPy); - -impl> IsIntoPy { - pub const VALUE: bool = true; -} - -probe!(IsIntoPyObjectRef); - -impl<'a, 'py, T: 'a> IsIntoPyObjectRef -where - &'a T: IntoPyObject<'py>, -{ - pub const VALUE: bool = true; -} - -probe!(IsIntoPyObject); - -impl<'py, T> IsIntoPyObject -where - T: IntoPyObject<'py>, -{ - pub const VALUE: bool = true; -} - /// ensures `obj` is not mutably aliased #[inline] unsafe fn ensure_no_mutable_alias<'py, ClassT: PyClass>( diff --git a/src/impl_/pyclass/assertions.rs b/src/impl_/pyclass/assertions.rs new file mode 100644 index 00000000000..25b3aa2815d --- /dev/null +++ b/src/impl_/pyclass/assertions.rs @@ -0,0 +1,40 @@ +/// Helper function that can be used at compile time to emit a diagnostic if +/// the type does not implement `Sync` when it should. +/// +/// The mere act of invoking this function will cause the diagnostic to be +/// emitted if `T` does not implement `Sync` when it should. +/// +/// The additional `const IS_SYNC: bool` parameter is used to allow the custom +/// diagnostic to be emitted; if `PyClassSync` +pub const fn assert_pyclass_sync() +where + T: PyClassSync + Sync, +{ +} + +#[cfg_attr( + diagnostic_namespace, + diagnostic::on_unimplemented( + message = "the trait `Sync` is not implemented for `{Self}`", + label = "needs to implement `Sync` to be `#[pyclass]`", + note = "to opt-out of threading support, use `#[pyclass(unsendable)]`", + note = "see for more information", + ) +)] +pub trait PyClassSync: private::Sealed {} + +mod private { + pub trait Sealed {} + impl Sealed for T {} + #[cfg(not(diagnostic_namespace))] + impl Sealed for T {} +} + +// If `true` is passed for the const parameter, then the diagnostic will +// not be emitted. +impl PyClassSync for T {} + +// Without `diagnostic_namespace`, the trait bound is not useful, so we add +// an implementation for `false`` to avoid a useless diagnostic. +#[cfg(not(diagnostic_namespace))] +impl PyClassSync for T {} diff --git a/src/impl_/pyclass/probes.rs b/src/impl_/pyclass/probes.rs new file mode 100644 index 00000000000..cbbbe8be0f1 --- /dev/null +++ b/src/impl_/pyclass/probes.rs @@ -0,0 +1,65 @@ +use std::marker::PhantomData; + +use crate::{conversion::IntoPyObject, IntoPy, Py, ToPyObject}; + +/// Trait used to combine with zero-sized types to calculate at compile time +/// some property of a type. +/// +/// The trick uses the fact that an associated constant has higher priority +/// than a trait constant, so we can use the trait to define the false case. +/// +/// The true case is defined in the zero-sized type's impl block, which is +/// gated on some property like trait bound or only being implemented +/// for fixed concrete types. +pub trait Probe { + const VALUE: bool = false; +} + +macro_rules! probe { + ($name:ident) => { + pub struct $name(PhantomData); + impl Probe for $name {} + }; +} + +probe!(IsPyT); + +impl IsPyT> { + pub const VALUE: bool = true; +} + +probe!(IsToPyObject); + +impl IsToPyObject { + pub const VALUE: bool = true; +} + +probe!(IsIntoPy); + +impl> IsIntoPy { + pub const VALUE: bool = true; +} + +probe!(IsIntoPyObjectRef); + +impl<'a, 'py, T: 'a> IsIntoPyObjectRef +where + &'a T: IntoPyObject<'py>, +{ + pub const VALUE: bool = true; +} + +probe!(IsIntoPyObject); + +impl<'py, T> IsIntoPyObject +where + T: IntoPyObject<'py>, +{ + pub const VALUE: bool = true; +} + +probe!(IsSync); + +impl IsSync { + pub const VALUE: bool = true; +} diff --git a/tests/ui/pyclass_send.rs b/tests/ui/pyclass_send.rs index a587c071f51..5faded9874f 100644 --- a/tests/ui/pyclass_send.rs +++ b/tests/ui/pyclass_send.rs @@ -1,26 +1,28 @@ use pyo3::prelude::*; -use std::rc::Rc; +use std::os::raw::c_void; #[pyclass] -struct NotThreadSafe { - data: Rc, -} - -fn main() { - let obj = Python::with_gil(|py| { - Bound::new(py, NotThreadSafe { data: Rc::new(5) }) - .unwrap() - .unbind() - }); - - std::thread::spawn(move || { - Python::with_gil(|py| { - // Uh oh, moved Rc to a new thread! - let c = obj.bind(py).downcast::().unwrap(); - - assert_eq!(*c.borrow().data, 5); - }) - }) - .join() - .unwrap(); -} +struct NotSyncNotSend(*mut c_void); + +#[pyclass] +struct SendNotSync(*mut c_void); +unsafe impl Send for SendNotSync {} + +#[pyclass] +struct SyncNotSend(*mut c_void); +unsafe impl Sync for SyncNotSend {} + +// None of the `unsendable` forms below should fail to compile + +#[pyclass(unsendable)] +struct NotSyncNotSendUnsendable(*mut c_void); + +#[pyclass(unsendable)] +struct SendNotSyncUnsendable(*mut c_void); +unsafe impl Send for SendNotSyncUnsendable {} + +#[pyclass(unsendable)] +struct SyncNotSendUnsendable(*mut c_void); +unsafe impl Sync for SyncNotSendUnsendable {} + +fn main() {} diff --git a/tests/ui/pyclass_send.stderr b/tests/ui/pyclass_send.stderr index 0db9106ab42..42a640d1ad8 100644 --- a/tests/ui/pyclass_send.stderr +++ b/tests/ui/pyclass_send.stderr @@ -1,17 +1,56 @@ -error[E0277]: `Rc` cannot be sent between threads safely +error[E0277]: the trait `Sync` is not implemented for `NotSyncNotSend` + --> tests/ui/pyclass_send.rs:5:8 + | +5 | struct NotSyncNotSend(*mut c_void); + | ^^^^^^^^^^^^^^ needs to implement `Sync` to be `#[pyclass]` + | + = help: the trait `PyClassSync` is not implemented for `NotSyncNotSend` + = note: to opt-out of threading support, use `#[pyclass(unsendable)]` + = note: see for more information +note: required by a bound in `pyo3::impl_::pyclass::assertions::assert_pyclass_sync` + --> src/impl_/pyclass/assertions.rs + | + | pub const fn assert_pyclass_sync() + | ------------------- required by a bound in this function + | where + | T: PyClassSync + Sync, + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_pyclass_sync` + +error[E0277]: `*mut c_void` cannot be shared between threads safely + --> tests/ui/pyclass_send.rs:5:8 + | +5 | struct NotSyncNotSend(*mut c_void); + | ^^^^^^^^^^^^^^ `*mut c_void` cannot be shared between threads safely + | + = help: within `NotSyncNotSend`, the trait `Sync` is not implemented for `*mut c_void`, which is required by `NotSyncNotSend: Sync` +note: required because it appears within the type `NotSyncNotSend` + --> tests/ui/pyclass_send.rs:5:8 + | +5 | struct NotSyncNotSend(*mut c_void); + | ^^^^^^^^^^^^^^ +note: required by a bound in `pyo3::impl_::pyclass::assertions::assert_pyclass_sync` + --> src/impl_/pyclass/assertions.rs + | + | pub const fn assert_pyclass_sync() + | ------------------- required by a bound in this function + | where + | T: PyClassSync + Sync, + | ^^^^ required by this bound in `assert_pyclass_sync` + +error[E0277]: `*mut c_void` cannot be sent between threads safely --> tests/ui/pyclass_send.rs:4:1 | 4 | #[pyclass] - | ^^^^^^^^^^ `Rc` cannot be sent between threads safely + | ^^^^^^^^^^ `*mut c_void` cannot be sent between threads safely | - = help: within `NotThreadSafe`, the trait `Send` is not implemented for `Rc`, which is required by `SendablePyClass: pyo3::impl_::pyclass::PyClassThreadChecker` + = help: within `NotSyncNotSend`, the trait `Send` is not implemented for `*mut c_void`, which is required by `SendablePyClass: pyo3::impl_::pyclass::PyClassThreadChecker` = help: the trait `pyo3::impl_::pyclass::PyClassThreadChecker` is implemented for `SendablePyClass` -note: required because it appears within the type `NotThreadSafe` +note: required because it appears within the type `NotSyncNotSend` --> tests/ui/pyclass_send.rs:5:8 | -5 | struct NotThreadSafe { - | ^^^^^^^^^^^^^ - = note: required for `SendablePyClass` to implement `pyo3::impl_::pyclass::PyClassThreadChecker` +5 | struct NotSyncNotSend(*mut c_void); + | ^^^^^^^^^^^^^^ + = note: required for `SendablePyClass` to implement `pyo3::impl_::pyclass::PyClassThreadChecker` note: required by a bound in `PyClassImpl::ThreadChecker` --> src/impl_/pyclass.rs | @@ -19,21 +58,100 @@ note: required by a bound in `PyClassImpl::ThreadChecker` | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyClassImpl::ThreadChecker` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: `Rc` cannot be sent between threads safely +error[E0277]: the trait `Sync` is not implemented for `SendNotSync` + --> tests/ui/pyclass_send.rs:8:8 + | +8 | struct SendNotSync(*mut c_void); + | ^^^^^^^^^^^ needs to implement `Sync` to be `#[pyclass]` + | + = help: the trait `PyClassSync` is not implemented for `SendNotSync` + = note: to opt-out of threading support, use `#[pyclass(unsendable)]` + = note: see for more information +note: required by a bound in `pyo3::impl_::pyclass::assertions::assert_pyclass_sync` + --> src/impl_/pyclass/assertions.rs + | + | pub const fn assert_pyclass_sync() + | ------------------- required by a bound in this function + | where + | T: PyClassSync + Sync, + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_pyclass_sync` + +error[E0277]: `*mut c_void` cannot be shared between threads safely + --> tests/ui/pyclass_send.rs:8:8 + | +8 | struct SendNotSync(*mut c_void); + | ^^^^^^^^^^^ `*mut c_void` cannot be shared between threads safely + | + = help: within `SendNotSync`, the trait `Sync` is not implemented for `*mut c_void`, which is required by `SendNotSync: Sync` +note: required because it appears within the type `SendNotSync` + --> tests/ui/pyclass_send.rs:8:8 + | +8 | struct SendNotSync(*mut c_void); + | ^^^^^^^^^^^ +note: required by a bound in `pyo3::impl_::pyclass::assertions::assert_pyclass_sync` + --> src/impl_/pyclass/assertions.rs + | + | pub const fn assert_pyclass_sync() + | ------------------- required by a bound in this function + | where + | T: PyClassSync + Sync, + | ^^^^ required by this bound in `assert_pyclass_sync` + +error[E0277]: `*mut c_void` cannot be sent between threads safely + --> tests/ui/pyclass_send.rs:11:1 + | +11 | #[pyclass] + | ^^^^^^^^^^ `*mut c_void` cannot be sent between threads safely + | + = help: within `SyncNotSend`, the trait `Send` is not implemented for `*mut c_void`, which is required by `SendablePyClass: pyo3::impl_::pyclass::PyClassThreadChecker` + = help: the trait `pyo3::impl_::pyclass::PyClassThreadChecker` is implemented for `SendablePyClass` +note: required because it appears within the type `SyncNotSend` + --> tests/ui/pyclass_send.rs:12:8 + | +12 | struct SyncNotSend(*mut c_void); + | ^^^^^^^^^^^ + = note: required for `SendablePyClass` to implement `pyo3::impl_::pyclass::PyClassThreadChecker` +note: required by a bound in `PyClassImpl::ThreadChecker` + --> src/impl_/pyclass.rs + | + | type ThreadChecker: PyClassThreadChecker; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyClassImpl::ThreadChecker` + = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `*mut c_void` cannot be sent between threads safely --> tests/ui/pyclass_send.rs:4:1 | 4 | #[pyclass] - | ^^^^^^^^^^ `Rc` cannot be sent between threads safely + | ^^^^^^^^^^ `*mut c_void` cannot be sent between threads safely | - = help: within `NotThreadSafe`, the trait `Send` is not implemented for `Rc`, which is required by `NotThreadSafe: Send` -note: required because it appears within the type `NotThreadSafe` + = help: within `NotSyncNotSend`, the trait `Send` is not implemented for `*mut c_void`, which is required by `NotSyncNotSend: Send` +note: required because it appears within the type `NotSyncNotSend` --> tests/ui/pyclass_send.rs:5:8 | -5 | struct NotThreadSafe { - | ^^^^^^^^^^^^^ +5 | struct NotSyncNotSend(*mut c_void); + | ^^^^^^^^^^^^^^ note: required by a bound in `SendablePyClass` --> src/impl_/pyclass.rs | | pub struct SendablePyClass(PhantomData); | ^^^^ required by this bound in `SendablePyClass` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `*mut c_void` cannot be sent between threads safely + --> tests/ui/pyclass_send.rs:11:1 + | +11 | #[pyclass] + | ^^^^^^^^^^ `*mut c_void` cannot be sent between threads safely + | + = help: within `SyncNotSend`, the trait `Send` is not implemented for `*mut c_void`, which is required by `SyncNotSend: Send` +note: required because it appears within the type `SyncNotSend` + --> tests/ui/pyclass_send.rs:12:8 + | +12 | struct SyncNotSend(*mut c_void); + | ^^^^^^^^^^^ +note: required by a bound in `SendablePyClass` + --> src/impl_/pyclass.rs + | + | pub struct SendablePyClass(PhantomData); + | ^^^^ required by this bound in `SendablePyClass` + = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)