Skip to content

Commit

Permalink
abi3: add support for dict and weakref from Python 3.9
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Dec 27, 2020
1 parent 3f093d9 commit 3bb8974
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 24 deletions.
4 changes: 2 additions & 2 deletions src/pycell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ pub struct PyCell<T: PyClass> {

impl<T: PyClass> PyCell<T> {
/// Get the offset of the dictionary from the start of the struct in bytes.
#[cfg(not(Py_LIMITED_API))]
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
pub(crate) fn dict_offset() -> Option<usize> {
if T::Dict::IS_DUMMY {
None
Expand All @@ -184,7 +184,7 @@ impl<T: PyClass> PyCell<T> {
}

/// Get the offset of the weakref list from the start of the struct in bytes.
#[cfg(not(Py_LIMITED_API))]
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
pub(crate) fn weakref_offset() -> Option<usize> {
if T::WeakRef::IS_DUMMY {
None
Expand Down
88 changes: 76 additions & 12 deletions src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ where
slots.maybe_push(ffi::Py_tp_new, new.map(|v| v as _));
slots.maybe_push(ffi::Py_tp_call, call.map(|v| v as _));

#[cfg(Py_3_9)]
{
let members = py_class_members::<T>();
if !members.is_empty() {
slots.push(ffi::Py_tp_members, into_raw(members))
}
}

// normal methods
if !methods.is_empty() {
slots.push(ffi::Py_tp_methods, into_raw(methods));
Expand All @@ -203,7 +211,7 @@ where
basicsize: std::mem::size_of::<T::Layout>() as c_int,
itemsize: 0,
flags: py_class_flags::<T>(has_gc_methods),
slots: slots.0.as_mut_slice().as_mut_ptr(),
slots: slots.0.as_mut_ptr(),
};

let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) };
Expand Down Expand Up @@ -247,16 +255,22 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
}
}
// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
unsafe {
(*type_object).tp_dictoffset = dict_offset as ffi::Py_ssize_t;

// Setting tp_dictoffset and tp_weaklistoffset via slots doesn't work until Python 3.9, so on
// older versions again we must fixup the type object.
#[cfg(not(Py_3_9))]
{
// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
unsafe {
(*type_object).tp_dictoffset = dict_offset as ffi::Py_ssize_t;
}
}
}
// weakref support
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
unsafe {
(*type_object).tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
// weakref support
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
unsafe {
(*type_object).tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
}
}
}
}
Expand Down Expand Up @@ -333,6 +347,46 @@ fn py_class_method_defs<T: PyMethods>() -> (
(new, call, defs)
}

/// Generates the __dictoffset__ and __weaklistoffset__ members, to set tp_dictoffset and
/// tp_weaklistoffset.
///
/// Only works on Python 3.9 and up.
#[cfg(Py_3_9)]
fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
static DICTOFFSET: &str = "__dictoffset__\0";
static WEAKLISTOFFSET: &str = "__weaklistoffset__\0";

let mut members = Vec::new();

// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
members.push(ffi::structmember::PyMemberDef {
name: DICTOFFSET.as_ptr() as _,
type_code: ffi::structmember::T_PYSSIZET,
offset: dict_offset as _,
flags: ffi::structmember::READONLY,
doc: std::ptr::null_mut(),
});
}

// weakref support
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
members.push(ffi::structmember::PyMemberDef {
name: WEAKLISTOFFSET.as_ptr() as _,
type_code: ffi::structmember::T_PYSSIZET,
offset: weakref_offset as _,
flags: ffi::structmember::READONLY,
doc: std::ptr::null_mut(),
});
}

if !members.is_empty() {
members.push(unsafe { std::mem::zeroed() });
}

members
}

fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
let mut defs = std::collections::HashMap::new();

Expand All @@ -357,11 +411,21 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
}

let mut props: Vec<_> = defs.values().cloned().collect();

// PyPy doesn't automatically adds __dict__ getter / setter.
// PyObject_GenericGetDict not in the limited API until Python 3.10.
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
if !T::Dict::IS_DUMMY {
props.push(ffi::PyGetSetDef_DICT);
props.push(ffi::PyGetSetDef {
name: "__dict__\0".as_ptr() as *mut c_char,
get: Some(ffi::PyObject_GenericGetDict),
set: Some(ffi::PyObject_GenericSetDict),
doc: ptr::null_mut(),
closure: ptr::null_mut(),
});
}
if !props.is_empty() {
props.push(ffi::PyGetSetDef_INIT);
props.push(unsafe { std::mem::zeroed() });
}
props
}
Expand Down
11 changes: 6 additions & 5 deletions tests/test_dunder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ fn test_cls_impl() {
struct DunderDictSupport {}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn dunder_dict_support() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand All @@ -472,8 +472,9 @@ fn dunder_dict_support() {
);
}

// Accessing inst.__dict__ only supported in limited API from Python 3.10
#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
fn access_dunder_dict() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand All @@ -495,7 +496,7 @@ struct InheritDict {
}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn inherited_dict() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand All @@ -505,7 +506,7 @@ fn inherited_dict() {
inst,
r#"
inst.a = 1
assert inst.__dict__ == {'a': 1}
assert inst.a == 1
"#
);
}
Expand All @@ -514,7 +515,7 @@ fn inherited_dict() {
struct WeakRefDunderDictSupport {}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn weakref_dunder_dict_support() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand Down
4 changes: 2 additions & 2 deletions tests/test_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ fn gc_integration2() {
struct WeakRefSupport {}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn weakref_support() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand All @@ -169,7 +169,7 @@ struct InheritWeakRef {
}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn inherited_weakref() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand Down
4 changes: 2 additions & 2 deletions tests/test_unsendable_dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ impl UnsendableDictClass {
}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn test_unsendable_dict() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand All @@ -33,7 +33,7 @@ impl UnsendableDictClassWithWeakRef {
}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn test_unsendable_dict_with_weakref() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand Down
2 changes: 1 addition & 1 deletion tests/test_various.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn add_module(py: Python, module: &PyModule) -> PyResult<()> {
}

#[test]
#[cfg_attr(Py_LIMITED_API, ignore)]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_9)), ignore)]
fn test_pickle() {
let gil = Python::acquire_gil();
let py = gil.python();
Expand Down

0 comments on commit 3bb8974

Please sign in to comment.