Skip to content

Commit c7c3c53

Browse files
authored
emit deprecation on pyclasses that are affected by the FromPyObject blanket impl (#5550)
1 parent 5589b17 commit c7c3c53

File tree

21 files changed

+102
-37
lines changed

21 files changed

+102
-37
lines changed

guide/src/class.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ Classes can also be passed by value if they can be cloned, i.e. they automatical
893893
```rust,no_run
894894
# #![allow(dead_code)]
895895
# use pyo3::prelude::*;
896-
#[pyclass]
896+
#[pyclass(from_py_object)]
897897
#[derive(Clone)]
898898
struct MyClass {
899899
my_field: Box<i32>,

guide/src/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ You may have a nested struct similar to this:
9999

100100
```rust,no_run
101101
# use pyo3::prelude::*;
102-
#[pyclass]
102+
#[pyclass(from_py_object)]
103103
#[derive(Clone)]
104104
struct Inner {/* fields omitted */}
105105

guide/src/migration.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
44
For a detailed list of all changes, see the [CHANGELOG](changelog.md).
55

6+
## from 0.27.* to 0.28
7+
8+
### Deprecation of automatic `FromPyObject` for `#[pyclass]` types which implement `Clone`
9+
10+
`#[pyclass]` types which implement `Clone` used to also implement `FromPyObject` automatically.
11+
This behaviour is phased out and replaced by an explicit opt-in.
12+
Affected types will by marked by a deprecation message.
13+
To migrate use either
14+
15+
- `from_py_object` to keep the automatic derive, or
16+
- `skip_from_py_object` to accept the new behaviour
17+
618
## from 0.26.* to 0.27
719

820
### `FromPyObject` reworked for flexibility and efficiency

newsfragments/5550.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
deprecate implicit by value extraction of pyclasses

pyo3-macros-backend/src/pyclass.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2511,6 +2511,20 @@ impl<'a> PyClassImplsBuilder<'a> {
25112511
}
25122512
};
25132513

2514+
let deprecation = if self.attr.options.skip_from_py_object.is_none()
2515+
&& self.attr.options.from_py_object.is_none()
2516+
{
2517+
quote! {
2518+
const _: () = {
2519+
#[allow(unused_import)]
2520+
use #pyo3_path::impl_::pyclass::Probe as _;
2521+
#pyo3_path::impl_::deprecated::HasAutomaticFromPyObject::<{ #pyo3_path::impl_::pyclass::IsClone::<#cls>::VALUE }>::MSG
2522+
};
2523+
}
2524+
} else {
2525+
TokenStream::new()
2526+
};
2527+
25142528
let extract_pyclass_with_clone = if let Some(from_py_object) =
25152529
self.attr.options.from_py_object
25162530
{
@@ -2540,6 +2554,8 @@ impl<'a> PyClassImplsBuilder<'a> {
25402554
};
25412555

25422556
Ok(quote! {
2557+
#deprecation
2558+
25432559
#extract_pyclass_with_clone
25442560

25452561
#assertions

pytests/src/pyclasses.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use pyo3::exceptions::{PyStopIteration, PyValueError};
44
use pyo3::prelude::*;
55
use pyo3::types::PyType;
66

7-
#[pyclass]
7+
#[pyclass(from_py_object)]
88
#[derive(Clone, Default)]
99
struct EmptyClass {}
1010

@@ -70,7 +70,7 @@ impl PyClassThreadIter {
7070
}
7171

7272
/// Demonstrates a base class which can operate on the relevant subclass in its constructor.
73-
#[pyclass(subclass)]
73+
#[pyclass(subclass, skip_from_py_object)]
7474
#[derive(Clone, Debug)]
7575
struct AssertingBaseClass;
7676

@@ -104,7 +104,7 @@ impl ClassWithDict {
104104
}
105105
}
106106

107-
#[pyclass]
107+
#[pyclass(skip_from_py_object)]
108108
#[derive(Clone)]
109109
struct ClassWithDecorators {
110110
attr: usize,

src/impl_.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod callback;
1010
pub mod concat;
1111
#[cfg(feature = "experimental-async")]
1212
pub mod coroutine;
13+
pub mod deprecated;
1314
pub mod exceptions;
1415
pub mod extract_argument;
1516
pub mod freelist;

src/impl_/deprecated.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pub struct HasAutomaticFromPyObject<const IS_CLONE: bool> {}
2+
3+
impl HasAutomaticFromPyObject<true> {
4+
#[deprecated(
5+
since = "0.28.0",
6+
note = "The automatically derived `FromPyObject` implementation for `#[pyclass]` types which implement `Clone` is being phased out. Use `from_py_object` to keep the automatic derive or `skip_from_py_object` to accept the new behaviour."
7+
)]
8+
pub const MSG: () = ();
9+
}
10+
11+
impl HasAutomaticFromPyObject<false> {
12+
pub const MSG: () = ();
13+
}

src/impl_/pyclass/probes.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ impl<T: super::doc::PyClassNewTextSignature> HasNewTextSignature<T> {
7373
pub const VALUE: bool = true;
7474
}
7575

76+
probe!(IsClone);
77+
78+
impl<T: Clone> IsClone<T> {
79+
pub const VALUE: bool = true;
80+
}
81+
7682
#[cfg(test)]
7783
macro_rules! value_of {
7884
($probe:ident, $ty:ty) => {{

src/marker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ mod tests {
10071007
fn test_py_run_inserts_globals_2() {
10081008
use std::ffi::CString;
10091009

1010-
#[crate::pyclass(crate = "crate")]
1010+
#[crate::pyclass(crate = "crate", skip_from_py_object)]
10111011
#[derive(Clone)]
10121012
struct CodeRunner {
10131013
code: CString,

0 commit comments

Comments
 (0)