Skip to content

Commit b862d1b

Browse files
committed
review: mejrs
1 parent 49aad6c commit b862d1b

File tree

4 files changed

+84
-8
lines changed

4 files changed

+84
-8
lines changed

pyo3-macros-backend/src/pymethod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,8 @@ pub fn impl_py_getter_def(
783783
let method_def = quote_spanned! {ty.span()=>
784784
#cfg_attrs
785785
{
786-
use #pyo3_path::impl_::pyclass::Tester;
786+
#[allow(unused_imports)] // might not be used if all probes are positve
787+
use #pyo3_path::impl_::pyclass::Probe;
787788

788789
struct Offset;
789790
unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {

src/impl_/pyclass.rs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,6 @@ impl<ClassT: PyClass, FieldT: ToPyObject, Offset: OffsetCalculator<ClassT, Field
12791279
diagnostic::on_unimplemented(
12801280
message = "`{Self}` cannot be converted to a Python object",
12811281
label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1282-
note = "`Py<T>` fields are always converible to Python objects",
12831282
note = "implement `ToPyObject` or `IntoPy<PyObject> + Clone` for `{Self}` to define the conversion",
12841283
)
12851284
)]
@@ -1313,24 +1312,24 @@ impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
13131312
/// The true case is defined in the zero-sized type's impl block, which is
13141313
/// gated on some property like trait bound or only being implemented
13151314
/// for fixed concrete types.
1316-
pub trait Tester {
1315+
pub trait Probe {
13171316
const VALUE: bool = false;
13181317
}
13191318

1320-
macro_rules! tester {
1319+
macro_rules! probe {
13211320
($name:ident) => {
13221321
pub struct $name<T>(PhantomData<T>);
1323-
impl<T> Tester for $name<T> {}
1322+
impl<T> Probe for $name<T> {}
13241323
};
13251324
}
13261325

1327-
tester!(IsPyT);
1326+
probe!(IsPyT);
13281327

13291328
impl<T> IsPyT<Py<T>> {
13301329
pub const VALUE: bool = true;
13311330
}
13321331

1333-
tester!(IsToPyObject);
1332+
probe!(IsToPyObject);
13341333

13351334
impl<T: ToPyObject> IsToPyObject<T> {
13361335
pub const VALUE: bool = true;
@@ -1379,3 +1378,79 @@ fn pyo3_get_value<
13791378
// _holder is preventing mutable aliasing
13801379
Ok((unsafe { &*value }).clone().into_py(py).into_ptr())
13811380
}
1381+
1382+
#[cfg(test)]
1383+
#[cfg(feature = "macros")]
1384+
mod tests {
1385+
use super::*;
1386+
1387+
#[test]
1388+
fn get_py_for_frozen_class() {
1389+
#[crate::pyclass(crate = "crate", frozen)]
1390+
struct FrozenClass {
1391+
#[pyo3(get)]
1392+
value: Py<PyAny>,
1393+
}
1394+
1395+
let mut methods = Vec::new();
1396+
let mut slots = Vec::new();
1397+
1398+
for items in FrozenClass::items_iter() {
1399+
methods.extend(items.methods.iter().map(|m| match m {
1400+
MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1401+
MaybeRuntimePyMethodDef::Runtime(r) => r(),
1402+
}));
1403+
slots.extend_from_slice(items.slots);
1404+
}
1405+
1406+
assert_eq!(methods.len(), 1);
1407+
assert!(slots.is_empty());
1408+
1409+
match methods.first() {
1410+
Some(PyMethodDefType::StructMember(member)) => {
1411+
assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1412+
assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1413+
assert_eq!(
1414+
member.offset,
1415+
(memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1416+
+ memoffset::offset_of!(FrozenClass, value))
1417+
as ffi::Py_ssize_t
1418+
);
1419+
assert_eq!(member.flags, ffi::Py_READONLY);
1420+
}
1421+
_ => panic!("Expected a StructMember"),
1422+
}
1423+
}
1424+
1425+
#[test]
1426+
fn get_py_for_non_frozen_class() {
1427+
#[crate::pyclass(crate = "crate")]
1428+
struct FrozenClass {
1429+
#[pyo3(get)]
1430+
value: Py<PyAny>,
1431+
}
1432+
1433+
let mut methods = Vec::new();
1434+
let mut slots = Vec::new();
1435+
1436+
for items in FrozenClass::items_iter() {
1437+
methods.extend(items.methods.iter().map(|m| match m {
1438+
MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1439+
MaybeRuntimePyMethodDef::Runtime(r) => r(),
1440+
}));
1441+
slots.extend_from_slice(items.slots);
1442+
}
1443+
1444+
assert_eq!(methods.len(), 1);
1445+
assert!(slots.is_empty());
1446+
1447+
match methods.first() {
1448+
Some(PyMethodDefType::Getter(getter)) => {
1449+
assert_eq!(getter.name, ffi::c_str!("value"));
1450+
assert_eq!(getter.doc, ffi::c_str!(""));
1451+
// tests for the function pointer are in test_getter_setter.py
1452+
}
1453+
_ => panic!("Expected a StructMember"),
1454+
}
1455+
}
1456+
}

src/impl_/pymethods.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl IPowModulo {
5252

5353
/// `PyMethodDefType` represents different types of Python callable objects.
5454
/// It is used by the `#[pymethods]` attribute.
55+
#[cfg_attr(test, derive(Clone))]
5556
pub enum PyMethodDefType {
5657
/// Represents class method
5758
Class(PyMethodDef),

tests/ui/invalid_property_args.stderr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ error[E0277]: `PhantomData<i32>` cannot be converted to a Python object
5353
| ^ required by `#[pyo3(get)]` to create a readable property from a field of type `PhantomData<i32>`
5454
|
5555
= help: the trait `IntoPy<Py<PyAny>>` is not implemented for `PhantomData<i32>`, which is required by `PhantomData<i32>: PyO3GetField`
56-
= note: `Py<T>` fields are always converible to Python objects
5756
= note: implement `ToPyObject` or `IntoPy<PyObject> + Clone` for `PhantomData<i32>` to define the conversion
5857
= note: required for `PhantomData<i32>` to implement `PyO3GetField`
5958
note: required by a bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false>::generate`

0 commit comments

Comments
 (0)