@@ -22,6 +22,7 @@ const funcs = @import("functions.zig");
22
22
const PyError = @import ("errors.zig" ).PyError ;
23
23
const PyMemAllocator = @import ("mem.zig" ).PyMemAllocator ;
24
24
const tramp = @import ("trampoline.zig" );
25
+ const unlimited = @import ("unlimited_api.zig" );
25
26
26
27
/// For a given Pydust class definition, return the encapsulating PyType struct.
27
28
pub fn PyTypeStruct (comptime definition : type ) type {
@@ -42,9 +43,17 @@ pub fn Type(comptime name: [:0]const u8, comptime definition: type) type {
42
43
};
43
44
44
45
const bases = Bases (definition );
45
- const attrs = Attributes (definition );
46
46
const slots = Slots (definition , name );
47
47
48
+ const flags = blk : {
49
+ var flags_ : usize = ffi .Py_TPFLAGS_DEFAULT | ffi .Py_TPFLAGS_BASETYPE ;
50
+ if (slots .gc .needsGc ) {
51
+ flags_ |= ffi .Py_TPFLAGS_HAVE_GC ;
52
+ }
53
+
54
+ break :blk flags_ ;
55
+ };
56
+
48
57
pub fn init (module : py.PyModule ) PyError ! py.PyObject {
49
58
var basesPtr : ? * ffi.PyObject = null ;
50
59
if (bases .bases .len > 0 ) {
@@ -61,7 +70,7 @@ pub fn Type(comptime name: [:0]const u8, comptime definition: type) type {
61
70
.name = qualifiedName .ptr ,
62
71
.basicsize = @sizeOf (PyTypeStruct (definition )),
63
72
.itemsize = 0 ,
64
- .flags = ffi . Py_TPFLAGS_DEFAULT | ffi . Py_TPFLAGS_BASETYPE ,
73
+ .flags = flags ,
65
74
.slots = @constCast (slots .slots .ptr ),
66
75
};
67
76
@@ -105,10 +114,24 @@ fn Slots(comptime definition: type, comptime name: [:0]const u8) type {
105
114
const properties = Properties (definition );
106
115
const doc = Doc (definition , name );
107
116
const richcmp = RichCompare (definition );
117
+ const gc = GC (definition );
108
118
109
119
/// Slots populated in the PyType
110
120
pub const slots : [:empty ]const ffi.PyType_Slot = blk : {
111
- var slots_ : [:empty ]const ffi.PyType_Slot = &.{};
121
+ var slots_ : [:empty ]const ffi.PyType_Slot = &.{ffi.PyType_Slot {
122
+ .slot = ffi .Py_tp_dealloc ,
123
+ .pfunc = @constCast (& tp_dealloc ),
124
+ }};
125
+
126
+ if (gc .needsGc ) {
127
+ slots_ = slots_ ++ .{ ffi.PyType_Slot {
128
+ .slot = ffi .Py_tp_clear ,
129
+ .pfunc = @constCast (& gc .tp_clear ),
130
+ }, ffi.PyType_Slot {
131
+ .slot = ffi .Py_tp_traverse ,
132
+ .pfunc = @constCast (& gc .tp_traverse ),
133
+ } };
134
+ }
112
135
113
136
if (doc .docLen != 0 ) {
114
137
slots_ = slots_ ++ .{ffi.PyType_Slot {
@@ -305,7 +328,7 @@ fn Slots(comptime definition: type, comptime name: [:0]const u8) type {
305
328
/// Note: tp_del is deprecated in favour of tp_finalize.
306
329
///
307
330
/// See https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_finalize.
308
- fn tp_finalize (pyself : * ffi.PyObject ) void {
331
+ fn tp_finalize (pyself : * ffi.PyObject ) callconv ( .C ) void {
309
332
// The finalize slot shouldn't alter any exception that is currently set.
310
333
// So it's recommended we save the existing one (if any) and restore it afterwards.
311
334
// NOTE(ngates): we may want to move this logic to PyErr if it happens more?
@@ -320,6 +343,22 @@ fn Slots(comptime definition: type, comptime name: [:0]const u8) type {
320
343
ffi .PyErr_Restore (error_type , error_value , error_tb );
321
344
}
322
345
346
+ /// Deallocte the type. Carefully handling cases where class implements `__del__`
347
+ fn tp_dealloc (pyself : * ffi.PyObject ) callconv (.C ) void {
348
+ const typeObj = py .type_ (pyself );
349
+ unlimited .finalizeFromDealloc (pyself );
350
+
351
+ if (gc .needsGc ) {
352
+ ffi .PyObject_GC_UnTrack (pyself );
353
+ }
354
+
355
+ _ = gc .tp_clear (pyself );
356
+
357
+ const freeFn : * const fn (* anyopaque ) void = @alignCast (@ptrCast (typeObj .getSlot (ffi .Py_tp_free ).? ));
358
+ freeFn (pyself );
359
+ typeObj .decref ();
360
+ }
361
+
323
362
fn bf_getbuffer (pyself : * ffi.PyObject , view : * ffi.Py_buffer , flags : c_int ) callconv (.C ) c_int {
324
363
// In case of any error, the view.obj field must be set to NULL.
325
364
view .obj = null ;
@@ -455,6 +494,182 @@ fn Doc(comptime definition: type, comptime name: [:0]const u8) type {
455
494
};
456
495
}
457
496
497
+ fn GC (comptime definition : type ) type {
498
+ const VisitProc = * const fn (* ffi.PyObject , * anyopaque ) callconv (.C ) c_int ;
499
+
500
+ return struct {
501
+ const needsGc = classNeedsGc (definition );
502
+
503
+ fn classNeedsGc (comptime CT : type ) bool {
504
+ inline for (@typeInfo (CT ).Struct .fields ) | field | {
505
+ if (typeNeedsGc (field .type )) {
506
+ return true ;
507
+ }
508
+ }
509
+ return false ;
510
+ }
511
+
512
+ fn typeNeedsGc (comptime FT : type ) bool {
513
+ return switch (@typeInfo (FT )) {
514
+ .Pointer = > | p | @typeInfo (p .child ) == .Struct and (p .child == ffi .PyObject or typeNeedsGc (p .child )),
515
+ .Struct = > blk : {
516
+ if (State .findDefinition (FT )) | def | {
517
+ break :blk switch (def .type ) {
518
+ .attribute = > typeNeedsGc (@typeInfo (FT ).Struct .fields [0 ].type ),
519
+ .property = > classNeedsGc (FT ),
520
+ .class , .module = > false ,
521
+ };
522
+ } else {
523
+ break :blk @hasField (FT , "obj" ) and @hasField (std .meta .fieldInfo (FT , .obj ).type , "py" ) or FT == py .PyObject ;
524
+ }
525
+ },
526
+ .Optional = > | o | (@typeInfo (o .child ) == .Struct or @typeInfo (o .child ) == .Pointer ) and typeNeedsGc (o .child ),
527
+ else = > return false ,
528
+ };
529
+ }
530
+
531
+ fn tp_clear (pyself : * ffi.PyObject ) callconv (.C ) c_int {
532
+ var self : * PyTypeStruct (definition ) = @ptrCast (pyself );
533
+ clearFields (self .state );
534
+ return 0 ;
535
+ }
536
+
537
+ fn clearFields (class : anytype ) void {
538
+ inline for (@typeInfo (@TypeOf (class )).Struct .fields ) | field | {
539
+ clear (@field (class , field .name ));
540
+ }
541
+ }
542
+
543
+ fn clear (obj : anytype ) void {
544
+ const fieldType = @TypeOf (obj );
545
+ switch (@typeInfo (fieldType )) {
546
+ .Pointer = > | p | if (@typeInfo (p .child ) == .Struct ) {
547
+ if (p .child == ffi .PyObject ) {
548
+ pyClear (obj );
549
+ }
550
+ if (State .findDefinition (fieldType )) | def | {
551
+ if (def .type == .class ) {
552
+ pyClear (py .object (obj ).py );
553
+ }
554
+ }
555
+ },
556
+ .Struct = > {
557
+ if (State .findDefinition (fieldType )) | def | {
558
+ switch (def .type ) {
559
+ .attribute = > clear (@field (obj , @typeInfo (fieldType ).Struct .fields [0 ].name )),
560
+ .property = > clearFields (obj ),
561
+ .class , .module = > {},
562
+ }
563
+ } else {
564
+ if (@hasField (fieldType , "obj" ) and @hasField (std .meta .fieldInfo (fieldType , .obj ).type , "py" )) {
565
+ pyClear (obj .obj .py );
566
+ }
567
+
568
+ if (fieldType == py .PyObject ) {
569
+ pyClear (obj .py );
570
+ }
571
+ }
572
+ },
573
+ .Optional = > | o | if (@typeInfo (o .child ) == .Struct or @typeInfo (o .child ) == .Pointer ) {
574
+ if (obj == null ) {
575
+ return ;
576
+ }
577
+
578
+ clear (obj .? );
579
+ },
580
+ else = > {},
581
+ }
582
+ }
583
+
584
+ inline fn pyClear (obj : * ffi.PyObject ) void {
585
+ var objRef = @constCast (& obj );
586
+ const objOld = objRef .* ;
587
+ objRef .* = undefined ;
588
+ py .decref (objOld );
589
+ }
590
+
591
+ /// Visit all members of pyself. We visit all PyObjects that this object references
592
+ fn tp_traverse (pyself : * ffi.PyObject , visit : VisitProc , arg : * anyopaque ) callconv (.C ) c_int {
593
+ if (pyVisit (py .type_ (pyself ).obj .py , visit , arg )) | ret | {
594
+ return ret ;
595
+ }
596
+
597
+ const self : * const PyTypeStruct (definition ) = @ptrCast (pyself );
598
+ if (traverseFields (self .state , visit , arg )) | ret | {
599
+ return ret ;
600
+ }
601
+ return 0 ;
602
+ }
603
+
604
+ fn traverseFields (class : anytype , visit : VisitProc , arg : * anyopaque ) ? c_int {
605
+ inline for (@typeInfo (@TypeOf (class )).Struct .fields ) | field | {
606
+ if (traverse (@field (class , field .name ), visit , arg )) | ret | {
607
+ return ret ;
608
+ }
609
+ }
610
+ return null ;
611
+ }
612
+
613
+ fn traverse (obj : anytype , visit : VisitProc , arg : * anyopaque ) ? c_int {
614
+ const fieldType = @TypeOf (obj );
615
+ switch (@typeInfo (@TypeOf (obj ))) {
616
+ .Pointer = > | p | if (@typeInfo (p .child ) == .Struct ) {
617
+ if (p .child == ffi .PyObject ) {
618
+ if (pyVisit (obj , visit , arg )) | ret | {
619
+ return ret ;
620
+ }
621
+ }
622
+ if (State .findDefinition (fieldType )) | def | {
623
+ if (def .type == .class ) {
624
+ if (pyVisit (py .object (obj ).py , visit , arg )) | ret | {
625
+ return ret ;
626
+ }
627
+ }
628
+ }
629
+ },
630
+ .Struct = > if (State .findDefinition (fieldType )) | def | {
631
+ switch (def .type ) {
632
+ .attribute = > if (traverse (@field (obj , @typeInfo (@TypeOf (obj )).Struct .fields [0 ].name ), visit , arg )) | ret | {
633
+ return ret ;
634
+ },
635
+ .property = > if (traverseFields (obj , visit , arg )) | ret | {
636
+ return ret ;
637
+ },
638
+ }
639
+ } else {
640
+ if (@hasField (fieldType , "obj" ) and @hasField (std .meta .fieldInfo (fieldType , .obj ).type , "py" )) {
641
+ if (pyVisit (obj .obj .py , visit , arg )) | ret | {
642
+ return ret ;
643
+ }
644
+ }
645
+
646
+ if (fieldType == py .PyObject ) {
647
+ if (pyVisit (obj .py , visit , arg )) | ret | {
648
+ return ret ;
649
+ }
650
+ }
651
+ },
652
+ .Optional = > | o | if (@typeInfo (o .child ) == .Struct or @typeInfo (o .child ) == .Pointer ) {
653
+ if (obj == null ) {
654
+ return null ;
655
+ }
656
+
657
+ if (traverse (obj .? , visit , arg )) | ret | {
658
+ return ret ;
659
+ }
660
+ },
661
+ else = > return null ,
662
+ }
663
+ return null ;
664
+ }
665
+
666
+ inline fn pyVisit (obj : * ffi.PyObject , visit : VisitProc , arg : * anyopaque ) ? c_int {
667
+ const ret = visit (obj , arg );
668
+ return if (ret != 0 ) ret else null ;
669
+ }
670
+ };
671
+ }
672
+
458
673
fn Members (comptime definition : type ) type {
459
674
return struct {
460
675
const count = State .countFieldsWithType (definition , .attribute );
0 commit comments