Skip to content

Commit db66ed2

Browse files
committed
make it work on MSRV :(
1 parent 9ea2842 commit db66ed2

File tree

7 files changed

+136
-72
lines changed

7 files changed

+136
-72
lines changed

pyo3-macros-backend/src/pyclass.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,12 +1415,14 @@ pub fn gen_complex_enum_variant_attr(
14151415
};
14161416

14171417
let method_def = quote! {
1418-
#pyo3_path::class::PyMethodDefType::ClassAttribute({
1419-
#pyo3_path::class::PyClassAttributeDef::new(
1420-
#python_name,
1421-
#cls_type::#wrapper_ident
1422-
)
1423-
})
1418+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
1419+
#pyo3_path::class::PyMethodDefType::ClassAttribute({
1420+
#pyo3_path::class::PyClassAttributeDef::new(
1421+
#python_name,
1422+
#cls_type::#wrapper_ident
1423+
)
1424+
})
1425+
)
14241426
};
14251427

14261428
MethodAndMethodDef {

pyo3-macros-backend/src/pyimpl.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,14 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> MethodA
197197
};
198198

199199
let method_def = quote! {
200-
#pyo3_path::class::PyMethodDefType::ClassAttribute({
201-
#pyo3_path::class::PyClassAttributeDef::new(
202-
#python_name,
203-
#cls::#wrapper_ident
204-
)
205-
})
200+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
201+
#pyo3_path::class::PyMethodDefType::ClassAttribute({
202+
#pyo3_path::class::PyClassAttributeDef::new(
203+
#python_name,
204+
#cls::#wrapper_ident
205+
)
206+
})
207+
)
206208
};
207209

208210
MethodAndMethodDef {

pyo3-macros-backend/src/pymethod.rs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,9 @@ pub fn impl_py_method_def(
329329
};
330330
let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx);
331331
let method_def = quote! {
332-
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
332+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
333+
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
334+
)
333335
};
334336
Ok(MethodAndMethodDef {
335337
associated_method,
@@ -510,12 +512,14 @@ fn impl_py_class_attribute(
510512
};
511513

512514
let method_def = quote! {
513-
#pyo3_path::class::PyMethodDefType::ClassAttribute({
514-
#pyo3_path::class::PyClassAttributeDef::new(
515-
#python_name,
516-
#cls::#wrapper_ident
517-
)
518-
})
515+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
516+
#pyo3_path::class::PyMethodDefType::ClassAttribute({
517+
#pyo3_path::class::PyClassAttributeDef::new(
518+
#python_name,
519+
#cls::#wrapper_ident
520+
)
521+
})
522+
)
519523
};
520524

521525
Ok(MethodAndMethodDef {
@@ -700,11 +704,13 @@ pub fn impl_py_setter_def(
700704

701705
let method_def = quote! {
702706
#cfg_attrs
703-
#pyo3_path::class::PyMethodDefType::Setter(
704-
#pyo3_path::class::PySetterDef::new(
705-
#python_name,
706-
#cls::#wrapper_ident,
707-
#doc
707+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
708+
#pyo3_path::class::PyMethodDefType::Setter(
709+
#pyo3_path::class::PySetterDef::new(
710+
#python_name,
711+
#cls::#wrapper_ident,
712+
#doc
713+
)
708714
)
709715
)
710716
};
@@ -776,15 +782,25 @@ pub fn impl_py_getter_def(
776782
#cfg_attrs
777783
{
778784
use #pyo3_path::impl_::pyclass::Tester;
779-
const OFFSET: usize = ::std::mem::offset_of!(#cls, #field);
785+
786+
struct Offset;
787+
unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {
788+
fn offset() -> usize {
789+
#pyo3_path::impl_::pyclass::class_offset::<#cls>() +
790+
#pyo3_path::impl_::pyclass::offset_of!(#cls, #field)
791+
}
792+
}
793+
780794
const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::<
781795
#cls,
782796
#ty,
783-
OFFSET,
797+
Offset,
784798
{ #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE },
785799
{ #pyo3_path::impl_::pyclass::IsToPyObject::<#ty>::VALUE },
786800
> = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
787-
GENERATOR.generate(#python_name, #doc)
801+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime(
802+
|| GENERATOR.generate(#python_name, #doc)
803+
)
788804
}
789805
};
790806

@@ -820,11 +836,13 @@ pub fn impl_py_getter_def(
820836

821837
let method_def = quote! {
822838
#cfg_attrs
823-
#pyo3_path::class::PyMethodDefType::Getter(
824-
#pyo3_path::class::PyGetterDef::new(
825-
#python_name,
826-
#cls::#wrapper_ident,
827-
#doc
839+
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
840+
#pyo3_path::class::PyMethodDefType::Getter(
841+
#pyo3_path::class::PyGetterDef::new(
842+
#python_name,
843+
#cls::#wrapper_ident,
844+
#doc
845+
)
828846
)
829847
)
830848
};

src/impl_/pyclass.rs

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,15 @@ impl<T> Clone for PyClassImplCollector<T> {
131131

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

134+
pub enum MaybeRuntimePyMethodDef {
135+
/// Used in cases where const functionality is not sufficient to define the method
136+
/// purely at compile time.
137+
Runtime(fn() -> PyMethodDefType),
138+
Static(PyMethodDefType),
139+
}
140+
134141
pub struct PyClassItems {
135-
pub methods: &'static [PyMethodDefType],
142+
pub methods: &'static [MaybeRuntimePyMethodDef],
136143
pub slots: &'static [ffi::PyType_Slot],
137144
}
138145

@@ -1172,73 +1179,92 @@ pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
11721179
result
11731180
}
11741181

1182+
// Below MSRV 1.77 we can't use `std::mem::offset_of!`, and the replacement in
1183+
// `memoffset::offset_of` doesn't work in const contexts for types containing `UnsafeCell`.
1184+
pub unsafe trait OffsetCalculator<T: PyClass, U> {
1185+
/// Offset to the field within a PyClassObject<T>, in bytes.
1186+
///
1187+
/// The trait is unsafe to implement because producing an incorrect offset will lead to UB.
1188+
fn offset() -> usize;
1189+
}
1190+
1191+
// Used in generated implementations of OffsetCalculator
1192+
pub fn class_offset<T: PyClass>() -> usize {
1193+
offset_of!(PyClassObject<T>, contents)
1194+
}
1195+
1196+
// Used in generated implementations of OffsetCalculator
1197+
pub use memoffset::offset_of;
1198+
11751199
/// Type which uses specialization on impl blocks to determine how to read a field from a Rust pyclass
11761200
/// as part of a `#[pyo3(get)]` annotation.
11771201
pub struct PyClassGetterGenerator<
11781202
// structural information about the field: class type, field type, where the field is within the
11791203
// class struct
11801204
ClassT: PyClass,
11811205
FieldT,
1182-
const OFFSET: usize,
1206+
Offset: OffsetCalculator<ClassT, FieldT>, // on Rust 1.77+ this could be a const OFFSET: usize
11831207
// additional metadata about the field which is used to switch between different implementations
11841208
// at compile time
11851209
const IS_PY_T: bool,
11861210
const IMPLEMENTS_TOPYOBJECT: bool,
1187-
>(PhantomData<ClassT>, PhantomData<FieldT>);
1211+
>(PhantomData<(ClassT, FieldT, Offset)>);
11881212

11891213
impl<
11901214
ClassT: PyClass,
11911215
FieldT,
1192-
const OFFSET: usize,
1216+
Offset: OffsetCalculator<ClassT, FieldT>,
11931217
const IS_PY_T: bool,
11941218
const IMPLEMENTS_TOPYOBJECT: bool,
1195-
> PyClassGetterGenerator<ClassT, FieldT, OFFSET, IS_PY_T, IMPLEMENTS_TOPYOBJECT>
1219+
> PyClassGetterGenerator<ClassT, FieldT, Offset, IS_PY_T, IMPLEMENTS_TOPYOBJECT>
11961220
{
1197-
/// Safety: constructing this type requires that there exists a value of type X
1198-
/// at offset OFFSET within the type T.
1221+
/// Safety: constructing this type requires that there exists a value of type FieldT
1222+
/// at the calculated offset within the type ClassT.
11991223
pub const unsafe fn new() -> Self {
1200-
Self(PhantomData, PhantomData)
1224+
Self(PhantomData)
12011225
}
12021226
}
12031227

1204-
impl<ClassT: PyClass, U, const OFFSET: usize, const IMPLEMENTS_TOPYOBJECT: bool>
1205-
PyClassGetterGenerator<ClassT, Py<U>, OFFSET, true, IMPLEMENTS_TOPYOBJECT>
1228+
impl<
1229+
ClassT: PyClass,
1230+
U,
1231+
Offset: OffsetCalculator<ClassT, Py<U>>,
1232+
const IMPLEMENTS_TOPYOBJECT: bool,
1233+
> PyClassGetterGenerator<ClassT, Py<U>, Offset, true, IMPLEMENTS_TOPYOBJECT>
12061234
{
12071235
/// Py<T> fields have a potential optimization to use Python's "struct members" to read
12081236
/// the field directly from the struct, rather than using a getter function.
12091237
///
12101238
/// This is the most efficient operation the Python interpreter could possibly do to
12111239
/// read a field, but it's only possible for us to allow this for frozen classes.
1212-
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1240+
pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
12131241
use crate::pyclass::boolean_struct::private::Boolean;
12141242
if ClassT::Frozen::VALUE {
12151243
PyMethodDefType::StructMember(ffi::PyMemberDef {
12161244
name: name.as_ptr(),
12171245
type_code: ffi::Py_T_OBJECT_EX,
1218-
offset: (std::mem::offset_of!(PyClassObject::<ClassT>, contents) + OFFSET)
1219-
as ffi::Py_ssize_t,
1246+
offset: Offset::offset() as ffi::Py_ssize_t,
12201247
flags: ffi::Py_READONLY,
12211248
doc: doc.as_ptr(),
12221249
})
12231250
} else {
12241251
PyMethodDefType::Getter(crate::PyGetterDef {
12251252
name,
1226-
meth: pyo3_get_value_topyobject::<ClassT, Py<U>, OFFSET>,
1253+
meth: pyo3_get_value_topyobject::<ClassT, Py<U>, Offset>,
12271254
doc,
12281255
})
12291256
}
12301257
}
12311258
}
12321259

12331260
/// Field is not Py<T>; try to use `ToPyObject` to avoid potentially expensive clones of containers like `Vec`
1234-
impl<ClassT: PyClass, FieldT: ToPyObject, const OFFSET: usize>
1235-
PyClassGetterGenerator<ClassT, FieldT, OFFSET, false, true>
1261+
impl<ClassT: PyClass, FieldT: ToPyObject, Offset: OffsetCalculator<ClassT, FieldT>>
1262+
PyClassGetterGenerator<ClassT, FieldT, Offset, false, true>
12361263
{
12371264
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
12381265
PyMethodDefType::Getter(crate::PyGetterDef {
1239-
// TODO: store &CStr in PyGetterDef etc
12401266
name,
1241-
meth: pyo3_get_value_topyobject::<ClassT, FieldT, OFFSET>,
1267+
meth: pyo3_get_value_topyobject::<ClassT, FieldT, Offset>,
12421268
doc,
12431269
})
12441270
}
@@ -1257,8 +1283,8 @@ pub trait PyO3GetField: IntoPy<Py<PyAny>> + Clone {}
12571283
impl<T: IntoPy<Py<PyAny>> + Clone> PyO3GetField for T {}
12581284

12591285
/// Base case attempts to use IntoPy + Clone, which was the only behaviour before PyO3 0.22.
1260-
impl<ClassT: PyClass, FieldT, const OFFSET: usize>
1261-
PyClassGetterGenerator<ClassT, FieldT, OFFSET, false, false>
1286+
impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
1287+
PyClassGetterGenerator<ClassT, FieldT, Offset, false, false>
12621288
{
12631289
pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType
12641290
// The bound goes here rather than on the block so that this impl is always available
@@ -1267,9 +1293,8 @@ impl<ClassT: PyClass, FieldT, const OFFSET: usize>
12671293
FieldT: PyO3GetField,
12681294
{
12691295
PyMethodDefType::Getter(crate::PyGetterDef {
1270-
// TODO: store &CStr in PyGetterDef etc
12711296
name,
1272-
meth: pyo3_get_value::<ClassT, FieldT, OFFSET>,
1297+
meth: pyo3_get_value::<ClassT, FieldT, Offset>,
12731298
doc,
12741299
})
12751300
}
@@ -1307,7 +1332,11 @@ impl<T: ToPyObject> IsToPyObject<T> {
13071332
pub const VALUE: bool = true;
13081333
}
13091334

1310-
fn pyo3_get_value_topyobject<ClassT: PyClass, FieldT: ToPyObject, const OFFSET: usize>(
1335+
fn pyo3_get_value_topyobject<
1336+
ClassT: PyClass,
1337+
FieldT: ToPyObject,
1338+
Offset: OffsetCalculator<ClassT, FieldT>,
1339+
>(
13111340
py: Python<'_>,
13121341
obj: *mut ffi::PyObject,
13131342
) -> PyResult<*mut ffi::PyObject> {
@@ -1318,18 +1347,18 @@ fn pyo3_get_value_topyobject<ClassT: PyClass, FieldT: ToPyObject, const OFFSET:
13181347
.try_borrow()?
13191348
};
13201349

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

1327-
// SAFETY: OFFSET is known to describe the location of the value, and
1352+
// SAFETY: Offset is known to describe the location of the value, and
13281353
// _holder is preventing mutable aliasing
13291354
Ok((unsafe { &*value }).to_object(py).into_ptr())
13301355
}
13311356

1332-
fn pyo3_get_value<ClassT: PyClass, FieldT: IntoPy<Py<PyAny>> + Clone, const OFFSET: usize>(
1357+
fn pyo3_get_value<
1358+
ClassT: PyClass,
1359+
FieldT: IntoPy<Py<PyAny>> + Clone,
1360+
Offset: OffsetCalculator<ClassT, FieldT>,
1361+
>(
13331362
py: Python<'_>,
13341363
obj: *mut ffi::PyObject,
13351364
) -> PyResult<*mut ffi::PyObject> {
@@ -1340,13 +1369,9 @@ fn pyo3_get_value<ClassT: PyClass, FieldT: IntoPy<Py<PyAny>> + Clone, const OFFS
13401369
.try_borrow()?
13411370
};
13421371

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

1349-
// SAFETY: OFFSET is known to describe the location of the value, and
1374+
// SAFETY: Offset is known to describe the location of the value, and
13501375
// _holder is preventing mutable aliasing
13511376
Ok((unsafe { &*value }).clone().into_py(py).into_ptr())
13521377
}

src/impl_/pyclass/lazy_type_object.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88
use crate::{
99
exceptions::PyRuntimeError,
1010
ffi,
11+
impl_::pyclass::MaybeRuntimePyMethodDef,
1112
pyclass::{create_type_object, PyClassTypeObject},
1213
sync::{GILOnceCell, GILProtected},
1314
types::PyType,
@@ -149,7 +150,15 @@ impl LazyTypeObjectInner {
149150
let mut items = vec![];
150151
for class_items in items_iter {
151152
for def in class_items.methods {
152-
if let PyMethodDefType::ClassAttribute(attr) = def {
153+
let built_method;
154+
let method = match def {
155+
MaybeRuntimePyMethodDef::Runtime(builder) => {
156+
built_method = builder();
157+
&built_method
158+
}
159+
MaybeRuntimePyMethodDef::Static(method) => method,
160+
};
161+
if let PyMethodDefType::ClassAttribute(attr) = method {
153162
match (attr.meth)(py) {
154163
Ok(val) => items.push((attr.name, val)),
155164
Err(err) => {

src/pyclass/create_type_object.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
pycell::PyClassObject,
66
pyclass::{
77
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
8-
tp_dealloc_with_gc, PyClassItemsIter,
8+
tp_dealloc_with_gc, MaybeRuntimePyMethodDef, PyClassItemsIter,
99
},
1010
pymethods::{Getter, Setter},
1111
trampoline::trampoline,
@@ -317,6 +317,14 @@ impl PyTypeBuilder {
317317
self.push_slot(slot.slot, slot.pfunc);
318318
}
319319
for method in items.methods {
320+
let built_method;
321+
let method = match method {
322+
MaybeRuntimePyMethodDef::Runtime(builder) => {
323+
built_method = builder();
324+
&built_method
325+
}
326+
MaybeRuntimePyMethodDef::Static(method) => method,
327+
};
320328
self.pymethod_def(method);
321329
}
322330
}

0 commit comments

Comments
 (0)