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 import std.string: format;
26 import std.exception: enforce;
27 
28 import pyd.func_wrap;
29 import deimos.python.Python;
30 import pyd.util.multi_index;
31 import pyd.util.typeinfo;
32 
33 // s/wrapped_class_object!T/PyObject/
34 
35 /**
36  * A useful check for whether a given class has been wrapped. Mainly used by
37  * the conversion functions (see make_object.d), but possibly useful elsewhere.
38  */
39 template is_wrapped(T) {
40     alias pyd_references!T.Mapping Mapping;
41 
42     static if(!is(Mapping.TypeInfoType!T == T)) {
43         alias is_wrapped!(Mapping.TypeInfoType!T) is_wrapped;
44     }else{
45         bool is_wrapped = false;
46     }
47 }
48 
49 /// _
50 // wrapped_class_type
51 template PydTypeObject(T) {
52     alias pyd_references!T.Mapping Mapping;
53 
54     static if(!is(Mapping.TypeInfoType!T == T)) {
55         alias PydTypeObject!(Mapping.TypeInfoType!T) PydTypeObject;
56     }else{
57         // The type object, an instance of PyType_Type
58         __gshared PyTypeObject PydTypeObject;
59     }
60 }
61 
62 template IsRefParam(alias p) {
63     enum IsRefParam = p == ParameterStorageClass.ref_ ||
64         p == ParameterStorageClass.out_;
65 }
66 
67 template FnHasRefParams(Fn) {
68     alias anySatisfy!(IsRefParam, ParameterStorageClassTuple!(Fn))
69         FnHasRefParams;
70 }
71 
72 struct DFn_Py_Mapping {
73     const(void)* d;
74     PyObject* py;
75     TypeInfo d_typeinfo;
76     TypeInfo[] params_info;
77     uint functionAttributes;
78     string linkage;
79     Constness constness;
80 
81     this(Fn)(Fn d, PyObject* py) if(isFunctionPointer!Fn) {
82         static assert(!FnHasRefParams!Fn,
83                 "Pyd cannot handle ref or out parameters at this time");
84         this.d = DKey(d);
85         this.d_typeinfo = typeid(TypeInfoType!Fn);
86         this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Fn))
87             .elements;
88         this.py = py;
89         this.functionAttributes = .functionAttributes!Fn;
90         this.linkage = functionLinkage!Fn;
91         this.constness = .constness!Fn;
92     }
93 
94     template TypeInfoType(T) if(isFunctionPointer!T) {
95         alias Unqual!(
96                 SetFunctionAttributes!(T, functionLinkage!T,
97                     FunctionAttribute.none)) TypeInfoType;
98     }
99 
100     public static const(void)* DKey(T)(T t)
101         if(isFunctionPointer!T) {
102             return cast(const(void)*) t;
103         }
104 
105     public T FromKey(T)() {
106         return cast(T) this.d;
107     }
108 }
109 
110 struct DDg_Py_Mapping {
111     const(void)*[2] d;
112     PyObject* py;
113     TypeInfo d_typeinfo;
114     TypeInfo[] params_info;
115     uint functionAttributes;
116     string linkage;
117     Constness constness;
118 
119     this(Dg)(Dg d, PyObject* py) if(isDelegate!Dg) {
120         static assert(!FnHasRefParams!Dg,
121                 "Pyd cannot handle ref or out parameters at this time");
122         this.d = DKey(d);
123         this.d_typeinfo = typeid(TypeInfoType!Dg);
124         this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Dg))
125             .elements;
126         this.py = py;
127         this.functionAttributes = .functionAttributes!Dg;
128         this.linkage = functionLinkage!Dg;
129         this.constness = .constness!Dg;
130     }
131 
132     template TypeInfoType(T) {
133         alias Unqual!(
134                 SetFunctionAttributes!(T, functionLinkage!T,
135                     FunctionAttribute.none)) TypeInfoType;
136     }
137 
138     public static const(void)*[2] DKey(T)(T t)
139         if(isDelegate!T) {
140             typeof(return) key;
141             key[0] = cast(const(void)*) t.ptr;
142             key[1] = cast(const(void)*) t.funcptr;
143             return key;
144         }
145 
146     public T FromKey(T)() {
147         T x;
148         x.ptr = cast(void*) d[0];
149         x.funcptr = cast(typeof(x.funcptr)) d[1];
150         return x;
151     }
152 }
153 
154 struct DStruct_Py_Mapping {
155     const(void)* d;
156     PyObject* py;
157     TypeInfo d_typeinfo;
158     Constness constness;
159 
160     this(S)(S d, PyObject* py) if(isPointer!S &&
161             is(PointerTarget!S == struct)) {
162         this.d = DKey(d);
163         this.d_typeinfo = typeid(TypeInfoType!S);
164         this.py = py;
165         this.constness = .constness!S;
166     }
167 
168     template TypeInfoType(T) if(isPointer!T && is(PointerTarget!T == struct)) {
169         alias Unqual!T TypeInfoType;
170     }
171 
172     public static const(void)* DKey(T)(T t)
173         if(isPointer!T && is(PointerTarget!T == struct)) {
174             return cast(const(void)*) t;
175     }
176 
177     public T FromKey(T)() {
178         return cast(T) this.d;
179     }
180 }
181 
182 struct DClass_Py_Mapping {
183     const(void)* d;
184     PyObject* py;
185     TypeInfo d_typeinfo;
186     Constness constness;
187 
188     this(S)(S d, PyObject* py) if(is(S == class)) {
189         this.d = cast(const(void)*) d;
190         this.d_typeinfo = typeid(TypeInfoType!S);
191         this.py = py;
192         this.constness = .constness!S;
193     }
194 
195     template TypeInfoType(T) if(is(T == class)) {
196             alias Unqual!T TypeInfoType;
197     }
198 
199     public static const(void)* DKey(T)(T t)
200         if(is(T == class)) {
201             return cast(const(void)*) t;
202     }
203 
204     public T FromKey(T)() {
205         return cast(T) this.d;
206     }
207 }
208 
209 /// A bidirectional mapping of a pyobject and the d object associated to it.
210 /// On the python side, these are weak references; we don't want to prevent
211 /// python from reclaiming objects it is finished with. As such, on the D side,
212 /// if you take PyObject*s out of here and store them for an extended time
213 /// elsewhere, be sure to increment the reference count.
214 /// On the D side, we have strong references, but that is incidental to the GC.
215 /// If you stick d objects not allocated with the GC, there will probably be
216 /// leaks.
217 /// We use malloc for the container's structure because we can't use the GC
218 /// inside a destructor and we need to use this container there.
219 template reference_container(Mapping) {
220     static if(is(Mapping == DStruct_Py_Mapping)) {
221         // See #104 for the reason for the hash functions below
222         alias MultiIndexContainer!(
223             Mapping,
224             IndexedBy!(
225                 HashedNonUnique!("a.d", "cast(size_t) *cast(const void**) &a"), "d",
226                 HashedUnique!("a.py", "cast(size_t) *cast(const void**) &a"), "python"
227                 ),
228             MallocAllocator, MutableView)
229             Container;
230     }else{
231         alias MultiIndexContainer!(Mapping, IndexedBy!(
232                     HashedUnique!("a.d"), "d",
233                     HashedUnique!("a.py"), "python"),
234                 MallocAllocator, MutableView)
235             Container;
236     }
237     Container _reference_container = null;
238 
239     @property reference_container() {
240         if(!_reference_container) {
241             _reference_container = Container.create();
242             Py_AtExit(&clear);
243         }
244         return _reference_container;
245     }
246 
247     extern(C) void clear() {
248         if(_reference_container) {
249             _reference_container.d.clear();
250         }
251     }
252 }
253 
254 // A mapping of all GC references that are being held by Python.
255 template pyd_references(T) {
256     static if(isDelegate!T) {
257         alias DDg_Py_Mapping Mapping;
258     }else static if (isFunctionPointer!T) {
259         alias DFn_Py_Mapping Mapping;
260     }else static if(isPointer!T && is(PointerTarget!T == struct)) {
261         alias DStruct_Py_Mapping Mapping;
262     }else static if (is(T == class)) {
263         alias DClass_Py_Mapping Mapping;
264     }else static assert(0, format("type %s cannot sent to pyd, because ??",
265                 T.stringof));
266     alias reference_container!Mapping container;
267 }
268 
269 void set_pyd_mapping(T) (PyObject* _self, T t) {
270     alias pyd_references!T.Mapping Mapping;
271     alias pyd_references!T.container container;
272 
273     Mapping mapping = Mapping(t, _self);
274     auto py_index = container.python;
275     auto range = py_index.equalRange(_self);
276     if (range.empty) {
277         auto count = py_index.insert(mapping);
278         enforce(count != 0,
279                 format("could not add py reference %x for T=%s, t=%s",
280                     _self, T.stringof,  Mapping.DKey(t)));
281     }else{
282         auto count = py_index.replace(PSR(range).front, mapping);
283         enforce(count != 0,
284                 format("could not update py reference %x for T=%s, t=%s",
285                     _self, T.stringof,  Mapping.DKey(t)));
286     }
287 }
288 
289 void remove_pyd_mapping(T)(PyObject* self) {
290     import std.range;
291     import std.stdio;
292     alias pyd_references!T.Mapping Mapping;
293     alias pyd_references!T.container container;
294 
295     auto py_index = container.python;
296     auto range = py_index.equalRange(self);
297     if(!range.empty) {
298         py_index.remove(take(PSR(range),1));
299     }
300 }
301 
302 bool isConversionAddingFunctionAttributes(
303         uint fromTypeFunctionAttributes,
304         uint toTypeFunctionAttributes) {
305     return (~(fromTypeFunctionAttributes | StrippedFunctionAttributes) & toTypeFunctionAttributes) != 0;
306 }
307 
308 
309 /**
310  * Returns the object contained in a Python wrapped type.
311  */
312 T get_d_reference(T) (PyObject* _self) {
313     alias pyd_references!T.container container;
314     alias pyd_references!T.Mapping Mapping;
315     import thread = pyd.thread;
316 
317     thread.ensureAttached();
318 
319     enforce(is_wrapped!T,
320             format(
321                 "Error extracting D object: Type %s is not wrapped.",
322                 typeid(T).toString()));
323     enforce(_self !is null,
324             "Error: trying to find D reference for null PyObject*!");
325 
326     auto py_index = container.python;
327     auto range = py_index.equalRange(_self);
328 
329     enforce(!range.empty,
330             "Error extracting D object: reference not found!");
331     // don't cast away type!
332     static if(is(T == class)) {
333         auto tif = cast(TypeInfo_Class) range.front.d_typeinfo;
334         auto t_info = typeid(Mapping.TypeInfoType!T);
335         bool found = false;
336         while(tif) {
337             if(t_info == tif) {
338                 found = true;
339                 break;
340             }
341             tif = tif.base;
342         }
343 
344         enforce(found,
345             format(
346                 "Type mismatch extracting D object: found: %s, required: %s",
347                 range.front.d_typeinfo,
348                 typeid(Mapping.TypeInfoType!T)));
349 
350     }else{
351         enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo,
352             format(
353                 "Type mismatch extracting D object: found: %s, required: %s",
354                 range.front.d_typeinfo,
355                 typeid(Mapping.TypeInfoType!T)));
356     }
357     // don't cast away constness!
358     // mutable => const, etc, okay
359     enforce(constCompatible(range.front.constness, constness!T),
360             format(
361                 "constness mismatch required: %s, found: %s",
362                 constness_ToString(constness!T),
363                 constness_ToString(range.front.constness)));
364     static if(isFunctionPointer!T || isDelegate!T) {
365         // don't cast away linkage!
366         enforce(range.front.linkage == functionLinkage!T,
367                 format(
368                     "trying to convert a extern(\"%s\") " ~
369                     "%s to extern(\"%s\")",
370                     range.front.linkage, (isDelegate!T ? "delegate":"function"),
371                     functionLinkage!T));
372         // losing function attributes is ok,
373         // giving a function new ones, not so much
374         enforce(
375                 !isConversionAddingFunctionAttributes(
376                     range.front.functionAttributes, functionAttributes!T),
377                 format(
378                     "trying to convert %s%s to %s",
379                     SetFunctionAttributes!(T,
380                         functionLinkage!T,
381                         FunctionAttribute.none).stringof,
382                     attrs_to_string(range.front.functionAttributes),
383                     T.stringof));
384     }
385 
386     return range.front.FromKey!T();
387 }
388 
389 /// If the passed D reference has an existing Python object, return a borrowed
390 /// reference to it. Otherwise, return null.
391 PyObject_BorrowedRef* get_python_reference(T) (T t) {
392     alias pyd_references!T.container container;
393     alias pyd_references!T.Mapping Mapping;
394 
395     auto d_index = container.d;
396     auto range = d_index.equalRange(Mapping.DKey(t));
397     static if(is(Mapping == DStruct_Py_Mapping)) {
398         findMatchingType!T(range);
399     }
400     if(range.empty) return null;
401     return borrowed(range.front.py);
402 }
403 
404 void findMatchingType(T, R)(ref R range) {
405     while(!range.empty) {
406         if(range.front.d_typeinfo == typeid(T)) {
407             break;
408         }
409         range.popFront();
410     }
411 }
412 
413 /**
414  * Returns a new Python object of a wrapped type.
415  */
416 // WrapPyObject_FromTypeAndObject
417 // WrapPyObject_FromObject
418 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) {
419     if (!type) {
420         type = &PydTypeObject!T;
421     }
422     if (is_wrapped!(T)) {
423         // If this object is already wrapped, get the existing object.
424         PyObject_BorrowedRef* obj_p = get_python_reference(t);
425         if (obj_p) {
426             return Py_INCREF(obj_p);
427         }
428         // Otherwise, allocate a new object
429         PyObject* obj = type.tp_new(type, null, null);
430         // Set the contained instance
431         set_pyd_mapping(obj, t);
432         return obj;
433     } else {
434         PyErr_SetString(PyExc_RuntimeError,
435                 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr);
436         return null;
437     }
438 }