@@ -131,8 +131,15 @@ impl<T> Clone for PyClassImplCollector<T> {
131
131
132
132
impl < T > Copy for PyClassImplCollector < T > { }
133
133
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
+
134
141
pub struct PyClassItems {
135
- pub methods : & ' static [ PyMethodDefType ] ,
142
+ pub methods : & ' static [ MaybeRuntimePyMethodDef ] ,
136
143
pub slots : & ' static [ ffi:: PyType_Slot ] ,
137
144
}
138
145
@@ -1172,73 +1179,92 @@ pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1172
1179
result
1173
1180
}
1174
1181
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
+
1175
1199
/// Type which uses specialization on impl blocks to determine how to read a field from a Rust pyclass
1176
1200
/// as part of a `#[pyo3(get)]` annotation.
1177
1201
pub struct PyClassGetterGenerator <
1178
1202
// structural information about the field: class type, field type, where the field is within the
1179
1203
// class struct
1180
1204
ClassT : PyClass ,
1181
1205
FieldT ,
1182
- const OFFSET : usize ,
1206
+ Offset : OffsetCalculator < ClassT , FieldT > , // on Rust 1.77+ this could be a const OFFSET: usize
1183
1207
// additional metadata about the field which is used to switch between different implementations
1184
1208
// at compile time
1185
1209
const IS_PY_T : bool ,
1186
1210
const IMPLEMENTS_TOPYOBJECT : bool ,
1187
- > ( PhantomData < ClassT > , PhantomData < FieldT > ) ;
1211
+ > ( PhantomData < ( ClassT , FieldT , Offset ) > ) ;
1188
1212
1189
1213
impl <
1190
1214
ClassT : PyClass ,
1191
1215
FieldT ,
1192
- const OFFSET : usize ,
1216
+ Offset : OffsetCalculator < ClassT , FieldT > ,
1193
1217
const IS_PY_T : bool ,
1194
1218
const IMPLEMENTS_TOPYOBJECT : bool ,
1195
- > PyClassGetterGenerator < ClassT , FieldT , OFFSET , IS_PY_T , IMPLEMENTS_TOPYOBJECT >
1219
+ > PyClassGetterGenerator < ClassT , FieldT , Offset , IS_PY_T , IMPLEMENTS_TOPYOBJECT >
1196
1220
{
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 .
1199
1223
pub const unsafe fn new ( ) -> Self {
1200
- Self ( PhantomData , PhantomData )
1224
+ Self ( PhantomData )
1201
1225
}
1202
1226
}
1203
1227
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 >
1206
1234
{
1207
1235
/// Py<T> fields have a potential optimization to use Python's "struct members" to read
1208
1236
/// the field directly from the struct, rather than using a getter function.
1209
1237
///
1210
1238
/// This is the most efficient operation the Python interpreter could possibly do to
1211
1239
/// 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 {
1213
1241
use crate :: pyclass:: boolean_struct:: private:: Boolean ;
1214
1242
if ClassT :: Frozen :: VALUE {
1215
1243
PyMethodDefType :: StructMember ( ffi:: PyMemberDef {
1216
1244
name : name. as_ptr ( ) ,
1217
1245
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 ,
1220
1247
flags : ffi:: Py_READONLY ,
1221
1248
doc : doc. as_ptr ( ) ,
1222
1249
} )
1223
1250
} else {
1224
1251
PyMethodDefType :: Getter ( crate :: PyGetterDef {
1225
1252
name,
1226
- meth : pyo3_get_value_topyobject :: < ClassT , Py < U > , OFFSET > ,
1253
+ meth : pyo3_get_value_topyobject :: < ClassT , Py < U > , Offset > ,
1227
1254
doc,
1228
1255
} )
1229
1256
}
1230
1257
}
1231
1258
}
1232
1259
1233
1260
/// 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 >
1236
1263
{
1237
1264
pub const fn generate ( & self , name : & ' static CStr , doc : & ' static CStr ) -> PyMethodDefType {
1238
1265
PyMethodDefType :: Getter ( crate :: PyGetterDef {
1239
- // TODO: store &CStr in PyGetterDef etc
1240
1266
name,
1241
- meth : pyo3_get_value_topyobject :: < ClassT , FieldT , OFFSET > ,
1267
+ meth : pyo3_get_value_topyobject :: < ClassT , FieldT , Offset > ,
1242
1268
doc,
1243
1269
} )
1244
1270
}
@@ -1257,8 +1283,8 @@ pub trait PyO3GetField: IntoPy<Py<PyAny>> + Clone {}
1257
1283
impl < T : IntoPy < Py < PyAny > > + Clone > PyO3GetField for T { }
1258
1284
1259
1285
/// 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 >
1262
1288
{
1263
1289
pub const fn generate ( & self , name : & ' static CStr , doc : & ' static CStr ) -> PyMethodDefType
1264
1290
// 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>
1267
1293
FieldT : PyO3GetField ,
1268
1294
{
1269
1295
PyMethodDefType :: Getter ( crate :: PyGetterDef {
1270
- // TODO: store &CStr in PyGetterDef etc
1271
1296
name,
1272
- meth : pyo3_get_value :: < ClassT , FieldT , OFFSET > ,
1297
+ meth : pyo3_get_value :: < ClassT , FieldT , Offset > ,
1273
1298
doc,
1274
1299
} )
1275
1300
}
@@ -1307,7 +1332,11 @@ impl<T: ToPyObject> IsToPyObject<T> {
1307
1332
pub const VALUE : bool = true ;
1308
1333
}
1309
1334
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
+ > (
1311
1340
py : Python < ' _ > ,
1312
1341
obj : * mut ffi:: PyObject ,
1313
1342
) -> PyResult < * mut ffi:: PyObject > {
@@ -1318,18 +1347,18 @@ fn pyo3_get_value_topyobject<ClassT: PyClass, FieldT: ToPyObject, const OFFSET:
1318
1347
. try_borrow ( ) ?
1319
1348
} ;
1320
1349
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 > ( ) } ;
1326
1351
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
1328
1353
// _holder is preventing mutable aliasing
1329
1354
Ok ( ( unsafe { & * value } ) . to_object ( py) . into_ptr ( ) )
1330
1355
}
1331
1356
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
+ > (
1333
1362
py : Python < ' _ > ,
1334
1363
obj : * mut ffi:: PyObject ,
1335
1364
) -> PyResult < * mut ffi:: PyObject > {
@@ -1340,13 +1369,9 @@ fn pyo3_get_value<ClassT: PyClass, FieldT: IntoPy<Py<PyAny>> + Clone, const OFFS
1340
1369
. try_borrow ( ) ?
1341
1370
} ;
1342
1371
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 > ( ) } ;
1348
1373
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
1350
1375
// _holder is preventing mutable aliasing
1351
1376
Ok ( ( unsafe { & * value } ) . clone ( ) . into_py ( py) . into_ptr ( ) )
1352
1377
}
0 commit comments