1 /*
2 Permission is hereby granted, free of charge, to any person obtaining a copy of
3 this software and associated documentation files (the "Software"), to deal in
4 the Software without restriction, including without limitation the rights to
5 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
6 of the Software, and to permit persons to whom the Software is furnished to do
7 so, subject to the following conditions:
8 
9 The above copyright notice and this permission notice shall be included in all
10 copies or substantial portions of the Software.
11 
12 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 SOFTWARE.
19 */
20 
21 module pyd.references;
22 
23 import std.traits;
24 import std.typetuple;
25 
26 import deimos.python.Python;
27 import util.multi_index;
28 import util.typeinfo;
29 
30 // s/wrapped_class_object!T/PyObject/
31 
32 /**
33  * A useful check for whether a given class has been wrapped. Mainly used by
34  * the conversion functions (see make_object.d), but possibly useful elsewhere.
35  */
36 template is_wrapped(T) {
37     alias pyd_references!T.Mapping Mapping;
38 
39     static if(!is(Mapping.TypeInfoType!T == T)) {
40         alias is_wrapped!(Mapping.TypeInfoType!T) is_wrapped;
41     }else{
42         bool is_wrapped = false;
43     }
44 }
45 
46 /// _
47 // wrapped_class_type
48 template PydTypeObject(T) {
49     alias pyd_references!T.Mapping Mapping;
50 
51     static if(!is(Mapping.TypeInfoType!T == T)) {
52         alias PydTypeObject!(Mapping.TypeInfoType!T) PydTypeObject;
53     }else{
54         // The type object, an instance of PyType_Type
55         PyTypeObject PydTypeObject;
56     }
57 }
58 
59 template IsRefParam(alias p) {
60     enum IsRefParam = p == ParameterStorageClass.ref_ ||
61         p == ParameterStorageClass.out_;
62 }
63 
64 template FnHasRefParams(Fn) {
65     alias anySatisfy!(IsRefParam, ParameterStorageClassTuple!(Fn)) 
66         FnHasRefParams;
67 }
68 
69 struct DFn_Py_Mapping {
70     const(void)* d;
71     PyObject* py;
72     TypeInfo d_typeinfo;
73     TypeInfo[] params_info;
74     uint functionAttributes;
75     string linkage;
76     Constness constness;
77 
78     this(Fn)(Fn d, PyObject* py) if(isFunctionPointer!Fn) {
79         static assert(!FnHasRefParams!Fn, 
80                 "Pyd cannot handle ref or out parameters at this time");
81         this.d = DKey(d);
82         this.d_typeinfo = typeid(TypeInfoType!Fn);
83         this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Fn))
84             .elements; 
85         this.py = py;
86         this.functionAttributes = .functionAttributes!Fn;
87         this.linkage = functionLinkage!Fn;
88         this.constness = .constness!Fn;
89     }
90 
91     template TypeInfoType(T) if(isFunctionPointer!T) {
92         alias Unqual!(
93                 SetFunctionAttributes!(T, functionLinkage!T, 
94                     FunctionAttribute.none)) TypeInfoType;
95     }
96 
97     public static const(void)* DKey(T)(T t) 
98         if(isFunctionPointer!T) {
99             return cast(const(void)*) t;
100         }
101 
102     public T FromKey(T)() {
103         return cast(T) this.d;
104     }
105 }
106 
107 struct DDg_Py_Mapping {
108     const(void)*[2] d;
109     PyObject* py;
110     TypeInfo d_typeinfo;
111     TypeInfo[] params_info;
112     uint functionAttributes;
113     string linkage;
114     Constness constness;
115 
116     this(Dg)(Dg d, PyObject* py) if(isDelegate!Dg) {
117         static assert(!FnHasRefParams!Dg, 
118                 "Pyd cannot handle ref or out parameters at this time");
119         this.d = DKey(d);
120         this.d_typeinfo = typeid(TypeInfoType!Dg);
121         this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Dg))
122             .elements; 
123         this.py = py;
124         this.functionAttributes = .functionAttributes!Dg;
125         this.linkage = functionLinkage!Dg;
126         this.constness = .constness!Dg;
127     }
128 
129     template TypeInfoType(T) {
130         alias Unqual!(
131                 SetFunctionAttributes!(T, functionLinkage!T, 
132                     FunctionAttribute.none)) TypeInfoType;
133     }
134 
135     public static const(void)*[2] DKey(T)(T t) 
136         if(isDelegate!T) {
137             typeof(return) key;
138             key[0] = cast(const(void)*) t.ptr;
139             key[1] = cast(const(void)*) t.funcptr;
140             return key;
141         }
142 
143     public T FromKey(T)() {
144         T x;
145         x.ptr = cast(void*) d[0];
146         x.funcptr = cast(typeof(x.funcptr)) d[1];
147         return x;
148     }
149 }
150 
151 struct DStruct_Py_Mapping {
152     const(void)* d;
153     PyObject* py;
154     TypeInfo d_typeinfo;
155     Constness constness;
156 
157     this(S)(S d, PyObject* py) if(isPointer!S && 
158             is(pointerTarget!S == struct)) {
159         this.d = DKey(d);
160         this.d_typeinfo = typeid(TypeInfoType!S);
161         this.py = py;
162         this.constness = .constness!S;
163     }
164 
165     template TypeInfoType(T) if(isPointer!T && is(pointerTarget!T == struct)) {
166         alias Unqual!T TypeInfoType;
167     }
168 
169     public static const(void)* DKey(T)(T t) 
170         if(isPointer!T && is(pointerTarget!T == struct)) {
171             return cast(const(void)*) t;
172     }
173 
174     public T FromKey(T)() {
175         return cast(T) this.d;
176     }
177 }
178 
179 struct DClass_Py_Mapping {
180     const(void)* d;
181     PyObject* py;
182     TypeInfo d_typeinfo;
183     Constness constness;
184 
185     this(S)(S d, PyObject* py) if(is(S == class)) {
186         this.d = cast(const(void)*) d;
187         this.d_typeinfo = typeid(TypeInfoType!S);
188         this.py = py;
189         this.constness = .constness!S;
190     }
191 
192     template TypeInfoType(T) if(is(T == class)) {
193             alias Unqual!T TypeInfoType;
194     }
195 
196     public static const(void)* DKey(T)(T t) 
197         if(is(T == class)) {
198             return cast(const(void)*) t;
199     }
200 
201     public T FromKey(T)() {
202         return cast(T) this.d;
203     }
204 }
205 
206 /// A bidirectional mapping of a pyobject and the d object associated to it.
207 /// On the python side, these are weak references; we don't want to prevent
208 /// python from reclaiming objects it is finished with. As such, on the D side,
209 /// if you take PyObject*s out of here and store them for an extended time 
210 /// elsewhere, be sure to increment the reference count. 
211 /// On the D side, we have strong references, but that is incidental to the GC.
212 /// If you stick d objects not allocated with the GC, there will probably be 
213 /// leaks.
214 /// We use malloc for the container's structure because we can't use the GC 
215 /// inside a destructor and we need to use this container there.
216 template reference_container(Mapping) {
217     alias MultiIndexContainer!(Mapping, IndexedBy!(
218                 HashedUnique!("a.d"), 
219                 HashedUnique!("a.py")), 
220             MallocAllocator, MutableView)
221         Container;
222     Container _reference_container = null;
223 
224     @property reference_container() {
225         if(!_reference_container) {
226             _reference_container = new Container();
227             Py_AtExit(&clear);
228         }
229         return _reference_container;
230     }
231 
232     extern(C) void clear() {
233         if(_reference_container) {
234             _reference_container.get_index!0 .clear();
235         }
236     }
237 }
238 
239 // A mapping of all GC references that are being held by Python.
240 template pyd_references(T) {
241     static if(isDelegate!T) {
242         alias DDg_Py_Mapping Mapping;
243     }else static if (isFunctionPointer!T) {
244         alias DFn_Py_Mapping Mapping;
245     }else static if(isPointer!T && is(pointerTarget!T == struct)) {
246         alias DStruct_Py_Mapping Mapping;
247     }else static if (is(T == class)) {
248         alias DClass_Py_Mapping Mapping;
249     }else static assert(0, format("type %s cannot sent to pyd, because ??",
250                 T.stringof));
251     alias reference_container!Mapping container;
252 }
253 
254 void set_pyd_mapping(T) (PyObject* _self, T t) {
255     import std.stdio;
256     alias pyd_references!T.Mapping Mapping;
257     alias pyd_references!T.container container;
258     
259     Mapping mapping = Mapping(t, _self); 
260     auto py_index = container.get_index!1;
261     auto range = py_index.equalRange(_self);
262     if (range.empty) {
263         auto count = py_index.insert(mapping);
264         enforce(count != 0,
265                 format("could not add py reference %x for T=%s, t=%s", 
266                     _self, T.stringof,  Mapping.DKey(t)));
267     }else{
268         auto count = py_index.replace(PSR(range).front, mapping);
269         enforce(count != 0,
270                 format("could not update py reference %x for T=%s, t=%s",
271                     _self, T.stringof,  Mapping.DKey(t)));
272     }
273 }
274 
275 void remove_pyd_mapping(T)(PyObject* self) {
276     import std.range;
277     import std.stdio;
278     alias pyd_references!T.Mapping Mapping;
279     alias pyd_references!T.container container;
280     
281     auto py_index = container.get_index!1;
282     auto range = py_index.equalRange(self);
283     if(!range.empty) {
284         py_index.remove(take(PSR(range),1));
285     }
286 }
287 
288 
289 /**
290  * Returns the object contained in a Python wrapped type.
291  */
292 T get_d_reference(T) (PyObject* _self) {
293     alias pyd_references!T.container container;
294     alias pyd_references!T.Mapping Mapping;
295     import thread = pyd.thread;
296 
297     thread.ensureAttached();
298 
299     enforce(is_wrapped!T,
300             format(
301                 "Error extracting D object: Type %s is not wrapped.",
302                 typeid(T).toString()));
303     enforce(_self !is null,
304             "Error: trying to find D reference for null PyObject*!");
305 
306     auto py_index = container.get_index!1;
307     auto range = py_index.equalRange(_self);
308 
309     enforce(!range.empty,
310             "Error extracting D object: reference not found!");
311     // don't cast away type!
312     static if(is(T == class)) {
313         auto tif = cast(TypeInfo_Class) range.front.d_typeinfo;
314         auto t_info = typeid(Mapping.TypeInfoType!T);
315         bool found = false;
316         while(tif) {
317             if(t_info == tif) {
318                 found = true;
319                 break;
320             }
321             tif = tif.base;
322         }
323 
324         enforce(found, 
325             format(
326                 "Type mismatch extracting D object: found: %s, required: %s",
327                 range.front.d_typeinfo,
328                 typeid(Mapping.TypeInfoType!T)));
329 
330     }else{
331         enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo,
332             format(
333                 "Type mismatch extracting D object: found: %s, required: %s",
334                 range.front.d_typeinfo,
335                 typeid(Mapping.TypeInfoType!T)));
336     }
337     // don't cast away constness!
338     // mutable => const, etc, okay
339     enforce(constCompatible(range.front.constness, constness!T),
340             format(
341                 "constness mismatch required: %s, found: %s", 
342                 constness_ToString(constness!T),
343                 constness_ToString(range.front.constness)));
344     static if(isFunctionPointer!T || isDelegate!T) {
345         // don't cast away linkage!
346         enforce(range.front.linkage == functionLinkage!T,
347                 format(
348                     "trying to convert a extern(\"%s\") "
349                     "%s to extern(\"%s\")",
350                     range.front.linkage, (isDelegate!T ? "delegate":"function"),
351                     functionLinkage!T));
352         // losing function attributes is ok,
353         // giving a function new ones, not so much
354         enforce(
355                 (~range.front.functionAttributes & functionAttributes!T) == 0,
356                 format(
357                     "trying to convert %s%s to %s",
358                     SetFunctionAttributes!(T, 
359                         functionLinkage!T, 
360                         FunctionAttribute.none).stringof,
361                     attrs_to_string(range.front.functionAttributes),
362                     T.stringof));
363     }
364 
365     return range.front.FromKey!T();
366 }
367 
368 /// If the passed D reference has an existing Python object, return a borrowed
369 /// reference to it. Otherwise, return null.
370 PyObject_BorrowedRef* get_python_reference(T) (T t) {
371     alias pyd_references!T.container container;
372     alias pyd_references!T.Mapping Mapping;
373 
374     auto d_index = container.get_index!0;
375     auto range = d_index.equalRange(Mapping.DKey(t));
376     if(range.empty) return null;
377     return borrowed(range.front.py);
378 }
379 
380 /**
381  * Returns a new Python object of a wrapped type.
382  */
383 // WrapPyObject_FromTypeAndObject
384 // WrapPyObject_FromObject
385 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) {
386     if (!type) {
387         type = &PydTypeObject!T;
388     }
389     if (is_wrapped!(T)) {
390         // If this object is already wrapped, get the existing object.
391         PyObject_BorrowedRef* obj_p = get_python_reference(t);
392         if (obj_p) {
393             return Py_INCREF(obj_p);
394         }
395         // Otherwise, allocate a new object
396         PyObject* obj = type.tp_new(type, null, null);
397         // Set the contained instance
398         set_pyd_mapping(obj, t);
399         return obj;
400     } else {
401         PyErr_SetString(PyExc_RuntimeError, 
402                 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr);
403         return null;
404     }
405 }
406