Skip to content

Commit 77b8a47

Browse files
Add deref field specilization
1 parent 5ea952a commit 77b8a47

File tree

5 files changed

+105
-76
lines changed

5 files changed

+105
-76
lines changed

pyo3-macros-backend/src/pymethod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ pub fn impl_py_getter_def(
845845
{ #pyo3_path::impl_::pyclass::IsIntoPy::<#ty>::VALUE },
846846
{ #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#ty>::VALUE },
847847
{ #pyo3_path::impl_::pyclass::IsIntoPyObject::<#ty>::VALUE },
848+
{ #pyo3_path::impl_::pyclass::IsDerefIntoPyObject::<#ty>::VALUE },
848849
> = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
849850
#generator
850851
}

src/conversions/std/sync.rs

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,9 @@
11
#[cfg(feature = "experimental-inspect")]
22
use crate::inspect::types::TypeInfo;
33
use crate::types::PyAnyMethods;
4-
use crate::{Bound, BoundObject, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python};
4+
use crate::{Bound, FromPyObject, PyAny, PyResult};
55
use std::sync::Arc;
66

7-
// TODO find a better way (without the extra type parameters) to name the associated types in the trait.
8-
impl<'py, A, T, O, E> IntoPyObject<'py> for Arc<A>
9-
where
10-
for<'a> &'a A: IntoPyObject<'py, Target = T, Output = O, Error = E>,
11-
O: BoundObject<'py, T>,
12-
E: Into<PyErr>,
13-
{
14-
type Target = T;
15-
type Output = O;
16-
type Error = E;
17-
18-
#[inline]
19-
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
20-
(&*self).into_pyobject(py)
21-
}
22-
23-
#[cfg(feature = "experimental-inspect")]
24-
fn type_output() -> TypeInfo {
25-
<&A as IntoPyObject<'py>>::type_output()
26-
}
27-
}
28-
29-
impl<'a, 'py, T: 'a> IntoPyObject<'py> for &'a Arc<T>
30-
where
31-
&'a T: IntoPyObject<'py>,
32-
{
33-
type Target = <&'a T as IntoPyObject<'py>>::Target;
34-
type Output = <&'a T as IntoPyObject<'py>>::Output;
35-
type Error = <&'a T as IntoPyObject<'py>>::Error;
36-
37-
#[inline]
38-
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
39-
(&**self).into_pyobject(py)
40-
}
41-
42-
#[cfg(feature = "experimental-inspect")]
43-
fn type_output() -> TypeInfo {
44-
<&'a T as IntoPyObject<'py>>::type_output()
45-
}
46-
}
47-
487
impl<'py, T> FromPyObject<'py> for Arc<T>
498
where
509
T: FromPyObject<'py>,
@@ -58,28 +17,3 @@ where
5817
T::type_input()
5918
}
6019
}
61-
62-
#[cfg(test)]
63-
mod tests {
64-
use super::*;
65-
use crate::types::PyInt;
66-
use crate::Python;
67-
68-
#[test]
69-
fn test_arc_into_pyobject() {
70-
macro_rules! test_roundtrip {
71-
($arc:expr) => {
72-
Python::with_gil(|py| {
73-
let arc = $arc;
74-
let obj: Bound<'_, PyInt> = arc.into_pyobject(py).unwrap();
75-
assert_eq!(obj.extract::<i32>().unwrap(), 42);
76-
let roundtrip = obj.extract::<Arc<i32>>().unwrap();
77-
assert_eq!(&42, roundtrip.as_ref());
78-
});
79-
};
80-
}
81-
82-
test_roundtrip!(Arc::new(42));
83-
test_roundtrip!(&Arc::new(42));
84-
}
85-
}

src/impl_/pyclass.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
};
1515
#[allow(deprecated)]
1616
use crate::{IntoPy, ToPyObject};
17+
use std::ops::Deref;
1718
use std::{
1819
borrow::Cow,
1920
ffi::{CStr, CString},
@@ -1229,6 +1230,7 @@ pub struct PyClassGetterGenerator<
12291230
const IMPLEMENTS_INTOPY: bool,
12301231
const IMPLEMENTS_INTOPYOBJECT_REF: bool,
12311232
const IMPLEMENTS_INTOPYOBJECT: bool,
1233+
const IMPLEMENTS_DEREF: bool,
12321234
>(PhantomData<(ClassT, FieldT, Offset)>);
12331235

12341236
impl<
@@ -1240,6 +1242,7 @@ impl<
12401242
const IMPLEMENTS_INTOPY: bool,
12411243
const IMPLEMENTS_INTOPYOBJECT_REF: bool,
12421244
const IMPLEMENTS_INTOPYOBJECT: bool,
1245+
const IMPLEMENTS_DEREF: bool,
12431246
>
12441247
PyClassGetterGenerator<
12451248
ClassT,
@@ -1250,6 +1253,7 @@ impl<
12501253
IMPLEMENTS_INTOPY,
12511254
IMPLEMENTS_INTOPYOBJECT_REF,
12521255
IMPLEMENTS_INTOPYOBJECT,
1256+
IMPLEMENTS_DEREF,
12531257
>
12541258
{
12551259
/// Safety: constructing this type requires that there exists a value of type FieldT
@@ -1267,6 +1271,7 @@ impl<
12671271
const IMPLEMENTS_INTOPY: bool,
12681272
const IMPLEMENTS_INTOPYOBJECT_REF: bool,
12691273
const IMPLEMENTS_INTOPYOBJECT: bool,
1274+
const IMPLEMENTS_DEREF: bool,
12701275
>
12711276
PyClassGetterGenerator<
12721277
ClassT,
@@ -1277,6 +1282,7 @@ impl<
12771282
IMPLEMENTS_INTOPY,
12781283
IMPLEMENTS_INTOPYOBJECT_REF,
12791284
IMPLEMENTS_INTOPYOBJECT,
1285+
IMPLEMENTS_DEREF,
12801286
>
12811287
{
12821288
/// `Py<T>` fields have a potential optimization to use Python's "struct members" to read
@@ -1312,7 +1318,19 @@ impl<
13121318
FieldT: ToPyObject,
13131319
Offset: OffsetCalculator<ClassT, FieldT>,
13141320
const IMPLEMENTS_INTOPY: bool,
1315-
> PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPY, false, false>
1321+
const IMPLEMENTS_DEREF: bool,
1322+
>
1323+
PyClassGetterGenerator<
1324+
ClassT,
1325+
FieldT,
1326+
Offset,
1327+
false,
1328+
true,
1329+
IMPLEMENTS_INTOPY,
1330+
false,
1331+
false,
1332+
IMPLEMENTS_DEREF,
1333+
>
13161334
{
13171335
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
13181336
PyMethodDefType::Getter(PyGetterDef {
@@ -1332,6 +1350,7 @@ impl<
13321350
const IMPLEMENTS_TOPYOBJECT: bool,
13331351
const IMPLEMENTS_INTOPY: bool,
13341352
const IMPLEMENTS_INTOPYOBJECT: bool,
1353+
const IMPLEMENTS_DEREF: bool,
13351354
>
13361355
PyClassGetterGenerator<
13371356
ClassT,
@@ -1342,6 +1361,7 @@ impl<
13421361
IMPLEMENTS_INTOPY,
13431362
true,
13441363
IMPLEMENTS_INTOPYOBJECT,
1364+
IMPLEMENTS_DEREF,
13451365
>
13461366
where
13471367
ClassT: PyClass,
@@ -1357,9 +1377,34 @@ where
13571377
}
13581378
}
13591379

1380+
/// If Field does not implement `IntoPyObject` but Deref::Target does, use that instead
1381+
impl<ClassT, FieldT, Offset>
1382+
PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, false, false, false, true>
1383+
where
1384+
ClassT: PyClass,
1385+
FieldT: Deref,
1386+
for<'a, 'py> &'a FieldT::Target: IntoPyObject<'py>,
1387+
Offset: OffsetCalculator<ClassT, FieldT>,
1388+
{
1389+
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1390+
PyMethodDefType::Getter(PyGetterDef {
1391+
name,
1392+
meth: pyo3_get_value_into_pyobject_deref::<ClassT, FieldT, Offset>,
1393+
doc,
1394+
})
1395+
}
1396+
}
1397+
13601398
/// Temporary case to prefer `IntoPyObject + Clone` over `IntoPy + Clone`, while still showing the
13611399
/// `IntoPyObject` suggestion if neither is implemented;
1362-
impl<ClassT, FieldT, Offset, const IMPLEMENTS_TOPYOBJECT: bool, const IMPLEMENTS_INTOPY: bool>
1400+
impl<
1401+
ClassT,
1402+
FieldT,
1403+
Offset,
1404+
const IMPLEMENTS_TOPYOBJECT: bool,
1405+
const IMPLEMENTS_INTOPY: bool,
1406+
const IMPLEMENTS_DEREF: bool,
1407+
>
13631408
PyClassGetterGenerator<
13641409
ClassT,
13651410
FieldT,
@@ -1369,6 +1414,7 @@ impl<ClassT, FieldT, Offset, const IMPLEMENTS_TOPYOBJECT: bool, const IMPLEMENTS
13691414
IMPLEMENTS_INTOPY,
13701415
false,
13711416
true,
1417+
IMPLEMENTS_DEREF,
13721418
>
13731419
where
13741420
ClassT: PyClass,
@@ -1386,8 +1432,18 @@ where
13861432

13871433
/// IntoPy + Clone fallback case, which was the only behaviour before PyO3 0.22.
13881434
#[allow(deprecated)]
1389-
impl<ClassT, FieldT, Offset>
1390-
PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, true, false, false>
1435+
impl<ClassT, FieldT, Offset, const IMPLEMENTS_DEREF: bool>
1436+
PyClassGetterGenerator<
1437+
ClassT,
1438+
FieldT,
1439+
Offset,
1440+
false,
1441+
false,
1442+
true,
1443+
false,
1444+
false,
1445+
IMPLEMENTS_DEREF,
1446+
>
13911447
where
13921448
ClassT: PyClass,
13931449
Offset: OffsetCalculator<ClassT, FieldT>,
@@ -1415,7 +1471,7 @@ impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
14151471

14161472
/// Base case attempts to use IntoPyObject + Clone
14171473
impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
1418-
PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, false, false, false>
1474+
PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, false, false, false, false>
14191475
{
14201476
pub const fn generate(&self, _name: &'static CStr, _doc: &'static CStr) -> PyMethodDefType
14211477
// The bound goes here rather than on the block so that this impl is always available
@@ -1489,6 +1545,28 @@ where
14891545
.into_ptr())
14901546
}
14911547

1548+
fn pyo3_get_value_into_pyobject_deref<ClassT, FieldT, Offset>(
1549+
py: Python<'_>,
1550+
obj: *mut ffi::PyObject,
1551+
) -> PyResult<*mut ffi::PyObject>
1552+
where
1553+
ClassT: PyClass,
1554+
FieldT: Deref,
1555+
for<'a, 'py> &'a FieldT::Target: IntoPyObject<'py>,
1556+
Offset: OffsetCalculator<ClassT, FieldT>,
1557+
{
1558+
let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1559+
let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1560+
1561+
// SAFETY: Offset is known to describe the location of the value, and
1562+
// _holder is preventing mutable aliasing
1563+
Ok((unsafe { &*value })
1564+
.deref()
1565+
.into_pyobject(py)
1566+
.map_err(Into::into)?
1567+
.into_ptr())
1568+
}
1569+
14921570
fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
14931571
py: Python<'_>,
14941572
obj: *mut ffi::PyObject,

src/impl_/pyclass/probes.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::marker::PhantomData;
2-
31
use crate::{conversion::IntoPyObject, Py};
42
#[allow(deprecated)]
53
use crate::{IntoPy, ToPyObject};
4+
use std::marker::PhantomData;
5+
use std::sync::Arc;
66

77
/// Trait used to combine with zero-sized types to calculate at compile time
88
/// some property of a type.
@@ -70,3 +70,19 @@ probe!(IsSync);
7070
impl<T: Sync> IsSync<T> {
7171
pub const VALUE: bool = true;
7272
}
73+
74+
probe!(IsDerefIntoPyObject);
75+
76+
impl<T> IsDerefIntoPyObject<Arc<T>>
77+
where
78+
for<'a, 'py> &'a T: IntoPyObject<'py>,
79+
{
80+
pub const VALUE: bool = true;
81+
}
82+
83+
impl<T> IsDerefIntoPyObject<Box<T>>
84+
where
85+
for<'a, 'py> &'a T: IntoPyObject<'py>,
86+
{
87+
pub const VALUE: bool = true;
88+
}

tests/ui/invalid_property_args.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ error[E0277]: `PhantomData<i32>` cannot be converted to a Python object
6565
&'a (T0, T1, T2, T3, T4)
6666
and $N others
6767
= note: required for `PhantomData<i32>` to implement `for<'py> PyO3GetField<'py>`
68-
note: required by a bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false, false, false, false>::generate`
68+
note: required by a bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false, false, false, false, false>::generate`
6969
--> src/impl_/pyclass.rs
7070
|
7171
| pub const fn generate(&self, _name: &'static CStr, _doc: &'static CStr) -> PyMethodDefType
7272
| -------- required by a bound in this associated function
7373
...
7474
| for<'py> FieldT: PyO3GetField<'py>,
75-
| ^^^^^^^^^^^^^^^^^ required by this bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false, false, false, false>::generate`
75+
| ^^^^^^^^^^^^^^^^^ required by this bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false, false, false, false, false>::generate`

0 commit comments

Comments
 (0)