Skip to content

Commit

Permalink
make it work on MSRV :(
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Jun 19, 2024
1 parent 9ea2842 commit db66ed2
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 72 deletions.
14 changes: 8 additions & 6 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,12 +1415,14 @@ pub fn gen_complex_enum_variant_attr(
};

let method_def = quote! {
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#cls_type::#wrapper_ident
)
})
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#cls_type::#wrapper_ident
)
})
)
};

MethodAndMethodDef {
Expand Down
14 changes: 8 additions & 6 deletions pyo3-macros-backend/src/pyimpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,14 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> MethodA
};

let method_def = quote! {
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#cls::#wrapper_ident
)
})
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#cls::#wrapper_ident
)
})
)
};

MethodAndMethodDef {
Expand Down
58 changes: 38 additions & 20 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,9 @@ pub fn impl_py_method_def(
};
let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx);
let method_def = quote! {
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
)
};
Ok(MethodAndMethodDef {
associated_method,
Expand Down Expand Up @@ -510,12 +512,14 @@ fn impl_py_class_attribute(
};

let method_def = quote! {
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#cls::#wrapper_ident
)
})
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#cls::#wrapper_ident
)
})
)
};

Ok(MethodAndMethodDef {
Expand Down Expand Up @@ -700,11 +704,13 @@ pub fn impl_py_setter_def(

let method_def = quote! {
#cfg_attrs
#pyo3_path::class::PyMethodDefType::Setter(
#pyo3_path::class::PySetterDef::new(
#python_name,
#cls::#wrapper_ident,
#doc
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::Setter(
#pyo3_path::class::PySetterDef::new(
#python_name,
#cls::#wrapper_ident,
#doc
)
)
)
};
Expand Down Expand Up @@ -776,15 +782,25 @@ pub fn impl_py_getter_def(
#cfg_attrs
{
use #pyo3_path::impl_::pyclass::Tester;
const OFFSET: usize = ::std::mem::offset_of!(#cls, #field);

struct Offset;
unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {
fn offset() -> usize {
#pyo3_path::impl_::pyclass::class_offset::<#cls>() +
#pyo3_path::impl_::pyclass::offset_of!(#cls, #field)
}
}

const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::<
#cls,
#ty,
OFFSET,
Offset,
{ #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE },
{ #pyo3_path::impl_::pyclass::IsToPyObject::<#ty>::VALUE },
> = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
GENERATOR.generate(#python_name, #doc)
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime(
|| GENERATOR.generate(#python_name, #doc)
)
}
};

Expand Down Expand Up @@ -820,11 +836,13 @@ pub fn impl_py_getter_def(

let method_def = quote! {
#cfg_attrs
#pyo3_path::class::PyMethodDefType::Getter(
#pyo3_path::class::PyGetterDef::new(
#python_name,
#cls::#wrapper_ident,
#doc
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::Getter(
#pyo3_path::class::PyGetterDef::new(
#python_name,
#cls::#wrapper_ident,
#doc
)
)
)
};
Expand Down
97 changes: 61 additions & 36 deletions src/impl_/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,15 @@ impl<T> Clone for PyClassImplCollector<T> {

impl<T> Copy for PyClassImplCollector<T> {}

pub enum MaybeRuntimePyMethodDef {
/// Used in cases where const functionality is not sufficient to define the method
/// purely at compile time.
Runtime(fn() -> PyMethodDefType),
Static(PyMethodDefType),
}

pub struct PyClassItems {
pub methods: &'static [PyMethodDefType],
pub methods: &'static [MaybeRuntimePyMethodDef],
pub slots: &'static [ffi::PyType_Slot],
}

Expand Down Expand Up @@ -1172,73 +1179,92 @@ pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
result
}

// Below MSRV 1.77 we can't use `std::mem::offset_of!`, and the replacement in
// `memoffset::offset_of` doesn't work in const contexts for types containing `UnsafeCell`.
pub unsafe trait OffsetCalculator<T: PyClass, U> {
/// Offset to the field within a PyClassObject<T>, in bytes.
///
/// The trait is unsafe to implement because producing an incorrect offset will lead to UB.
fn offset() -> usize;
}

// Used in generated implementations of OffsetCalculator
pub fn class_offset<T: PyClass>() -> usize {
offset_of!(PyClassObject<T>, contents)
}

// Used in generated implementations of OffsetCalculator
pub use memoffset::offset_of;

/// Type which uses specialization on impl blocks to determine how to read a field from a Rust pyclass
/// as part of a `#[pyo3(get)]` annotation.
pub struct PyClassGetterGenerator<
// structural information about the field: class type, field type, where the field is within the
// class struct
ClassT: PyClass,
FieldT,
const OFFSET: usize,
Offset: OffsetCalculator<ClassT, FieldT>, // on Rust 1.77+ this could be a const OFFSET: usize
// additional metadata about the field which is used to switch between different implementations
// at compile time
const IS_PY_T: bool,
const IMPLEMENTS_TOPYOBJECT: bool,
>(PhantomData<ClassT>, PhantomData<FieldT>);
>(PhantomData<(ClassT, FieldT, Offset)>);

impl<
ClassT: PyClass,
FieldT,
const OFFSET: usize,
Offset: OffsetCalculator<ClassT, FieldT>,
const IS_PY_T: bool,
const IMPLEMENTS_TOPYOBJECT: bool,
> PyClassGetterGenerator<ClassT, FieldT, OFFSET, IS_PY_T, IMPLEMENTS_TOPYOBJECT>
> PyClassGetterGenerator<ClassT, FieldT, Offset, IS_PY_T, IMPLEMENTS_TOPYOBJECT>
{
/// Safety: constructing this type requires that there exists a value of type X
/// at offset OFFSET within the type T.
/// Safety: constructing this type requires that there exists a value of type FieldT
/// at the calculated offset within the type ClassT.
pub const unsafe fn new() -> Self {
Self(PhantomData, PhantomData)
Self(PhantomData)
}
}

impl<ClassT: PyClass, U, const OFFSET: usize, const IMPLEMENTS_TOPYOBJECT: bool>
PyClassGetterGenerator<ClassT, Py<U>, OFFSET, true, IMPLEMENTS_TOPYOBJECT>
impl<
ClassT: PyClass,
U,
Offset: OffsetCalculator<ClassT, Py<U>>,
const IMPLEMENTS_TOPYOBJECT: bool,
> PyClassGetterGenerator<ClassT, Py<U>, Offset, true, IMPLEMENTS_TOPYOBJECT>
{
/// Py<T> fields have a potential optimization to use Python's "struct members" to read
/// the field directly from the struct, rather than using a getter function.
///
/// This is the most efficient operation the Python interpreter could possibly do to
/// read a field, but it's only possible for us to allow this for frozen classes.
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
use crate::pyclass::boolean_struct::private::Boolean;
if ClassT::Frozen::VALUE {
PyMethodDefType::StructMember(ffi::PyMemberDef {
name: name.as_ptr(),
type_code: ffi::Py_T_OBJECT_EX,
offset: (std::mem::offset_of!(PyClassObject::<ClassT>, contents) + OFFSET)
as ffi::Py_ssize_t,
offset: Offset::offset() as ffi::Py_ssize_t,
flags: ffi::Py_READONLY,
doc: doc.as_ptr(),
})
} else {
PyMethodDefType::Getter(crate::PyGetterDef {
name,
meth: pyo3_get_value_topyobject::<ClassT, Py<U>, OFFSET>,
meth: pyo3_get_value_topyobject::<ClassT, Py<U>, Offset>,
doc,
})
}
}
}

/// Field is not Py<T>; try to use `ToPyObject` to avoid potentially expensive clones of containers like `Vec`
impl<ClassT: PyClass, FieldT: ToPyObject, const OFFSET: usize>
PyClassGetterGenerator<ClassT, FieldT, OFFSET, false, true>
impl<ClassT: PyClass, FieldT: ToPyObject, Offset: OffsetCalculator<ClassT, FieldT>>
PyClassGetterGenerator<ClassT, FieldT, Offset, false, true>
{
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
PyMethodDefType::Getter(crate::PyGetterDef {
// TODO: store &CStr in PyGetterDef etc
name,
meth: pyo3_get_value_topyobject::<ClassT, FieldT, OFFSET>,
meth: pyo3_get_value_topyobject::<ClassT, FieldT, Offset>,
doc,
})
}
Expand All @@ -1257,8 +1283,8 @@ pub trait PyO3GetField: IntoPy<Py<PyAny>> + Clone {}
impl<T: IntoPy<Py<PyAny>> + Clone> PyO3GetField for T {}

/// Base case attempts to use IntoPy + Clone, which was the only behaviour before PyO3 0.22.
impl<ClassT: PyClass, FieldT, const OFFSET: usize>
PyClassGetterGenerator<ClassT, FieldT, OFFSET, false, false>
impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
PyClassGetterGenerator<ClassT, FieldT, Offset, false, false>
{
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType
// The bound goes here rather than on the block so that this impl is always available
Expand All @@ -1267,9 +1293,8 @@ impl<ClassT: PyClass, FieldT, const OFFSET: usize>
FieldT: PyO3GetField,
{
PyMethodDefType::Getter(crate::PyGetterDef {
// TODO: store &CStr in PyGetterDef etc
name,
meth: pyo3_get_value::<ClassT, FieldT, OFFSET>,
meth: pyo3_get_value::<ClassT, FieldT, Offset>,
doc,
})
}
Expand Down Expand Up @@ -1307,7 +1332,11 @@ impl<T: ToPyObject> IsToPyObject<T> {
pub const VALUE: bool = true;
}

fn pyo3_get_value_topyobject<ClassT: PyClass, FieldT: ToPyObject, const OFFSET: usize>(
fn pyo3_get_value_topyobject<
ClassT: PyClass,
FieldT: ToPyObject,
Offset: OffsetCalculator<ClassT, FieldT>,
>(
py: Python<'_>,
obj: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Expand All @@ -1318,18 +1347,18 @@ fn pyo3_get_value_topyobject<ClassT: PyClass, FieldT: ToPyObject, const OFFSET:
.try_borrow()?
};

let value = unsafe {
obj.cast::<u8>()
.offset((std::mem::offset_of!(PyClassObject::<ClassT>, contents) + OFFSET) as isize)
.cast::<FieldT>()
};
let value = unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() };

// SAFETY: OFFSET is known to describe the location of the value, and
// SAFETY: Offset is known to describe the location of the value, and
// _holder is preventing mutable aliasing
Ok((unsafe { &*value }).to_object(py).into_ptr())
}

fn pyo3_get_value<ClassT: PyClass, FieldT: IntoPy<Py<PyAny>> + Clone, const OFFSET: usize>(
fn pyo3_get_value<
ClassT: PyClass,
FieldT: IntoPy<Py<PyAny>> + Clone,
Offset: OffsetCalculator<ClassT, FieldT>,
>(
py: Python<'_>,
obj: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Expand All @@ -1340,13 +1369,9 @@ fn pyo3_get_value<ClassT: PyClass, FieldT: IntoPy<Py<PyAny>> + Clone, const OFFS
.try_borrow()?
};

let value = unsafe {
obj.cast::<u8>()
.offset((std::mem::offset_of!(PyClassObject::<ClassT>, contents) + OFFSET) as isize)
.cast::<FieldT>()
};
let value = unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() };

// SAFETY: OFFSET is known to describe the location of the value, and
// SAFETY: Offset is known to describe the location of the value, and
// _holder is preventing mutable aliasing
Ok((unsafe { &*value }).clone().into_py(py).into_ptr())
}
11 changes: 10 additions & 1 deletion src/impl_/pyclass/lazy_type_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{
use crate::{
exceptions::PyRuntimeError,
ffi,
impl_::pyclass::MaybeRuntimePyMethodDef,
pyclass::{create_type_object, PyClassTypeObject},
sync::{GILOnceCell, GILProtected},
types::PyType,
Expand Down Expand Up @@ -149,7 +150,15 @@ impl LazyTypeObjectInner {
let mut items = vec![];
for class_items in items_iter {
for def in class_items.methods {
if let PyMethodDefType::ClassAttribute(attr) = def {
let built_method;
let method = match def {
MaybeRuntimePyMethodDef::Runtime(builder) => {
built_method = builder();
&built_method
}
MaybeRuntimePyMethodDef::Static(method) => method,
};
if let PyMethodDefType::ClassAttribute(attr) = method {
match (attr.meth)(py) {
Ok(val) => items.push((attr.name, val)),
Err(err) => {
Expand Down
10 changes: 9 additions & 1 deletion src/pyclass/create_type_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
pycell::PyClassObject,
pyclass::{
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
tp_dealloc_with_gc, PyClassItemsIter,
tp_dealloc_with_gc, MaybeRuntimePyMethodDef, PyClassItemsIter,
},
pymethods::{Getter, Setter},
trampoline::trampoline,
Expand Down Expand Up @@ -317,6 +317,14 @@ impl PyTypeBuilder {
self.push_slot(slot.slot, slot.pfunc);
}
for method in items.methods {
let built_method;
let method = match method {
MaybeRuntimePyMethodDef::Runtime(builder) => {
built_method = builder();
&built_method
}
MaybeRuntimePyMethodDef::Static(method) => method,
};
self.pymethod_def(method);
}
}
Expand Down
Loading

0 comments on commit db66ed2

Please sign in to comment.