@@ -3,12 +3,14 @@ use crate::exceptions::PyStopAsyncIteration;
3
3
use crate :: gil:: LockGIL ;
4
4
use crate :: impl_:: panic:: PanicTrap ;
5
5
use crate :: impl_:: pycell:: { PyClassObject , PyClassObjectLayout } ;
6
+ use crate :: internal:: get_slot:: { get_slot, TP_BASE , TP_CLEAR , TP_TRAVERSE } ;
6
7
use crate :: pycell:: impl_:: PyClassBorrowChecker as _;
7
8
use crate :: pycell:: { PyBorrowError , PyBorrowMutError } ;
8
9
use crate :: pyclass:: boolean_struct:: False ;
9
10
use crate :: types:: any:: PyAnyMethods ;
10
11
#[ cfg( feature = "gil-refs" ) ]
11
- use crate :: types:: { PyModule , PyType } ;
12
+ use crate :: types:: PyModule ;
13
+ use crate :: types:: PyType ;
12
14
use crate :: {
13
15
ffi, Bound , DowncastError , Py , PyAny , PyClass , PyClassInitializer , PyErr , PyObject , PyRef ,
14
16
PyRefMut , PyResult , PyTraverseError , PyTypeCheck , PyVisit , Python ,
@@ -20,6 +22,8 @@ use std::os::raw::{c_int, c_void};
20
22
use std:: panic:: { catch_unwind, AssertUnwindSafe } ;
21
23
use std:: ptr:: null_mut;
22
24
25
+ use super :: trampoline;
26
+
23
27
/// Python 3.8 and up - __ipow__ has modulo argument correctly populated.
24
28
#[ cfg( Py_3_8 ) ]
25
29
#[ repr( transparent) ]
@@ -277,6 +281,7 @@ pub unsafe fn _call_traverse<T>(
277
281
impl_ : fn ( & T , PyVisit < ' _ > ) -> Result < ( ) , PyTraverseError > ,
278
282
visit : ffi:: visitproc ,
279
283
arg : * mut c_void ,
284
+ current_traverse : ffi:: traverseproc ,
280
285
) -> c_int
281
286
where
282
287
T : PyClass ,
@@ -291,6 +296,11 @@ where
291
296
let trap = PanicTrap :: new ( "uncaught panic inside __traverse__ handler" ) ;
292
297
let lock = LockGIL :: during_traverse ( ) ;
293
298
299
+ let super_retval = call_super_traverse ( slf, visit, arg, current_traverse) ;
300
+ if super_retval != 0 {
301
+ return super_retval;
302
+ }
303
+
294
304
// SAFETY: `slf` is a valid Python object pointer to a class object of type T, and
295
305
// traversal is running so no mutations can occur.
296
306
let class_object: & PyClassObject < T > = & * slf. cast ( ) ;
@@ -330,6 +340,127 @@ where
330
340
retval
331
341
}
332
342
343
+ /// Call super-type traverse method, if necessary.
344
+ ///
345
+ /// Adapted from <https://github.com/cython/cython/blob/7acfb375fb54a033f021b0982a3cd40c34fb22ac/Cython/Utility/ExtensionTypes.c#L386>
346
+ ///
347
+ /// TODO: There are possible optimizations over looking up the base type in this way
348
+ /// - if the base type is known in this module, can potentially look it up directly in module state
349
+ /// (when we have it)
350
+ /// - if the base type is a Python builtin, can jut call the C function directly
351
+ /// - if the base type is a PyO3 type defined in the same module, can potentially do similar to
352
+ /// tp_alloc where we solve this at compile time
353
+ unsafe fn call_super_traverse (
354
+ obj : * mut ffi:: PyObject ,
355
+ visit : ffi:: visitproc ,
356
+ arg : * mut c_void ,
357
+ current_traverse : ffi:: traverseproc ,
358
+ ) -> c_int {
359
+ // SAFETY: in this function here it's ok to work with raw type objects `ffi::Py_TYPE`
360
+ // because the GC is running and so
361
+ // - (a) we cannot do refcounting and
362
+ // - (b) the type of the object cannot change.
363
+ let mut ty = ffi:: Py_TYPE ( obj) ;
364
+ let mut traverse: Option < ffi:: traverseproc > ;
365
+
366
+ // First find the current type by the current_traverse function
367
+ loop {
368
+ traverse = get_slot ( ty, TP_TRAVERSE ) ;
369
+ if traverse == Some ( current_traverse) {
370
+ break ;
371
+ }
372
+ ty = get_slot ( ty, TP_BASE ) ;
373
+ if ty. is_null ( ) {
374
+ // FIXME: return an error if current type not in the MRO? Should be impossible.
375
+ return 0 ;
376
+ }
377
+ }
378
+
379
+ // Get first base which has a different traverse function
380
+ while traverse == Some ( current_traverse) {
381
+ ty = get_slot ( ty, TP_BASE ) ;
382
+ if ty. is_null ( ) {
383
+ break ;
384
+ }
385
+ traverse = get_slot ( ty, TP_TRAVERSE ) ;
386
+ }
387
+
388
+ // If we found a type with a different traverse function, call it
389
+ if let Some ( traverse) = traverse {
390
+ return traverse ( obj, visit, arg) ;
391
+ }
392
+
393
+ // FIXME same question as cython: what if the current type is not in the MRO?
394
+ 0
395
+ }
396
+
397
+ /// Calls an implementation of __clear__ for tp_clear
398
+ pub unsafe fn _call_clear (
399
+ slf : * mut ffi:: PyObject ,
400
+ impl_ : for <' py > unsafe fn ( Python < ' py > , * mut ffi:: PyObject ) -> PyResult < ( ) > ,
401
+ current_clear : ffi:: inquiry ,
402
+ ) -> c_int {
403
+ trampoline:: trampoline ( move |py| {
404
+ let super_retval = call_super_clear ( py, slf, current_clear) ;
405
+ if super_retval != 0 {
406
+ return Err ( PyErr :: fetch ( py) ) ;
407
+ }
408
+ impl_ ( py, slf) ?;
409
+ Ok ( 0 )
410
+ } )
411
+ }
412
+
413
+ /// Call super-type traverse method, if necessary.
414
+ ///
415
+ /// Adapted from <https://github.com/cython/cython/blob/7acfb375fb54a033f021b0982a3cd40c34fb22ac/Cython/Utility/ExtensionTypes.c#L386>
416
+ ///
417
+ /// TODO: There are possible optimizations over looking up the base type in this way
418
+ /// - if the base type is known in this module, can potentially look it up directly in module state
419
+ /// (when we have it)
420
+ /// - if the base type is a Python builtin, can jut call the C function directly
421
+ /// - if the base type is a PyO3 type defined in the same module, can potentially do similar to
422
+ /// tp_alloc where we solve this at compile time
423
+ unsafe fn call_super_clear (
424
+ py : Python < ' _ > ,
425
+ obj : * mut ffi:: PyObject ,
426
+ current_clear : ffi:: inquiry ,
427
+ ) -> c_int {
428
+ let mut ty = PyType :: from_borrowed_type_ptr ( py, ffi:: Py_TYPE ( obj) ) ;
429
+ let mut clear: Option < ffi:: inquiry > ;
430
+
431
+ // First find the current type by the current_clear function
432
+ loop {
433
+ clear = ty. get_slot ( TP_CLEAR ) ;
434
+ if clear == Some ( current_clear) {
435
+ break ;
436
+ }
437
+ let base = ty. get_slot ( TP_BASE ) ;
438
+ if base. is_null ( ) {
439
+ // FIXME: return an error if current type not in the MRO? Should be impossible.
440
+ return 0 ;
441
+ }
442
+ ty = PyType :: from_borrowed_type_ptr ( py, base) ;
443
+ }
444
+
445
+ // Get first base which has a different clear function
446
+ while clear == Some ( current_clear) {
447
+ let base = ty. get_slot ( TP_BASE ) ;
448
+ if base. is_null ( ) {
449
+ break ;
450
+ }
451
+ ty = PyType :: from_borrowed_type_ptr ( py, base) ;
452
+ clear = ty. get_slot ( TP_CLEAR ) ;
453
+ }
454
+
455
+ // If we found a type with a different clear function, call it
456
+ if let Some ( clear) = clear {
457
+ return clear ( obj) ;
458
+ }
459
+
460
+ // FIXME same question as cython: what if the current type is not in the MRO?
461
+ 0
462
+ }
463
+
333
464
// Autoref-based specialization for handling `__next__` returning `Option`
334
465
335
466
pub struct IterBaseTag ;
0 commit comments