1 /**
2    This module contains D code for the contract tests between Python
3    and its C API regarding user-defined types.
4  */
5 module contract.udt;
6 
7 
8 import python;
9 
10 
11 extern(C):
12 
13 
14 package PyObject* simple_struct_func(PyObject* self, PyObject *args) nothrow @nogc {
15 
16     static struct MyType {
17         mixin PyObjectHead;
18         int i = 2;
19         double d = 3;
20     }
21 
22     // either this of PyGetSetDef
23     static PyMemberDef[3] members;
24     members[0].name = cast(typeof(PyMemberDef.name)) &"the_int"[0];
25     members[0].type = MemberType.Int;
26     members[0].offset = MyType.i.offsetof;
27     members[1].name = cast(typeof(PyMemberDef.name)) &"the_double"[0];
28     members[1].type = MemberType.Double;
29     members[1].offset = MyType.d.offsetof;
30 
31     static PyTypeObject type;
32 
33     if(type == type.init) {
34         type.tp_name = &"MyType"[0];
35         type.tp_basicsize = MyType.sizeof;
36         type.tp_members = &members[0];
37         type.tp_flags = TypeFlags.Default;
38 
39         if(PyType_Ready(&type) < 0) {
40             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
41             return null;
42         }
43     }
44 
45     auto obj = pyObjectNew!MyType(&type);
46     obj.i = 42;
47     obj.d = 33.3;
48 
49     return cast(typeof(return)) obj;
50 }
51 
52 
53 package PyObject* twice_struct_func(PyObject* self, PyObject *args) nothrow @nogc {
54 
55     import python.cooked: pyMethodDef;
56 
57     static struct Twice {
58         mixin PyObjectHead;
59         int i;
60         int twice() @safe @nogc pure nothrow const {
61             return i * 2;
62         }
63     }
64 
65     static extern(C) PyObject* twice(Twice* self, PyObject* args) {
66         return PyLong_FromLong(self.twice);
67     }
68 
69     if(PyTuple_Size(args) != 1) {
70         PyErr_SetString(PyExc_TypeError, &"Must be used with one parameter"[0]);
71         return null;
72     }
73 
74     auto arg = PyTuple_GetItem(args, 0);
75     if(arg is null) {
76         PyErr_SetString(PyExc_TypeError, &"Could not get 1st argument"[0]);
77         return null;
78     }
79 
80     static PyMethodDef[2] methods;
81     // can't use a function literal because they're not allowed to be extern(C)
82     methods[0] = pyMethodDef!("twice")(&twice);
83 
84     static PyTypeObject type;
85 
86     if(type == type.init) {
87         type.tp_name = &"Twice"[0];
88         type.tp_basicsize = Twice.sizeof;
89         type.tp_methods = &methods[0];
90         type.tp_flags = TypeFlags.Default;
91 
92         if(PyType_Ready(&type) < 0) {
93             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
94             return null;
95         }
96     }
97 
98     auto obj = pyObjectNew!Twice(&type);
99     obj.i = cast(int) PyLong_AsLong(arg);
100 
101     return cast(typeof(return)) obj;
102 }
103 
104 
105 struct StructDefaultCtor {
106     int i = 42;
107 }
108 
109 struct StructUserCtor {
110     int i = 42;
111     double d = 33.3;
112     string s = "foobar";
113     private int _shouldBeOk;
114 
115     this(int i, string s) {
116         this.i = i;
117         this.d = 77.7;
118         this.s = s;
119     }
120 
121     this(int i, double d, string s) {
122         this.i = i;
123         this.d = d;
124         this.s = s;
125     }
126 }
127 
128 
129 package PyObject* struct_getset(PyObject* self, PyObject *args) nothrow @nogc {
130 
131     static struct StructGetSet {
132         mixin PyObjectHead;
133         double d;
134         Inner inner;
135 
136         static struct Inner {
137             mixin PyObjectHead;
138             int i;
139             double d;
140             // we don't bother wrapping `s` just to show that what Python sees
141             // doesn't have to be exactly what D does.
142             string s;
143         }
144     }
145 
146     static extern(C) PyObject* getOuterInt(PyObject*, void*) {
147         return PyLong_FromLong(42);
148     }
149 
150     static extern(C) PyObject* getOuterDouble(PyObject* self_, void*) {
151         auto self = cast(StructGetSet*) self_;
152         return PyFloat_FromDouble(self.d);
153     }
154 
155     static extern(C) int setOuterDouble(PyObject* self_, PyObject* value, void*) {
156          auto self = cast(StructGetSet*) self_;
157          self.d = PyFloat_AsDouble(value);
158          return 0;
159     }
160 
161     static extern(C) PyObject* getInnerInt(PyObject* self_, void*) {
162         auto self = cast(StructGetSet.Inner*) self_;
163         return PyLong_FromLong(self.i);
164     }
165 
166     static extern(C) PyObject* getInnerDouble(PyObject* self_, void*) {
167         auto self = cast(StructGetSet.Inner*) self_;
168         return PyFloat_FromDouble(self.d);
169     }
170 
171     static extern(C) int setInnerInt(PyObject* self_, PyObject* value, void*) {
172          auto self = cast(StructGetSet.Inner*) self_;
173          self.i = cast(int) PyLong_AsLong(value);
174          return 0;
175     }
176 
177     static extern(C) int setInnerDouble(PyObject* self_, PyObject* value, void*) {
178          auto self = cast(StructGetSet.Inner*) self_;
179          self.d = PyFloat_AsDouble(value);
180          return 0;
181     }
182 
183     extern(C) static PyObject* reprOuter(PyObject* self_) {
184         import std.conv: text;
185 
186         auto self = cast(StructGetSet*) self_;
187         auto ret = text(*self);
188 
189         return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
190     }
191 
192     extern(C) static PyObject* reprInner(PyObject* self_) {
193         import std.conv: text;
194 
195         auto self = cast(StructGetSet.Inner*) self_;
196         auto ret = text(*self);
197 
198         return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
199     }
200 
201     static PyTypeObject outerType;
202     static PyTypeObject innerType;
203 
204     static extern(C) PyObject* getInner(PyObject* self_, void*) {
205         auto self = cast(StructGetSet*) self_;
206         auto innerObj = cast(PyObject*) &self.inner;
207         pyIncRef(innerObj);
208         return innerObj;
209     }
210 
211     static PyGetSetDef[10] outerGetSets;
212     static PyGetSetDef[10] innerGetSets;
213 
214     if(outerType == outerType.init) {
215 
216         outerGetSets[0].name = cast(typeof(PyGetSetDef.name)) &"i"[0];
217         outerGetSets[0].get = &getOuterInt;
218         // outerGetSets[0].set is null so will raise AttributeError
219         outerGetSets[1].name = cast(typeof(PyGetSetDef.name)) &"d"[0];
220         outerGetSets[1].get = &getOuterDouble;
221         outerGetSets[1].set = &setOuterDouble;
222         outerGetSets[2].name = cast(typeof(PyGetSetDef.name)) &"inner"[0];
223         outerGetSets[2].get = &getInner;
224 
225         outerType.tp_name = &"StructGetSet"[0];
226         outerType.tp_basicsize = StructGetSet.sizeof;
227         outerType.tp_flags = TypeFlags.Default;
228         outerType.tp_repr = outerType.tp_str = &reprOuter;
229         outerType.tp_getset = &outerGetSets[0];
230 
231         if(PyType_Ready(&outerType) < 0) {
232             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
233             return null;
234         }
235 
236         innerGetSets[0].name = cast(typeof(PyGetSetDef.name)) &"i"[0];
237         innerGetSets[0].get = &getInnerInt;
238         innerGetSets[0].set = &setInnerInt;
239         innerGetSets[1].name = cast(typeof(PyGetSetDef.name)) &"d"[0];
240         innerGetSets[1].get = &getInnerDouble;
241         innerGetSets[1].set = &setInnerDouble;
242 
243         innerType.tp_name = &"StructGetSet.Inner"[0];
244         innerType.tp_basicsize = StructGetSet.Inner.sizeof;
245         innerType.tp_flags = TypeFlags.Default;
246         innerType.tp_repr = innerType.tp_str = &reprInner;
247         innerType.tp_getset = &innerGetSets[0];
248 
249         if(PyType_Ready(&innerType) < 0) {
250             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
251             return null;
252         }
253     }
254 
255     auto ret = pyObjectNew!StructGetSet(&outerType);
256     ret.d = 3.3;
257     ret.inner.i = 999;
258     ret.inner.d = 777.77;
259     // this line below is important, it's the equivalent of pyObjectNew above
260     PyObject_Init(cast(PyObject*) &ret.inner, &innerType);
261 
262     return cast(typeof(return)) ret;
263 }