1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
|
# mode: run
# tag: cpp, cpp17, no-cpp-locals
# no-cpp-locals because the test is already run with it explicitly set
# cython: cpp_locals=True
cimport cython
from libcpp cimport bool as cppbool
cdef extern from *:
r"""
static void print_C_destructor();
class C {
public:
C() = delete; // look! No default constructor
C(int x, bool print_destructor=true) : x(x), print_destructor(print_destructor) {}
C(C&& rhs) : x(rhs.x), print_destructor(rhs.print_destructor) {
rhs.print_destructor = false; // moved-from instances are deleted silently
}
// also test that we don't require the assignment operator
C& operator=(C&& rhs) = delete;
C(const C& rhs) = delete;
C& operator=(const C& rhs) = default;
~C() {
if (print_destructor) print_C_destructor();
}
int getX() const { return x; }
private:
int x;
bool print_destructor;
};
C make_C(int x) {
return C(x);
}
"""
cdef cppclass C:
C(int)
C(int, cppbool)
int getX() const
C make_C(int) except + # needs a temp to receive
# this function just makes sure the output from the destructor can be captured by doctest
cdef void print_C_destructor "print_C_destructor" () with gil:
print("~C()")
def maybe_assign_infer(assign, value, do_print):
"""
>>> maybe_assign_infer(True, 5, True)
5
~C()
>>> maybe_assign_infer(False, 0, True)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
>>> maybe_assign_infer(False, 0, False) # no destructor call here
"""
if assign:
x = C(value)
if do_print:
print(x.getX())
def maybe_assign_cdef(assign, value):
"""
>>> maybe_assign_cdef(True, 5)
5
~C()
>>> maybe_assign_cdef(False, 0)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
"""
cdef C x
if assign:
x = C(value)
print(x.getX())
def maybe_assign_annotation(assign, value):
"""
>>> maybe_assign_annotation(True, 5)
5
~C()
>>> maybe_assign_annotation(False, 0)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
"""
x: C
if assign:
x = C(value)
print(x.getX())
def maybe_assign_directive1(assign, value):
"""
>>> maybe_assign_directive1(True, 5)
5
~C()
>>> maybe_assign_directive1(False, 0)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
"""
x = cython.declare(C)
if assign:
x = C(value)
print(x.getX())
@cython.locals(x=C)
def maybe_assign_directive2(assign, value):
"""
>>> maybe_assign_directive2(True, 5)
5
~C()
>>> maybe_assign_directive2(False, 0)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
"""
if assign:
x = C(value)
print(x.getX())
def maybe_assign_nocheck(assign, value):
"""
>>> maybe_assign_nocheck(True, 5)
5
~C()
# unfortunately it's quite difficult to test not assigning because there's a decent chance it'll crash
"""
if assign:
x = C(value)
with cython.initializedcheck(False):
print(x.getX())
def uses_temp(value):
"""
needs a temp to handle the result of make_C - still doesn't use the default constructor
>>> uses_temp(10)
10
~C()
"""
x = make_C(value)
print(x.getX())
# c should not be optional - it isn't easy to check this, but we can at least check it compiles
cdef void has_argument(C c):
print(c.getX())
def call_has_argument():
"""
>>> call_has_argument()
50
"""
has_argument(C(50, False))
cdef class HoldsC:
"""
>>> inst = HoldsC(True, False)
>>> inst.getCX()
10
>>> access_from_function_with_different_directive(inst)
10
10
>>> inst.getCX() # it was changed in access_from_function_with_different_directive
20
>>> inst = HoldsC(False, False)
>>> inst.getCX()
Traceback (most recent call last):
...
AttributeError: C++ attribute 'value' is not initialized
>>> access_from_function_with_different_directive(inst)
Traceback (most recent call last):
...
AttributeError: C++ attribute 'value' is not initialized
"""
cdef C value
def __cinit__(self, initialize, print_destructor):
if initialize:
self.value = C(10, print_destructor)
def getCX(self):
return self.value.getX()
cdef acceptC(C& c):
return c.getX()
@cython.cpp_locals(False)
def access_from_function_with_different_directive(HoldsC c):
# doctest is in HoldsC class
print(acceptC(c.value)) # this originally tried to pass a __Pyx_Optional<C> as a C instance
print(c.value.getX())
c.value = C(20, False) # make sure that we can change it too
def dont_test_on_pypy(f):
import sys
if not hasattr(sys, "pypy_version_info"):
return f
@dont_test_on_pypy # non-deterministic destruction
def testHoldsCDestruction(initialize):
"""
>>> testHoldsCDestruction(True)
~C()
>>> testHoldsCDestruction(False) # no destructor
"""
x = HoldsC(initialize, True)
del x
cdef C global_var
def initialize_global_var():
global global_var
global_var = C(-1, False)
def read_global_var():
"""
>>> read_global_var()
Traceback (most recent call last):
...
NameError: C++ global 'global_var' is not initialized
>>> initialize_global_var()
>>> read_global_var()
-1
"""
print(global_var.getX())
|