1"""
2Check that the @cython.no_gc_clear decorator disables generation of the
3tp_clear slot so that __dealloc__ will still see the original reference
4contents.
5
6Discussed here: http://article.gmane.org/gmane.comp.python.cython.devel/14986
7"""
8
9cimport cython
10from cpython.ref cimport PyObject, Py_TYPE
11
12# Pull tp_clear for PyTypeObject as I did not find another way to access it
13# from Cython code.
14
15cdef extern from *:
16    ctypedef struct PyTypeObject:
17        void (*tp_clear)(object)
18
19    ctypedef struct __pyx_CyFunctionObject:
20        PyObject* func_closure
21
22
23def is_tp_clear_null(obj):
24    return (<PyTypeObject*>Py_TYPE(obj)).tp_clear is NULL
25
26
27def is_closure_tp_clear_null(func):
28    return is_tp_clear_null(
29        <object>(<__pyx_CyFunctionObject*>func).func_closure)
30
31
32@cython.no_gc_clear
33cdef class DisableTpClear:
34    """
35    An extension type that has a tp_clear method generated to test that it
36    actually clears the references to NULL.
37
38    >>> uut = DisableTpClear()
39    >>> is_tp_clear_null(uut)
40    True
41    >>> uut.call_tp_clear()
42    >>> type(uut.requires_cleanup) == list
43    True
44    >>> del uut
45    """
46
47    cdef public object requires_cleanup
48
49    def __cinit__(self):
50        self.requires_cleanup = [
51                "Some object that needs cleaning in __dealloc__"]
52
53    def call_tp_clear(self):
54        cdef PyTypeObject *pto = Py_TYPE(self)
55        if pto.tp_clear != NULL:
56            pto.tp_clear(self)
57
58
59cdef class ReallowTpClear(DisableTpClear):
60    """
61    >>> import gc
62    >>> obj = ReallowTpClear()
63    >>> is_tp_clear_null(obj)
64    False
65
66    >>> obj.attr = obj  # create hard reference cycle
67    >>> del obj; _ignore = gc.collect()
68
69    # Problem: cannot really validate that the cycle was cleaned up without using weakrefs etc...
70    """
71    cdef public object attr
72
73
74def test_closure_without_clear(str x):
75    """
76    >>> c = test_closure_without_clear('abc')
77    >>> is_tp_clear_null(c)
78    False
79    >>> is_closure_tp_clear_null(c)
80    True
81    >>> c('cba')
82    'abcxyzcba'
83    """
84    def c(str s):
85        return x + 'xyz' + s
86    return c
87
88
89def test_closure_with_clear(list x):
90    """
91    >>> c = test_closure_with_clear(list('abc'))
92    >>> is_tp_clear_null(c)
93    False
94    >>> is_closure_tp_clear_null(c)
95    False
96    >>> c('cba')
97    'abcxyzcba'
98    """
99    def c(str s):
100        return ''.join(x) + 'xyz' + s
101    return c
102