-
Notifications
You must be signed in to change notification settings - Fork 791
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
require #[pyclass]
to be Sync
#4566
Changes from 13 commits
2f9ebf3
bbc2049
799a7a9
9d9c2e5
862b882
0b8d5fa
245300b
a7381a0
9e50a84
eedd5b8
dc8b7e7
4a26752
d69df5e
d543ed8
ba09a08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
* The `pyclass` macro now creates a rust type that is `Sync` by default. If you | ||
would like to opt out of this, annotate your class with | ||
`pyclass(unsendable)`. See the migraiton guide entry (INSERT GUIDE LINK HERE) | ||
for more information on updating to accommadate this change. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,10 @@ pub struct Coroutine { | |
waker: Option<Arc<AsyncioWaker>>, | ||
} | ||
|
||
// 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 {} | ||
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the same logic that Exclusive uses? If so, I would prefer that we use that (or our own version of it) rather than this impl. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I marked this ready for review, but I think this hasn't been looked at yet. I'll leave that to David since I'm not sure if using |
||
|
||
impl Coroutine { | ||
/// Wrap a future into a Python coroutine. | ||
/// | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/// 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<T, const IS_SYNC: bool>() | ||
where | ||
T: PyClassSync<IS_SYNC> + Sync, | ||
{ | ||
} | ||
|
||
#[cfg_attr( | ||
diagnostic_namespace, | ||
diagnostic::on_unimplemented( | ||
message = "the trait `Sync` is not implemented for `{Self}`", | ||
label = "required by `#[pyclass]`", | ||
note = "replace thread-unsafe fields with thread-safe alternatives", | ||
note = "see <TODO INSERT PYO3 GUIDE> for more information", | ||
) | ||
)] | ||
pub trait PyClassSync<const IS_SYNC: bool>: private::Sealed<IS_SYNC> {} | ||
|
||
mod private { | ||
pub trait Sealed<const IS_SYNC: bool> {} | ||
impl<T> Sealed<true> for T {} | ||
#[cfg(not(diagnostic_namespace))] | ||
impl<T> Sealed<false> for T {} | ||
} | ||
|
||
// If `true` is passed for the const parameter, then the diagnostic will | ||
// not be emitted. | ||
impl<T> PyClassSync<true> 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<T> PyClassSync<false> for T {} | ||
|
||
mod tests { | ||
#[cfg(feature = "macros")] | ||
#[test] | ||
fn test_assert_pyclass_sync() { | ||
use super::assert_pyclass_sync; | ||
|
||
#[crate::pyclass(crate = "crate")] | ||
struct MyClass {} | ||
assert_pyclass_sync::<MyClass, true>(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use std::marker::PhantomData; | ||
|
||
use crate::{conversion::IntoPyObject, Py}; | ||
#[allow(deprecated)] | ||
use crate::{IntoPy, 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<T>(PhantomData<T>); | ||
impl<T> Probe for $name<T> {} | ||
}; | ||
} | ||
|
||
probe!(IsPyT); | ||
|
||
impl<T> IsPyT<Py<T>> { | ||
pub const VALUE: bool = true; | ||
} | ||
|
||
probe!(IsToPyObject); | ||
|
||
#[allow(deprecated)] | ||
impl<T: ToPyObject> IsToPyObject<T> { | ||
pub const VALUE: bool = true; | ||
} | ||
|
||
probe!(IsIntoPy); | ||
|
||
#[allow(deprecated)] | ||
impl<T: IntoPy<crate::PyObject>> IsIntoPy<T> { | ||
pub const VALUE: bool = true; | ||
} | ||
|
||
probe!(IsIntoPyObjectRef); | ||
|
||
// Possible clippy beta regression, | ||
// see https://github.com/rust-lang/rust-clippy/issues/13578 | ||
#[allow(clippy::extra_unused_lifetimes)] | ||
impl<'a, 'py, T: 'a> IsIntoPyObjectRef<T> | ||
where | ||
&'a T: IntoPyObject<'py>, | ||
{ | ||
pub const VALUE: bool = true; | ||
} | ||
|
||
probe!(IsIntoPyObject); | ||
|
||
impl<'py, T> IsIntoPyObject<T> | ||
where | ||
T: IntoPyObject<'py>, | ||
{ | ||
pub const VALUE: bool = true; | ||
} | ||
|
||
probe!(IsSync); | ||
|
||
impl<T: Sync> IsSync<T> { | ||
pub const VALUE: bool = true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please update (or maybe delete) this line and the discussion it references? I'm fine with docs being added in followup PRs but it's bad form to leave docs in a wrong (rather than just incomplete or missing) state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, thanks for pointing out that I missed that.