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 util.multi_index;
31 import 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         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     alias MultiIndexContainer!(Mapping, IndexedBy!(
221                 HashedUnique!("a.d"), "d",
222                 HashedUnique!("a.py"), "python"),
223             MallocAllocator, MutableView)
224         Container;
225     Container _reference_container = null;
226 
227     @property reference_container() {
228         if(!_reference_container) {
229             _reference_container = new Container();
230             Py_AtExit(&clear);
231         }
232         return _reference_container;
233     }
234 
235     extern(C) void clear() {
236         if(_reference_container) {
237             _reference_container.d.clear();
238         }
239     }
240 }
241 
242 // A mapping of all GC references that are being held by Python.
243 template pyd_references(T) {
244     static if(isDelegate!T) {
245         alias DDg_Py_Mapping Mapping;
246     }else static if (isFunctionPointer!T) {
247         alias DFn_Py_Mapping Mapping;
248     }else static if(isPointer!T && is(PointerTarget!T == struct)) {
249         alias DStruct_Py_Mapping Mapping;
250     }else static if (is(T == class)) {
251         alias DClass_Py_Mapping Mapping;
252     }else static assert(0, format("type %s cannot sent to pyd, because ??",
253                 T.stringof));
254     alias reference_container!Mapping container;
255 }
256 
257 void set_pyd_mapping(T) (PyObject* _self, T t) {
258     import std.stdio;
259     alias pyd_references!T.Mapping Mapping;
260     alias pyd_references!T.container container;
261 
262     Mapping mapping = Mapping(t, _self);
263     auto py_index = container.python;
264     auto range = py_index.equalRange(_self);
265     if (range.empty) {
266         auto count = py_index.insert(mapping);
267         enforce(count != 0,
268                 format("could not add py reference %x for T=%s, t=%s",
269                     _self, T.stringof,  Mapping.DKey(t)));
270     }else{
271         auto count = py_index.replace(PSR(range).front, mapping);
272         enforce(count != 0,
273                 format("could not update py reference %x for T=%s, t=%s",
274                     _self, T.stringof,  Mapping.DKey(t)));
275     }
276 }
277 
278 void remove_pyd_mapping(T)(PyObject* self) {
279     import std.range;
280     import std.stdio;
281     alias pyd_references!T.Mapping Mapping;
282     alias pyd_references!T.container container;
283 
284     auto py_index = container.python;
285     auto range = py_index.equalRange(self);
286     if(!range.empty) {
287         py_index.remove(take(PSR(range),1));
288     }
289 }
290 
291 bool isConversionAddingFunctionAttributes(
292         uint fromTypeFunctionAttributes,
293         uint toTypeFunctionAttributes) {
294     return (~(fromTypeFunctionAttributes | StrippedFunctionAttributes) & toTypeFunctionAttributes) != 0;
295 }
296 
297 
298 /**
299  * Returns the object contained in a Python wrapped type.
300  */
301 T get_d_reference(T) (PyObject* _self) {
302     alias pyd_references!T.container container;
303     alias pyd_references!T.Mapping Mapping;
304     import thread = pyd.thread;
305 
306     thread.ensureAttached();
307 
308     enforce(is_wrapped!T,
309             format(
310                 "Error extracting D object: Type %s is not wrapped.",
311                 typeid(T).toString()));
312     enforce(_self !is null,
313             "Error: trying to find D reference for null PyObject*!");
314 
315     auto py_index = container.python;
316     auto range = py_index.equalRange(_self);
317 
318     enforce(!range.empty,
319             "Error extracting D object: reference not found!");
320     // don't cast away type!
321     static if(is(T == class)) {
322         auto tif = cast(TypeInfo_Class) range.front.d_typeinfo;
323         auto t_info = typeid(Mapping.TypeInfoType!T);
324         bool found = false;
325         while(tif) {
326             if(t_info == tif) {
327                 found = true;
328                 break;
329             }
330             tif = tif.base;
331         }
332 
333         enforce(found,
334             format(
335                 "Type mismatch extracting D object: found: %s, required: %s",
336                 range.front.d_typeinfo,
337                 typeid(Mapping.TypeInfoType!T)));
338 
339     }else{
340         enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo,
341             format(
342                 "Type mismatch extracting D object: found: %s, required: %s",
343                 range.front.d_typeinfo,
344                 typeid(Mapping.TypeInfoType!T)));
345     }
346     // don't cast away constness!
347     // mutable => const, etc, okay
348     enforce(constCompatible(range.front.constness, constness!T),
349             format(
350                 "constness mismatch required: %s, found: %s",
351                 constness_ToString(constness!T),
352                 constness_ToString(range.front.constness)));
353     static if(isFunctionPointer!T || isDelegate!T) {
354         // don't cast away linkage!
355         enforce(range.front.linkage == functionLinkage!T,
356                 format(
357                     "trying to convert a extern(\"%s\") " ~
358                     "%s to extern(\"%s\")",
359                     range.front.linkage, (isDelegate!T ? "delegate":"function"),
360                     functionLinkage!T));
361         // losing function attributes is ok,
362         // giving a function new ones, not so much
363         enforce(
364                 !isConversionAddingFunctionAttributes(
365                     range.front.functionAttributes, functionAttributes!T),
366                 format(
367                     "trying to convert %s%s to %s",
368                     SetFunctionAttributes!(T,
369                         functionLinkage!T,
370                         FunctionAttribute.none).stringof,
371                     attrs_to_string(range.front.functionAttributes),
372                     T.stringof));
373     }
374 
375     return range.front.FromKey!T();
376 }
377 
378 /// If the passed D reference has an existing Python object, return a borrowed
379 /// reference to it. Otherwise, return null.
380 PyObject_BorrowedRef* get_python_reference(T) (T t) {
381     alias pyd_references!T.container container;
382     alias pyd_references!T.Mapping Mapping;
383 
384     auto d_index = container.d;
385     auto range = d_index.equalRange(Mapping.DKey(t));
386     if(range.empty) return null;
387     return borrowed(range.front.py);
388 }
389 
390 /**
391  * Returns a new Python object of a wrapped type.
392  */
393 // WrapPyObject_FromTypeAndObject
394 // WrapPyObject_FromObject
395 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) {
396     if (!type) {
397         type = &PydTypeObject!T;
398     }
399     if (is_wrapped!(T)) {
400         // If this object is already wrapped, get the existing object.
401         PyObject_BorrowedRef* obj_p = get_python_reference(t);
402         if (obj_p) {
403             return Py_INCREF(obj_p);
404         }
405         // Otherwise, allocate a new object
406         PyObject* obj = type.tp_new(type, null, null);
407         // Set the contained instance
408         set_pyd_mapping(obj, t);
409         return obj;
410     } else {
411         PyErr_SetString(PyExc_RuntimeError,
412                 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr);
413         return null;
414     }
415 }
416