1 /**
2    A D API for dealing with Python's PyTypeObject
3  */
4 module python.type;
5 
6 
7 import autowrap.reflection: isParameter, BinaryOperator;
8 import python.raw: PyObject;
9 import std.traits: Unqual, isArray, isIntegral, isBoolean, isFloatingPoint, isAggregateType, isCallable;
10 import std.datetime: DateTime, Date;
11 import std.typecons: Tuple;
12 import std.range.primitives: isInputRange;
13 import std.meta: allSatisfy;
14 
15 
16 package enum isPhobos(T) = isDateOrDateTime!T || isTuple!T;
17 package enum isDateOrDateTime(T) = is(Unqual!T == DateTime) || is(Unqual!T == Date);
18 package enum isTuple(T) = is(T: Tuple!A, A...);
19 package enum isUserAggregate(T) = isAggregateType!T && !isPhobos!(T);
20 package enum isNonRangeUDT(T) = isUserAggregate!T && !isInputRange!T;
21 
22 
23 /**
24    A wrapper for `PyTypeObject`.
25 
26    This struct does all of the necessary boilerplate to intialise
27    a `PyTypeObject` for a Python extension type that mimics the D
28    type `T`.
29  */
30 struct PythonType(T) {
31     import python.raw: PyTypeObject, PySequenceMethods, PyMappingMethods;
32     import std.traits: FieldNameTuple, Fields;
33     import std.meta: Alias, staticMap;
34 
35     alias fieldNames = FieldNameTuple!T;
36     alias fieldTypes = Fields!T;
37     enum hasLength = is(typeof({ size_t len = T.init.length; }));
38 
39     static PyTypeObject _pyType;
40     static bool failedToReady;
41 
42     static PyObject* pyObject() {
43         initialise;
44         return failedToReady ? null : cast(PyObject*) &_pyType;
45     }
46 
47     static PyTypeObject* pyType() nothrow {
48         initialise;
49         return failedToReady ? null : &_pyType;
50     }
51 
52     private static void initialise() nothrow {
53         import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags,
54             PyErr_SetString, PyExc_TypeError,
55             PyNumberMethods, PySequenceMethods;
56         import autowrap.reflection: UnaryOperators, BinaryOperators, functionName;
57         import std.traits: arity, hasMember, TemplateOf;
58         import std.meta: Filter;
59         static import std.typecons;
60 
61         if(_pyType != _pyType.init) return;
62 
63         _pyType.tp_name = T.stringof;
64         _pyType.tp_flags = TypeFlags.Default;
65 
66         // FIXME: types are that user aggregates *and* callables
67         static if(isUserAggregate!T) {
68             _pyType.tp_basicsize = PythonClass!T.sizeof;
69             _pyType.tp_getset = getsetDefs;
70             _pyType.tp_methods = methodDefs;
71             _pyType.tp_new = &_py_new;
72             _pyType.tp_repr = &_py_repr;
73             _pyType.tp_init = &_py_init;
74 
75             // special-case std.typecons.Typedef
76             // see: https://issues.dlang.org/show_bug.cgi?id=20117
77             static if(
78                 hasMember!(T, "opCmp")
79                 && !__traits(isSame, TemplateOf!T, std.typecons.Typedef)
80                 && &T.opCmp !is &Object.opCmp
81                 )
82             {
83                 _pyType.tp_richcompare = &PythonOpCmp!T._py_cmp;
84             }
85 
86             static if(hasMember!(T, "opSlice")) {
87                 _pyType.tp_iter = &PythonIter!T._py_iter;
88             }
89 
90             static if(hasMember!(T, "opIndex")) {
91                 if(_pyType.tp_as_mapping is null)
92                     _pyType.tp_as_mapping = new PyMappingMethods;
93                 _pyType.tp_as_mapping.mp_subscript = &PythonSubscript!T._py_index;
94             }
95 
96             enum isPythonableUnary(string op) = op == "+" || op == "-" || op == "~";
97             enum unaryOperators = Filter!(isPythonableUnary, UnaryOperators!T);
98             alias binaryOperators = BinaryOperators!T;
99 
100             static if(unaryOperators.length > 0 || binaryOperators.length > 0) {
101                 _pyType.tp_as_number = new PyNumberMethods;
102                 _pyType.tp_as_sequence = new PySequenceMethods;
103             }
104 
105             static foreach(op; unaryOperators) {
106                 mixin(`_pyType.`, dlangUnOpToPythonSlot(op), ` = &PythonUnaryOperator!(T, op)._py_un_op;`);
107             }
108 
109             static foreach(binOp; binaryOperators) {{
110                 // first get the Python function pointer name
111                 enum slot = dlangBinOpToPythonSlot(binOp.op);
112                 // some of them differ in arity
113                 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`));
114 
115                 // get the function name in PythonBinaryOperator
116                 // `in` is special because the function signature is different
117                 static if(binOp.op == "in") {
118                     enum cFuncName = "_py_in_func";
119                 } else {
120                     static if(slotArity == 2)
121                         enum cFuncName = "_py_bin_func";
122                     else static if(slotArity == 3)
123                         enum cFuncName = "_py_ter_func";
124                     else
125                         static assert("Do not know how to deal with slot " ~ slot);
126                 }
127 
128                 // set the C function that implements this operator
129                 mixin(`_pyType.`, slot, ` = &PythonBinaryOperator!(T, binOp).`, cFuncName, `;`);
130             }}
131 
132 
133         } else static if(isCallable!T) {
134             _pyType.tp_basicsize = PythonCallable!T.sizeof;
135             _pyType.tp_call = &PythonCallable!T._py_call;
136         } else
137             static assert(false, "Don't know what to do for type " ~ T.stringof);
138 
139         static if(hasLength) {
140             if(_pyType.tp_as_sequence is null)
141                 _pyType.tp_as_sequence = new PySequenceMethods;
142             _pyType.tp_as_sequence.sq_length = &_py_length;
143         }
144 
145         if(PyType_Ready(&_pyType) < 0) {
146             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
147             failedToReady = true;
148         }
149     }
150 
151     static if(isUserAggregate!T)
152     private static auto getsetDefs() {
153         import autowrap.reflection: Properties;
154         import python.raw: PyGetSetDef;
155         import std.meta: staticMap, Filter, Alias;
156         import std.traits: isFunction, ReturnType;
157 
158         alias AggMember(string memberName) = Alias!(__traits(getMember, T, memberName));
159         alias memberNames = __traits(allMembers, T);
160         enum isPublic(string memberName) =
161             __traits(getProtection, __traits(getMember, T, memberName)) == "public";
162         alias publicMemberNames = Filter!(isPublic, memberNames);
163         alias members = staticMap!(AggMember, publicMemberNames);
164         alias memberFunctions = Filter!(isFunction, members);
165         alias properties = Properties!memberFunctions;
166 
167         // +1 due to the sentinel
168         static PyGetSetDef[fieldNames.length + properties.length + 1] getsets;
169 
170         // don't bother if already initialised
171         if(getsets != getsets.init) return &getsets[0];
172 
173         // first deal with the public fields
174         static foreach(i; 0 .. fieldNames.length) {
175             getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i];
176             static if(__traits(getProtection, __traits(getMember, T, fieldNames[i])) == "public") {
177                 getsets[i].get = &PythonClass!T.get!i;
178                 getsets[i].set = &PythonClass!T.set!i;
179             }
180         }
181 
182         // then deal with the property functions
183         static foreach(j, property; properties) {{
184             enum i = fieldNames.length + j;
185 
186             getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property);
187 
188             static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) {
189                 static if(is(ReturnType!overload == void))
190                     getsets[i].set = &PythonClass!T.propertySet!(overload);
191                 else
192                     getsets[i].get = &PythonClass!T.propertyGet!(overload);
193             }
194         }}
195 
196         return &getsets[0];
197     }
198 
199     private static auto methodDefs()() {
200         import autowrap.reflection: isProperty;
201         import python.raw: PyMethodDef, MethodArgs;
202         import python.cooked: pyMethodDef, defaultMethodFlags;
203         import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot;
204         import std.traits: isSomeFunction;
205         import std.algorithm: startsWith;
206 
207         alias memberNames = AliasSeq!(__traits(allMembers, T));
208         enum ispublic(string name) = isPublic!(T, name);
209         alias publicMemberNames = Filter!(ispublic, memberNames);
210 
211         enum isRegular(string name) =
212             name != "this"
213             && name != "toHash"
214             && name != "factory"
215             && !name.startsWith("op")
216             && name != "__ctor"
217             ;
218         alias regularMemberNames = Filter!(isRegular, publicMemberNames);
219         alias overloads(string name) = AliasSeq!(__traits(getOverloads, T, name));
220         alias members = staticMap!(overloads, regularMemberNames);
221         alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members));
222 
223         // +1 due to sentinel
224         static PyMethodDef[memberFunctions.length + 1] methods;
225 
226         if(methods != methods.init) return &methods[0];
227 
228         static foreach(i, memberFunction; memberFunctions) {{
229 
230             static if(__traits(isStaticFunction, memberFunction))
231                 enum flags = defaultMethodFlags | MethodArgs.Static;
232             else
233                 enum flags = defaultMethodFlags;
234 
235             methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags)
236                                      (&PythonMethod!(T, memberFunction)._py_method_impl);
237         }}
238 
239         return &methods[0];
240     }
241 
242     import python.raw: Py_ssize_t;
243     private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow {
244 
245         return noThrowable!({
246             assert(self_ !is null);
247             static if(hasLength) {
248                 import python.conv: to;
249                 return self_.to!T.length;
250             } else
251                 return -1;
252         });
253     }
254 
255     private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow {
256 
257         return noThrowable!({
258 
259             import python: pyUnicodeDecodeUTF8;
260             import python.conv: to;
261             import std.string: toStringz;
262             import std.conv: text;
263 
264             assert(self_ !is null);
265             auto ret = text(self_.to!T);
266             return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
267         });
268     }
269 
270     private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow {
271         // nothing to do
272         return 0;
273     }
274 
275     static if(isUserAggregate!T)
276     private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow {
277         return noThrowable!({
278             import autowrap.reflection: FunctionParameters, Parameter;
279             import python.conv: toPython, to;
280             import python.raw: PyTuple_Size, PyTuple_GetItem;
281             import std.traits: hasMember, Unqual;
282             import std.meta: AliasSeq;
283 
284             const numArgs = PyTuple_Size(args);
285 
286             if(numArgs == 0) {
287                 return toPython(userAggregateInit!T);
288             }
289 
290             static if(hasMember!(T, "__ctor"))
291                 alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor"));
292             else
293                 alias constructors = AliasSeq!();
294 
295             static if(constructors.length == 0) {
296                 alias parameter(FieldType) = Parameter!(
297                     FieldType,
298                     "",
299                     void,
300                 );
301                 alias parameters = staticMap!(parameter, fieldTypes);
302                 return pythonConstructor!(T, false /*is variadic*/, parameters)(args, kwargs);
303             } else {
304                 import autowrap.reflection: NumRequiredParameters;
305                 import python.raw: PyErr_SetString, PyExc_TypeError;
306                 import std.traits: Parameters, variadicFunctionStyle, Variadic;
307 
308                 static foreach(constructor; constructors) {{
309 
310                     enum isVariadic = variadicFunctionStyle!constructor == Variadic.typesafe;
311 
312                     if(Parameters!constructor.length == numArgs) {
313                         return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs);
314                     } else if(numArgs >= NumRequiredParameters!constructor
315                               && numArgs <= Parameters!constructor.length)
316                     {
317                         return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs);
318                     }
319                 }}
320 
321                 PyErr_SetString(PyExc_TypeError, "Could not find a suitable constructor");
322                 return null;
323             }
324 
325         });
326     }
327 
328     // Creates a python object from the given arguments by converting them to D
329     // types, calling the D constructor and converting the result to a Python
330     // object.
331     static if(isUserAggregate!T)
332     private static auto pythonConstructor(T, bool isVariadic, P...)(PyObject* args, PyObject* kwargs) {
333         import python.conv: toPython;
334         import std.traits: hasMember;
335 
336         auto dArgs = pythonArgsToDArgs!(isVariadic, P)(args, kwargs);
337 
338         static if(is(T == class)) {
339             static if(hasMember!(T, "__ctor")) {
340                 // When immutable dmd prints an odd error message about not being
341                 // able to modify dobj
342                 static if(is(T == immutable))
343                     auto dobj = new T(dArgs.expand);
344                 else
345                     scope dobj = new T(dArgs.expand);
346             } else
347                 T dobj;
348         } else
349             auto dobj = T(dArgs.expand);
350 
351         return toPython(dobj);
352     }
353 }
354 
355 
356 // From a D operator (e.g. `+`) to a Python function pointer member name
357 private string dlangUnOpToPythonSlot(string op) {
358     enum opToSlot = [
359         "+": "tp_as_number.nb_positive",
360         "-": "tp_as_number.nb_negative",
361         "~": "tp_as_number.nb_invert",
362     ];
363     if(op !in opToSlot) throw new Exception("Unknown unary operator " ~ op);
364     return opToSlot[op];
365 }
366 
367 // From a D operator (e.g. `+`) to a Python function pointer member name
368 private string dlangBinOpToPythonSlot(string op) {
369     enum opToSlot = [
370         "+": "tp_as_number.nb_add",
371         "-": "tp_as_number.nb_subtract",
372         "*": "tp_as_number.nb_multiply",
373         "/": "tp_as_number.nb_divide",
374         "%": "tp_as_number.nb_remainder",
375         "^^": "tp_as_number.nb_power",
376         "&": "tp_as_number.nb_and",
377         "|": "tp_as_number.nb_or",
378         "^": "tp_as_number.nb_xor",
379         "<<": "tp_as_number.nb_lshift",
380         ">>": "tp_as_number.nb_rshift",
381         "~": "tp_as_sequence.sq_concat",
382         "in": "tp_as_sequence.sq_contains",
383     ];
384     if(op !in opToSlot) throw new Exception("Unknown binary operator " ~ op);
385     return opToSlot[op];
386 }
387 
388 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs)
389     if(allSatisfy!(isParameter, P))
390 {
391     import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem;
392     import python.conv: to;
393     import std.typecons: Tuple;
394     import std.meta: staticMap;
395     import std.traits: Unqual;
396     import std.conv: text;
397     import std.exception: enforce;
398 
399     const argsLength = args is null ? 0 : PyTuple_Size(args);
400 
401     alias Type(alias Param) = Param.Type;
402     alias Types = staticMap!(Type, P);
403 
404     // If one or more of the parameters is const/immutable,
405     // it'll be hard to construct it as such, so we Unqual
406     // the types for construction and cast to the appropriate
407     // type when returning.
408     alias MutableTuple = Tuple!(staticMap!(Unqual, Types));
409     alias RetTuple = Tuple!(Types);
410 
411     MutableTuple dArgs;
412 
413     int pythonArgIndex = 0;
414     static foreach(i; 0 .. P.length) {
415 
416         static if(i == P.length - 1 && isVariadic) {  // last parameter and it's a typesafe variadic one
417             // slice the remaining arguments
418             auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args));
419             dArgs[i] = remainingArgs.to!(P[i].Type);
420         } else static if(is(P[i].Default == void)) {
421             // ith parameter is required
422             enforce(i < argsLength,
423                     text(__FUNCTION__, ": not enough Python arguments"));
424             dArgs[i] = PyTuple_GetItem(args, i).to!(typeof(dArgs[i]));
425         } else {
426 
427             if(i < argsLength) {  // regular case
428                 dArgs[i] = PyTuple_GetItem(args, i).to!(P[i].Type);
429             } else {
430                 // Here it gets tricky. The user could have supplied it in
431                 // args positionally or via kwargs
432                 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0],
433                                                P[i].identifier.length,
434                                                null /*errors*/);
435                 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object");
436                 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null;
437                 dArgs[i] = val
438                     ? val.to!(P[i].Type) // use kwargs
439                     : P[i].Default; // use default value
440             }
441         }
442     }
443 
444     return cast(RetTuple) dArgs;
445 }
446 
447 
448 private alias Type(alias A) = typeof(A);
449 
450 
451 /**
452    The C API implementation of a Python method F of aggregate type T
453  */
454 struct PythonMethod(T, alias F) {
455     static extern(C) PyObject* _py_method_impl(PyObject* self,
456                                                PyObject* args,
457                                                PyObject* kwargs)
458         nothrow
459     {
460         return noThrowable!({
461             import python.raw: pyDecRef;
462             import python.conv: toPython, to;
463             import std.traits: Parameters, FunctionAttribute, functionAttributes, Unqual, hasFunctionAttributes;
464 
465             static if(!__traits(isStaticFunction, F))
466                 assert(self !is null,
467                        "Cannot call PythonMethod!" ~ __traits(identifier, F) ~ " on null self");
468 
469             static if(functionAttributes!F & FunctionAttribute.const_)
470                 alias Aggregate = const T;
471             else static if(functionAttributes!F & FunctionAttribute.immutable_)
472                 alias Aggregate = immutable T;
473             else
474                 alias Aggregate = Unqual!T;
475 
476             auto dAggregate = {
477                 // could be null for static member functions
478                 return self is null ? Aggregate.init : self.to!Aggregate;
479             }();
480 
481             // Not sure how else to take `dAggregate` and `F` and call the member
482             // function other than a mixin
483             mixin(`auto ret = callDlangFunction!((Parameters!F args) => dAggregate.`,
484                   __traits(identifier, F), `(args), F)(self, args, kwargs);`);
485 
486             // The member function could have side-effects, we need to copy the changes
487             // back to the Python object.
488             static if(!(functionAttributes!F & FunctionAttribute.const_)) {
489                 auto newSelf = {
490                     return self is null ? self : toPython(dAggregate);
491                 }();
492                 scope(exit) {
493                     if(self !is null) pyDecRef(newSelf);
494                 }
495                 auto pyClassSelf = cast(PythonClass!T*) self;
496                 auto pyClassNewSelf = cast(PythonClass!T*) newSelf;
497 
498                 static foreach(i; 0 .. PythonClass!T.fieldNames.length) {
499                     if(self !is null)
500                         pyClassSelf.set!i(self, pyClassNewSelf.get!i(newSelf));
501                 }
502             }
503 
504             return ret;
505         });
506     }
507 }
508 
509 
510 /**
511    The C API implementation that calls a D function F.
512  */
513 struct PythonFunction(alias F) {
514     static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
515         return noThrowable!(() => callDlangFunction!F(self, args, kwargs));
516     }
517 }
518 
519 
520 private auto noThrowable(alias F, A...)(auto ref A args) {
521     import python.raw: PyErr_SetString, PyExc_RuntimeError;
522     import std.string: toStringz;
523     import std.traits: ReturnType;
524 
525     try {
526         return F(args);
527     } catch(Exception e) {
528         PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz);
529         return ReturnType!F.init;
530     } catch(Error e) {
531         import std.conv: text;
532         try
533             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz);
534         catch(Exception _)
535             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz);
536 
537         return ReturnType!F.init;
538     }
539 }
540 
541 // simple, regular version for functions
542 private auto callDlangFunction(alias F)
543                               (PyObject* self, PyObject* args, PyObject* kwargs)
544 {
545     return callDlangFunction!(F, F)(self, args, kwargs);
546 }
547 
548 // Takes two functions due to how we're calling methods.
549 // One is the original function to reflect on, the other
550 // is the closure that's actually going to be called.
551 private auto callDlangFunction(alias callable, A...)
552                               (PyObject* self, PyObject* args, PyObject* kwargs)
553     if(A.length == 1 && isCallable!(A[0]))
554 {
555     import autowrap.reflection: FunctionParameters, NumDefaultParameters, NumRequiredParameters;
556     import python.raw: PyTuple_Size;
557     import python.conv: toPython;
558     import std.traits: Parameters, variadicFunctionStyle, Variadic;
559     import std.conv: text;
560     import std.string: toStringz;
561     import std.exception: enforce;
562 
563     alias originalFunction = A[0];
564 
565     enum numDefaults = NumDefaultParameters!originalFunction;
566     enum numRequired = NumRequiredParameters!originalFunction;
567     enum isVariadic = variadicFunctionStyle!originalFunction == Variadic.typesafe;
568 
569     static if(__traits(compiles, __traits(identifier, originalFunction)))
570         enum identifier = __traits(identifier, originalFunction);
571     else
572         enum identifier = "anonymous";
573 
574     const numArgs = args is null ? 0 : PyTuple_Size(args);
575     if(!isVariadic)
576         enforce(numArgs >= numRequired
577                 && numArgs <= Parameters!originalFunction.length,
578                 text("Received ", numArgs, " parameters but ",
579                      identifier, " takes ", Parameters!originalFunction.length));
580 
581     auto dArgs = pythonArgsToDArgs!(isVariadic, FunctionParameters!originalFunction)(args, kwargs);
582     return callDlangFunction!callable(dArgs);
583 }
584 
585 
586 private auto callDlangFunction(alias F, A)(auto ref A argTuple) {
587     import python.raw: pyIncRef, pyNone;
588     import python.conv: toPython;
589     import std.traits: ReturnType;
590 
591     // TODO - side-effects on parameters?
592     static if(is(ReturnType!F == void)) {
593         F(argTuple.expand);
594         pyIncRef(pyNone);
595         return pyNone;
596     } else {
597         auto dret = F(argTuple.expand);
598         return dret.toPython;
599     }
600 }
601 
602 
603 /**
604    Creates an instance of a Python class that is equivalent to the D type `T`.
605    Return PyObject*.
606  */
607 PyObject* pythonClass(T)(auto ref T dobj) {
608 
609     import python.conv: toPython;
610     import python.raw: pyObjectNew;
611 
612     static if(is(T == class)) {
613         if(dobj is null)
614             throw new Exception("Cannot create Python class from null D class");
615     }
616 
617     auto ret = pyObjectNew!(PythonClass!T)(PythonType!T.pyType);
618 
619     static foreach(fieldName; PythonType!T.fieldNames) {
620         static if(isPublic!(T, fieldName))
621             mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`);
622     }
623 
624     return cast(PyObject*) ret;
625 }
626 
627 
628 private template isPublic(T, string memberName) {
629 
630     static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) {
631         enum protection = __traits(getProtection, __traits(getMember, T, memberName));
632         enum isPublic = protection == "public" || protection == "export";
633     } else
634         enum isPublic = false;
635 }
636 
637 
638 
639 /**
640    A Python class that mirrors the D type `T`.
641    For instance, this struct:
642    ----------
643    struct Foo {
644        int i;
645        string s;
646    }
647    ----------
648 
649    Will generate a Python class called `Foo` with two members, and trying to
650    assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python
651    will raise `TypeError`.
652  */
653 struct PythonClass(T) if(isUserAggregate!T) {
654     import python.raw: PyObjectHead, PyGetSetDef;
655     import std.traits: Unqual;
656 
657     alias fieldNames = PythonType!(Unqual!T).fieldNames;
658     alias fieldTypes = PythonType!(Unqual!T).fieldTypes;
659 
660     // Every python object must have this
661     mixin PyObjectHead;
662 
663     // Field members
664     // Generate a python object field for every field in T
665     static foreach(fieldName; fieldNames) {
666         mixin(`PyObject* `, fieldName, `;`);
667     }
668 
669     // The function pointer for PyGetSetDef.get
670     private static extern(C) PyObject* get(int FieldIndex)
671                                           (PyObject* self_, void* closure = null)
672         nothrow
673         in(self_ !is null)
674     {
675         import python.raw: pyIncRef;
676 
677         auto self = cast(PythonClass*) self_;
678 
679         auto field = self.getField!FieldIndex;
680         assert(field !is null, "Cannot increase reference count on null field");
681         pyIncRef(field);
682 
683         return field;
684     }
685 
686     // The function pointer for PyGetSetDef.set
687     static extern(C) int set(int FieldIndex)
688                             (PyObject* self_, PyObject* value, void* closure = null)
689         nothrow
690         in(self_ !is null)
691     {
692         import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError;
693 
694         if(value is null) {
695             enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex];
696             PyErr_SetString(PyExc_TypeError, deleteErrStr);
697             return -1;
698         }
699 
700         // FIXME
701         // if(!checkPythonType!(fieldTypes[FieldIndex])(value)) {
702         //     return -1;
703         // }
704 
705         auto self = cast(PythonClass!T*) self_;
706         auto tmp = self.getField!FieldIndex;
707 
708         pyIncRef(value);
709         mixin(`self.`, fieldNames[FieldIndex], ` = value;`);
710         pyDecRef(tmp);
711 
712         return 0;
713     }
714 
715     PyObject* getField(int FieldIndex)() {
716         mixin(`return this.`, fieldNames[FieldIndex], `;`);
717     }
718 
719     static extern(C) PyObject* propertyGet(alias F)
720                                           (PyObject* self_, void* closure = null)
721         nothrow
722         in(self_ !is null)
723     {
724         return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/);
725     }
726 
727     static extern(C) int propertySet(alias F)
728                                     (PyObject* self_, PyObject* value, void* closure = null)
729         nothrow
730         in(self_ !is null)
731     {
732         import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef;
733 
734         auto args = PyTuple_New(1);
735         PyTuple_SetItem(args, 0, value);
736         scope(exit) pyDecRef(args);
737 
738         PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/);
739 
740         return 0;
741     }
742 }
743 
744 
745 PyObject* pythonCallable(T)(T callable) {
746     import python.raw: pyObjectNew;
747 
748     auto ret = pyObjectNew!(PythonCallable!T)(PythonType!T.pyType);
749     ret._callable = callable;
750 
751     return cast(PyObject*) ret;
752 }
753 
754 
755 /**
756    Reserves space for a callable to be stored in a PyObject struct so that it
757    can later be called.
758  */
759 private struct PythonCallable(T) if(isCallable!T) {
760     import python.raw: PyObjectHead;
761 
762     // Every python object must have this
763     mixin PyObjectHead;
764 
765     private T _callable;
766 
767     private static extern(C) PyObject* _py_call(PyObject* self_, PyObject* args, PyObject* kwargs)
768         nothrow
769         in(self_ !is null)
770     {
771         import std.traits: Parameters, ReturnType;
772         auto self = cast(PythonCallable!T*) self_;
773         assert(self._callable !is null, "Cannot have null callable");
774         return noThrowable!(() => callDlangFunction!((Parameters!T args) => self._callable(args), T)(self_, args, kwargs));
775     }
776 }
777 
778 
779 private template PythonUnaryOperator(T, string op) {
780     static extern(C) PyObject* _py_un_op(PyObject* self) nothrow {
781         return noThrowable!({
782             import python.conv.python_to_d: to;
783             import python.conv.d_to_python: toPython;
784             import std.traits: Parameters;
785 
786             static assert(Parameters!(T.opUnary!op).length == 0, "opUnary can't take any parameters");
787 
788             return self.to!T.opUnary!op.toPython;
789         });
790     }
791 }
792 
793 
794 private template PythonBinaryOperator(T, BinaryOperator operator) {
795 
796     static extern(C) int _py_in_func(PyObject* lhs, PyObject* rhs)
797         nothrow
798         in(operator.op == "in")
799     {
800         import python.conv.python_to_d: to;
801         import python.conv.d_to_python: toPython;
802         import std.traits: Parameters, hasMember;
803 
804         alias inParams(U) = Parameters!(U.opBinaryRight!(operator.op));
805 
806         static if(__traits(compiles, inParams!T))
807             alias parameters = inParams!T;
808         else
809             alias parameters = void;
810 
811         static if(is(typeof(T.init.opBinaryRight!(operator.op)(parameters.init)): bool)) {
812             return noThrowable!({
813 
814                 static assert(parameters.length == 1, "opBinaryRight!in must have one parameter");
815                 alias Arg = parameters[0];
816 
817                 auto this_ = lhs.to!T;
818                 auto dArg  = rhs.to!Arg;
819 
820                 const ret = this_.opBinaryRight!(operator.op)(dArg);
821                 // See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
822                 return ret ? 1 : 0;
823             });
824         } else {
825             // Error. See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
826             return -1;
827         }
828     }
829 
830     static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow {
831         return _py_ter_func(lhs, rhs, null);
832     }
833 
834     // Should only be for `^^` because in Python the function is ternary
835     static extern(C) PyObject* _py_ter_func(PyObject* lhs_, PyObject* rhs_, PyObject* extra) nothrow {
836         import python.conv.python_to_d: to;
837         import python.conv.d_to_python: toPython;
838         import autowrap.reflection: BinOpDir, functionName;
839         import std.traits: Parameters;
840         import std.exception: enforce;
841         import std.conv: text;
842 
843         return noThrowable!({
844 
845             PyObject* self, pArg;
846             if(lhs_.isInstanceOf!T) {
847                 self = lhs_;
848                 pArg = rhs_;
849             } else if(rhs_.isInstanceOf!T) {
850                 self = rhs_;
851                 pArg = lhs_;
852             } else
853                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
854 
855             PyObject* impl(BinOpDir dir)() {
856                 enum funcName = functionName(dir);
857                 static if(operator.dirs & dir) {
858                     mixin(`alias parameters = Parameters!(T.`, funcName, `!(operator.op));`);
859                     static assert(parameters.length == 1, "Binary operators must take one parameter");
860                     alias Arg = parameters[0];
861 
862                     auto this_ = self.to!T;
863                     auto dArg  = pArg.to!Arg;
864                     mixin(`return this_.`, funcName, `!(operator.op)(dArg).toPython;`);
865                 } else {
866                     throw new Exception(text(T.stringof, " does not support ", funcName, " with self on ", dir));
867                 }
868             }
869 
870             if(lhs_.isInstanceOf!T) {  // self is on the left hand side
871                 return impl!(BinOpDir.left);
872             } else if(rhs_.isInstanceOf!T) {  // self is on the right hand side
873                 return impl!(BinOpDir.right);
874             } else {
875                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
876             }
877         });
878     }
879 }
880 
881 
882 private template PythonOpCmp(T) {
883     static extern(C) PyObject* _py_cmp(PyObject* lhs, PyObject* rhs, int opId) nothrow {
884         import python.raw: Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE;
885         import python.conv.python_to_d: to;
886         import python.conv.d_to_python: toPython;
887         import std.conv: text;
888         import std.traits: Unqual, Parameters;
889 
890         return noThrowable!({
891 
892             alias parameters = Parameters!(T.opCmp);
893             static assert(parameters.length == 1, T.stringof ~ ".opCmp must have exactly one parameter");
894 
895             const cmp = lhs.to!(Unqual!T).opCmp(rhs.to!(Unqual!(parameters[0])));
896 
897             const dRes = {
898                 switch(opId) {
899                     default: throw new Exception(text("Unknown opId for opCmp: ", opId));
900                     case Py_LT: return cmp < 0;
901                     case Py_LE: return cmp <= 0;
902                     case Py_EQ: return cmp == 0;
903                     case Py_NE: return cmp !=0;
904                     case Py_GT: return cmp > 0;
905                     case Py_GE: return cmp >=0;
906                 }
907             }();
908 
909             return dRes.toPython;
910        });
911     }
912 }
913 
914 
915 private template PythonSubscript(T) {
916     static extern(C) PyObject* _py_index(PyObject* self, PyObject* key) nothrow {
917         import python.raw: pyIndexCheck, pySliceCheck;
918         import python.conv.python_to_d: to;
919         import python.conv.d_to_python: toPython;
920         import std.traits: Parameters, Unqual, hasMember;
921 
922         PyObject* impl() {
923             static if(hasMember!(T, "opIndex")) {
924                 if(pyIndexCheck(self)) {
925                     static if(__traits(compiles, Parameters!(T.opIndex))) {
926                         alias parameters = Parameters!(T.opIndex);
927                         static if(parameters.length == 1)
928                             return self.to!(Unqual!T).opIndex(key.to!(parameters[0])).toPython;
929                         else
930                             throw new Exception("Don't know how to handle opIndex with more than one parameter");
931                     } else
932                         throw new Exception("Cannot determine parameters of " ~ T.stringof, ".opIndex");
933                 } else
934                     throw new Exception(T.stringof ~ " failed pyIndexCheck");
935             } else
936                 throw new Exception(T.stringof ~ " has no opIndex");
937         }
938 
939         return noThrowable!impl;
940     }
941 }
942 
943 
944 /**
945    Implement a Python iterator for D type T.
946    We get a D slice from it, convert it to a Python list,
947    then return its iterator.
948  */
949 private template PythonIter(T) {
950 
951     static extern(C) PyObject* _py_iter(PyObject* self) nothrow {
952         import python.raw: PyObject_GetIter;
953         import python.conv.d_to_python: toPython;
954         import python.conv.python_to_d: to;
955         import std.array;
956 
957         PyObject* impl() {
958             static if(__traits(compiles, T.init.opSlice.array[0])) {
959                 auto dObj = self.to!T;
960                 auto list = dObj.opSlice.array.toPython;
961                 return PyObject_GetIter(list);
962             } else {
963                 throw new Exception("Cannot get an array from " ~ T.stringof ~ ".opSlice");
964             }
965         }
966 
967         return noThrowable!impl;
968     }
969 }
970 
971 
972 private bool isInstanceOf(T)(PyObject* obj) {
973     import python.raw: PyObject_IsInstance;
974     return cast(bool) PyObject_IsInstance(obj, cast(PyObject*) PythonType!T.pyType);
975 }
976 
977 
978 private bool checkPythonType(T)(PyObject* value) if(isArray!T) {
979     import python.raw: pyListCheck;
980     const ret = pyListCheck(value);
981     if(!ret) setPyErrTypeString!"list";
982     return ret;
983 }
984 
985 
986 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) {
987     import python.raw: pyIntCheck, pyLongCheck;
988     const ret = pyLongCheck(value) || pyIntCheck(value);
989     if(!ret) setPyErrTypeString!"long";
990     return ret;
991 }
992 
993 
994 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) {
995     import python.raw: pyFloatCheck;
996     const ret = pyFloatCheck(value);
997     if(!ret) setPyErrTypeString!"float";
998     return ret;
999 }
1000 
1001 
1002 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) {
1003     import python.raw: pyDateTimeCheck;
1004     const ret = pyDateTimeCheck(value);
1005     if(!ret) setPyErrTypeString!"DateTime";
1006     return ret;
1007 }
1008 
1009 
1010 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) {
1011     import python.raw: pyDateCheck;
1012     const ret = pyDateCheck(value);
1013     if(!ret) setPyErrTypeString!"Date";
1014     return ret;
1015 }
1016 
1017 
1018 private void setPyErrTypeString(string type)() @trusted @nogc nothrow {
1019     import python.raw: PyErr_SetString, PyExc_TypeError;
1020     enum str = "must be a " ~ type;
1021     PyErr_SetString(PyExc_TypeError, &str[0]);
1022 }
1023 
1024 // Generalises T.init for classes since null isn't a value we want to use
1025 T userAggregateInit(T)() {
1026     static if(is(T == class)) {
1027         auto buffer = new void[__traits(classInstanceSize, T)];
1028         // this is needed for the vtable to work
1029         buffer[] = typeid(T).initializer[];
1030         return cast(T) buffer.ptr;
1031     } else
1032         return T.init;
1033 }