1 /**
2    A D API for dealing with Python's PyTypeObject
3  */
4 module python.type;
5 
6 
7 import python.raw: PyObject;
8 import mirror.traits: isParameter, BinaryOperator;
9 import std.traits: Unqual, isArray, isIntegral, isBoolean, isFloatingPoint,
10     isAggregateType, isCallable, isAssociativeArray, isSomeFunction;
11 import std.datetime: DateTime, Date;
12 import std.typecons: Tuple;
13 import std.range.primitives: isInputRange;
14 import std.meta: allSatisfy;
15 static import core.time;
16 
17 
18 package enum isPhobos(T) = isDateOrDateTime!T || isTuple!T || is(Unqual!T == core.time.Duration);
19 package enum isDateOrDateTime(T) = is(Unqual!T == DateTime) || is(Unqual!T == Date);
20 package enum isTuple(T) = is(T: Tuple!A, A...);
21 package enum isUserAggregate(T) = isAggregateType!T && !isPhobos!(T);
22 package enum isNonRangeUDT(T) = isUserAggregate!T && !isInputRange!T;
23 
24 
25 /**
26    A wrapper for `PyTypeObject`.
27 
28    This struct does all of the necessary boilerplate to intialise
29    a `PyTypeObject` for a Python extension type that mimics the D
30    type `T`.
31  */
32 struct PythonType(T) {
33     import python.raw: PyTypeObject, PySequenceMethods, PyMappingMethods;
34     import std.traits: FieldNameTuple, Fields, Unqual, fullyQualifiedName, BaseClassesTuple;
35     import std.meta: Alias, AliasSeq, staticMap;
36 
37     static if(is(T == struct)) {
38         alias fieldNames = FieldNameTuple!T;
39         alias fieldTypes = Fields!T;
40     } else static if(is(T == class)) {
41         // recurse over base classes to get all fields
42         alias fieldNames = AliasSeq!(FieldNameTuple!T, staticMap!(FieldNameTuple, BaseClassesTuple!T));
43         private alias fieldType(string name) = typeof(__traits(getMember, T, name));
44         alias fieldTypes = staticMap!(fieldType, fieldNames);
45     } else static if(is(T == interface)) {
46         alias fieldNames = AliasSeq!();
47         alias fieldTypes = AliasSeq!();
48     }
49 
50     enum hasLength = is(typeof({ size_t len = T.init.length; }));
51 
52     static PyTypeObject _pyType;
53     static bool failedToReady;
54 
55     static PyObject* pyObject() {
56         initialise;
57         return failedToReady ? null : cast(PyObject*) &_pyType;
58     }
59 
60     static PyTypeObject* pyType() nothrow {
61         initialise;
62         return failedToReady ? null : &_pyType;
63     }
64 
65     private static void initialise() nothrow {
66         import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags,
67             PyErr_SetString, PyExc_TypeError,
68             PyNumberMethods, PySequenceMethods;
69         import mirror.traits: UnaryOperators, BinaryOperators, AssignOperators, functionName;
70         import std.traits: arity, hasMember, TemplateOf;
71         import std.meta: Filter;
72         static import std.typecons;
73 
74         if(_pyType != _pyType.init) return;
75 
76         // This allows tp_name to do its usual Python job and allos us to
77         // construct a D class from its runtime Python type.
78         _pyType.tp_name = fullyQualifiedName!(Unqual!T).ptr;
79         _pyType.tp_flags = TypeFlags.Default;
80         static if(is(T == class) || is(T == interface))
81             _pyType.tp_flags |= TypeFlags.BaseType;
82 
83         // FIXME: types are that user aggregates *and* callables
84         static if(isUserAggregate!T) {
85             _pyType.tp_basicsize = PythonClass!T.sizeof;
86 
87             static if(__traits(compiles, getsetDefs()))
88                 _pyType.tp_getset = getsetDefs;
89             else
90                 pragma(msg, "WARNING: could not generate attribute accessors for ", fullyQualifiedName!T);
91 
92             _pyType.tp_methods = methodDefs;
93             static if(!isAbstract!T)
94                 _pyType.tp_new = &_py_new;
95             _pyType.tp_repr = &_py_repr;
96             _pyType.tp_init = &_py_init;
97 
98             // special-case std.typecons.Typedef
99             // see: https://issues.dlang.org/show_bug.cgi?id=20117
100             static isSamePtr(void* lhs, void* rhs) {
101                 return lhs is rhs;
102             }
103 
104             static if(__traits(compiles, isSamePtr(&T.opCmp, &Object.opCmp))) {
105                 static if(
106                     hasMember!(T, "opCmp")
107                     && !__traits(isSame, TemplateOf!T, std.typecons.Typedef)
108                     && !isSamePtr(&T.opCmp, &Object.opCmp)
109                     )
110                 {
111                     _pyType.tp_richcompare = &PythonOpCmp!T._py_cmp;
112                 }
113             }
114 
115             static if(hasMember!(T, "opSlice")) {
116                 static if(__traits(compiles, &PythonIter!T._py_iter))
117                     _pyType.tp_iter = &PythonIter!T._py_iter;
118                 else
119                     pragma(msg, "WARNING: could not implement Python opSlice for ", fullyQualifiedName!T);
120             }
121 
122             // In Python, both D's opIndex and opSlice are dealt with by one function,
123             // in opSlice's case when the type is indexed by a Python slice
124             static if(hasMember!(T, "opIndex") || hasMember!(T, "opSlice")) {
125                 if(_pyType.tp_as_mapping is null)
126                     _pyType.tp_as_mapping = new PyMappingMethods;
127                 _pyType.tp_as_mapping.mp_subscript = &PythonSubscript!T._py_index;
128             }
129 
130             static if(hasMember!(T, "opIndexAssign")) {
131                 if(_pyType.tp_as_mapping is null)
132                     _pyType.tp_as_mapping = new PyMappingMethods;
133 
134                 static if(__traits(compiles, &PythonIndexAssign!T._py_index_assign))
135                     _pyType.tp_as_mapping.mp_ass_subscript = &PythonIndexAssign!T._py_index_assign;
136                 else
137                     pragma(msg, "WARNING: could not implement Python index assign for ",
138                            fullyQualifiedName!T);
139             }
140 
141             enum isPythonableUnary(string op) = op == "+" || op == "-" || op == "~";
142             enum unaryOperators = Filter!(isPythonableUnary, UnaryOperators!T);
143             alias binaryOperators = BinaryOperators!T;
144             alias assignOperators = AssignOperators!T;
145 
146             static if(unaryOperators.length > 0 || binaryOperators.length > 0 || assignOperators.length > 0) {
147                 _pyType.tp_as_number = new PyNumberMethods;
148                 _pyType.tp_as_sequence = new PySequenceMethods;
149             }
150 
151             static foreach(op; unaryOperators) {
152                 mixin(`_pyType.`, dlangUnOpToPythonSlot(op), ` = &PythonUnaryOperator!(T, op)._py_un_op;`);
153             }
154 
155             static foreach(binOp; binaryOperators) {{
156                 // first get the Python function pointer name
157                 enum slot = dlangBinOpToPythonSlot(binOp.op);
158                 // some of them differ in arity
159                 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`));
160 
161                 // get the function name in PythonBinaryOperator
162                 // `in` is special because the function signature is different
163                 static if(binOp.op == "in") {
164                     enum cFuncName = "_py_in_func";
165                 } else {
166                     static if(slotArity == 2)
167                         enum cFuncName = "_py_bin_func";
168                     else static if(slotArity == 3)
169                         enum cFuncName = "_py_ter_func";
170                     else
171                         static assert("Do not know how to deal with slot " ~ slot);
172                 }
173 
174                 // set the C function that implements this operator
175                 mixin(`_pyType.`, slot, ` = &PythonBinaryOperator!(T, binOp).`, cFuncName, `;`);
176             }}
177 
178             static foreach(assignOp; assignOperators) {{
179                 enum slot = dlangAssignOpToPythonSlot(assignOp);
180                                 // some of them differ in arity
181                 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`));
182 
183                 // get the function name in PythonAssignOperator
184                 static if(slotArity == 2)
185                     enum cFuncName = "_py_bin_func";
186                 else static if(slotArity == 3)
187                     enum cFuncName = "_py_ter_func";
188                 else
189                     static assert("Do not know how to deal with slot " ~ slot);
190 
191                 // set the C function that implements this operator
192                 mixin(`_pyType.`, slot, ` = &PythonAssignOperator!(T, assignOp).`, cFuncName, `;`);
193             }}
194 
195             static if(isCallable!T) {
196                 _pyType.tp_call = &PythonCallable!T._py_call;
197             }
198 
199             // inheritance
200             static if(is(T Bases == super)) {
201                 enum isSuperClass(U) = is(U == class) && !is(U == Object);
202                 alias supers = Filter!(isSuperClass, Bases);
203                 static if(supers.length == 1) {
204                     _pyType.tp_base = PythonType!(supers[0]).pyType;
205                 }
206             }
207 
208         } else static if(isCallable!T) {
209             _pyType.tp_basicsize = PythonCallable!T.sizeof;
210             _pyType.tp_call = &PythonCallable!T._py_call;
211         } else static if(is(T == enum)) {
212             import python.raw: PyEnum_Type;
213             _pyType.tp_basicsize = 0;
214             _pyType.tp_base = &PyEnum_Type;
215             try
216                 _pyType.tp_dict = classDict;
217             catch(Exception e) {
218                 import core.stdc.stdio;
219                 enum msg = "Could not create class dict for " ~ T.stringof ~ "\n";
220                 printf(msg);
221             }
222         } else
223             static assert(false, "Don't know what to do for type " ~ T.stringof);
224 
225         static if(hasLength) {
226             if(_pyType.tp_as_sequence is null)
227                 _pyType.tp_as_sequence = new PySequenceMethods;
228             _pyType.tp_as_sequence.sq_length = &_py_length;
229         }
230 
231         if(PyType_Ready(&_pyType) < 0) {
232             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
233             failedToReady = true;
234         }
235     }
236 
237     static if(is(T == enum)) {
238         private static PyObject* classDict() {
239             import python.conv.d_to_python: toPython;
240             import std.traits: EnumMembers, OriginalType;
241 
242             OriginalType!T[string] dict;
243 
244             static foreach(i; 0 .. EnumMembers!T.length) {
245                 dict[__traits(identifier, EnumMembers!T[i])] = EnumMembers!T[i];
246             }
247 
248             return dict.toPython;
249         }
250     }
251 
252     static if(isUserAggregate!T)
253     private static auto getsetDefs() {
254         import python.raw: PyGetSetDef;
255         import mirror.traits: isProperty, MemberFunctionsByOverload;
256         import std.meta: Filter;
257         import std.traits: ReturnType;
258 
259         alias properties = Filter!(isProperty, MemberFunctionsByOverload!T);
260 
261         // +1 due to the sentinel
262         static PyGetSetDef[fieldNames.length + properties.length + 1] getsets;
263 
264         // don't bother if already initialised
265         if(getsets != getsets.init) return &getsets[0];
266 
267         template isPublic(string fieldName) {
268             static if(__traits(compiles, __traits(getMember, T, fieldName))) {
269                 alias field = __traits(getMember, T, fieldName);
270                 static if(__traits(compiles, __traits(getProtection, field)))
271                     enum isPublic = __traits(getProtection, field) == "public";
272                 else
273                     enum isPublic = false;
274             } else
275                 enum isPublic = false;
276         }
277 
278         // first deal with the public fields
279         static foreach(i; 0 .. fieldNames.length) {
280             getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i];
281             static if(isPublic!(fieldNames[i])) {
282                 getsets[i].get = &PythonClass!T._get_impl!i;
283                 getsets[i].set = &PythonClass!T._set_impl!i;
284             }
285         }
286 
287         // then deal with the property functions
288         static foreach(j, property; properties) {{
289             enum i = fieldNames.length + j;
290 
291             getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property);
292 
293             static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) {
294                 static if(is(ReturnType!overload == void))  {// setter
295                     static if(__traits(compiles, &PythonClass!T.propertySet!overload))
296                         getsets[i].set = &PythonClass!T.propertySet!overload;
297                     else
298                         pragma(msg, "Cannot implement ", fullyQualifiedName!T, ".set!", i);
299                 } else  { // getter
300                     static if(__traits(compiles, &PythonClass!T.propertyGet!overload))
301                         getsets[i].get = &PythonClass!T.propertyGet!overload;
302                     else
303                         pragma(msg, "Cannot implement ", fullyQualifiedName!T, ".get!", i);
304                 }
305             }
306         }}
307 
308         return &getsets[0];
309     }
310 
311     private static auto methodDefs()() {
312         import python.raw: PyMethodDef, MethodArgs;
313         import python.cooked: pyMethodDef, defaultMethodFlags;
314         import mirror.traits: isProperty;
315         import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot;
316         import std.traits: isSomeFunction;
317         import std.algorithm: startsWith;
318 
319         alias memberNames = AliasSeq!(__traits(allMembers, T));
320         enum ispublic(string name) = isPublic!(T, name);
321         alias publicMemberNames = Filter!(ispublic, memberNames);
322 
323         enum isRegular(string name) =
324             name != "this"
325             && name != "toHash"
326             && name != "factory"
327             && !name.startsWith("op")
328             && !name.startsWith("__")
329             ;
330         alias regularMemberNames = Filter!(isRegular, publicMemberNames);
331         alias overloads(string name) = AliasSeq!(__traits(getOverloads, T, name));
332         alias members = staticMap!(overloads, regularMemberNames);
333         alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members));
334 
335         // +1 due to sentinel
336         static PyMethodDef[memberFunctions.length + 1] methods;
337 
338         if(methods != methods.init) return &methods[0];
339 
340         static foreach(i, memberFunction; memberFunctions) {{
341 
342             static if(__traits(isStaticFunction, memberFunction))
343                 enum flags = defaultMethodFlags | MethodArgs.Static;
344             else
345                 enum flags = defaultMethodFlags;
346 
347             static if(__traits(compiles, &PythonMethod!(T, memberFunction)._py_method_impl))
348                 methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags)
349                                          (&PythonMethod!(T, memberFunction)._py_method_impl);
350             else {
351                 pragma(msg, "WARNING: could not wrap D method `", T, ".", __traits(identifier, memberFunction), "`");
352                 // uncomment to get the compiler error message to find out why not
353                 // auto ptr = &PythonMethod!(T, memberFunction)._py_method_impl;
354             }
355         }}
356 
357         return &methods[0];
358     }
359 
360     import python.raw: Py_ssize_t;
361     private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow {
362 
363         return noThrowable!({
364             assert(self_ !is null);
365             static if(hasLength) {
366                 import python.conv: to;
367                 return self_.to!T.length;
368             } else
369                 return -1;
370         });
371     }
372 
373     private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow {
374 
375         return noThrowable!({
376 
377             import python: pyUnicodeDecodeUTF8;
378             import python.conv: to;
379             import std.string: toStringz;
380             import std.conv: text;
381             import std.traits: fullyQualifiedName;
382 
383             assert(self_ !is null);
384 
385             static if(__traits(compiles, self_.to!T)) {
386                 auto ret = text(self_.to!T);
387                 return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
388             } else {
389                 pragma(msg, "WARNING: cannot generate repr for ", fullyQualifiedName!T);
390                 PyObject* impl() {
391                     throw new Exception("Unable to generate Python repr for F " ~ fullyQualifiedName!T);
392                 }
393                 return impl;
394             }
395         });
396     }
397 
398     private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow {
399         // nothing to do
400         return 0;
401     }
402 
403     static if(isUserAggregate!T && !isAbstract!T)
404     private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow {
405         return noThrowable!({
406             import python.conv: toPython;
407             import python.raw: PyTuple_Size;
408             import mirror.traits: isPrivate;
409             import std.traits: hasMember, fullyQualifiedName;
410 
411             if(PyTuple_Size(args) == 0) return toPython(userAggregateInit!T);
412 
413             static if(hasMember!(T, "__ctor") && !isPrivate!(__traits(getMember, T, "__ctor"))) {
414                 static if(__traits(compiles, callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs)))
415                     return callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs);
416                 else {
417                     pragma(msg, "WARNING: cannot wrap constructor for `", fullyQualifiedName!T, "`");
418                     // uncomment below to see the compilation error
419                     // return callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs);
420                     return toPython(userAggregateInit!T);
421                 }
422 
423             } else { // allow implicit constructors to work in Python
424                 T impl(fieldTypes fields = fieldTypes.init) {
425                     static if(is(T == class)) {
426                         if(PyTuple_Size(args) != 0)
427                             throw new Exception(T.stringof ~ " has no constructor therefore can't construct one from arguments");
428                         return T.init;
429                     } else {
430                         static if(__traits(compiles, T(fields)))
431                             return T(fields);
432                         else {
433                             pragma(msg, "WARNING: cannot use implicit constructor for `", T, "`");
434                             // uncomment below to see the compiler error
435                             // auto _t_tmp = T(fields);
436                             return T.init;
437                         }
438                     }
439                 }
440 
441                 static if(__traits(compiles, callDlangFunction!(typeof(impl), impl)(null, args, kwargs)))
442                     return callDlangFunction!(typeof(impl), impl)(null /*self*/, args, kwargs);
443                 else {
444                     enum msg = "could not generate constructor for " ~ fullyQualifiedName!T;
445                     pragma(msg, "WARNING: ", msg);
446                     static PyObject* oops() {
447                         throw new Exception(msg);
448                     }
449                     return oops;
450                 }
451             }
452         });
453     }
454 }
455 
456 
457 private template isAbstract(T) {
458     import std.traits: isAbstractClass;
459     enum isAbstract = is(T == interface) || isAbstractClass!T;
460 }
461 
462 
463 // From a D operator (e.g. `+`) to a Python function pointer member name
464 private string dlangUnOpToPythonSlot(string op) {
465     enum opToSlot = [
466         "+": "tp_as_number.nb_positive",
467         "-": "tp_as_number.nb_negative",
468         "~": "tp_as_number.nb_invert",
469     ];
470     if(op !in opToSlot) throw new Exception("Unknown unary operator " ~ op);
471     return opToSlot[op];
472 }
473 
474 
475 // From a D operator (e.g. `+`) to a Python function pointer member name
476 private string dlangBinOpToPythonSlot(string op) {
477     enum opToSlot = [
478         "+":   "tp_as_number.nb_add",
479         "+=":  "tp_as_number.nb_inplace_add",
480         "-":   "tp_as_number.nb_subtract",
481         "-=":  "tp_as_number.nb_inplace_subtract",
482         "*":   "tp_as_number.nb_multiply",
483         "*=":  "tp_as_number.nb_inplace_multiply",
484         "/":   "tp_as_number.nb_divide",
485         "/=":  "tp_as_number.nb_inplace_true_divide",
486         "%":   "tp_as_number.nb_remainder",
487         "%=":  "tp_as_number.nb_inplace_remainder",
488         "^^":  "tp_as_number.nb_power",
489         "^^=": "tp_as_number.nb_inplace_power",
490         "&":   "tp_as_number.nb_and",
491         "&=":  "tp_as_number.nb_inplace_and",
492         "|":   "tp_as_number.nb_or",
493         "|=":  "tp_as_number.nb_inplace_or",
494         "^":   "tp_as_number.nb_xor",
495         "^=":  "tp_as_number.nb_inplace_xor",
496         "<<":  "tp_as_number.nb_lshift",
497         "<<=": "tp_as_number.nb_inplace_lshift",
498         ">>":  "tp_as_number.nb_rshift",
499         ">>=": "tp_as_number.nb_inplace_rshift",
500         "~":   "tp_as_sequence.sq_concat",
501         "~=":  "tp_as_sequence.sq_concat",
502         "in":  "tp_as_sequence.sq_contains",
503     ];
504     if(op !in opToSlot) throw new Exception("Unknown binary operator " ~ op);
505     return opToSlot[op];
506 }
507 
508 
509 // From a D operator (e.g. `+`) to a Python function pointer member name
510 private string dlangAssignOpToPythonSlot(string op) {
511     enum opToSlot = [
512         "+":  "tp_as_number.nb_inplace_add",
513         "-":  "tp_as_number.nb_inplace_subtract",
514         "*":  "tp_as_number.nb_inplace_multiply",
515         "/":  "tp_as_number.nb_inplace_true_divide",
516         "%":  "tp_as_number.nb_inplace_remainder",
517         "^^": "tp_as_number.nb_inplace_power",
518         "&":  "tp_as_number.nb_inplace_and",
519         "|":  "tp_as_number.nb_inplace_or",
520         "^":  "tp_as_number.nb_inplace_xor",
521         "<<": "tp_as_number.nb_inplace_lshift",
522         ">>": "tp_as_number.nb_inplace_rshift",
523         "~":  "tp_as_sequence.sq_concat",
524     ];
525     if(op !in opToSlot) throw new Exception("Unknown assignment operator " ~ op);
526     return opToSlot[op];
527 }
528 
529 
530 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs)
531     if(allSatisfy!(isParameter, P))
532 {
533     import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem;
534     import python.conv: to;
535     import std.typecons: Tuple;
536     import std.meta: staticMap;
537     import std.traits: Unqual;
538     import std.conv: text;
539     import std.exception: enforce;
540 
541     const argsLength = args is null ? 0 : PyTuple_Size(args);
542 
543     alias Type(alias Param) = Param.Type;
544     alias Types = staticMap!(Type, P);
545 
546     // If one or more of the parameters is const/immutable,
547     // it'll be hard to construct it as such, so we Unqual
548     // the types for construction and cast to the appropriate
549     // type when returning.
550     alias MutableTuple = Tuple!(staticMap!(Unqual, Types));
551     alias RetTuple = Tuple!(Types);
552 
553     MutableTuple dArgs;
554 
555     void positional(size_t i, T)() {
556         auto item = PyTuple_GetItem(args, i);
557 
558         static if(__traits(compiles, checkPythonType!T(item))) {
559             if(!checkPythonType!T(item)) {
560                 import python.raw: PyErr_Clear;
561                 PyErr_Clear;
562                 throw new ArgumentConversionException("Can't convert to " ~ T.stringof);
563             }
564         } else {
565             pragma(msg, "WARNING: cannot check python type for `", T, "`");
566             // uncomment to see the compilation error
567             // checkPythonType!T(item);
568         }
569 
570         dArgs[i] = item.to!T;
571     }
572 
573     int pythonArgIndex = 0;
574     static foreach(i; 0 .. P.length) {
575 
576         static if(i == P.length - 1 && isVariadic) {  // last parameter and it's a typesafe variadic one
577             // slice the remaining arguments
578             auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args));
579             dArgs[i] = remainingArgs.to!(P[i].Type);
580         } else static if(is(P[i].Default == void)) {
581             // ith parameter is required
582             enforce(i < argsLength,
583                     text(__FUNCTION__, ": not enough Python arguments"));
584             positional!(i, typeof(dArgs[i]));
585         } else {
586 
587             if(i < argsLength) {  // regular case
588                 positional!(i, P[i].Type);
589             } else {
590                 // Here it gets tricky. The user could have supplied it in
591                 // args positionally or via kwargs
592                 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0],
593                                                P[i].identifier.length,
594                                                null /*errors*/);
595                 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object");
596                 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null;
597                 dArgs[i] = val
598                     ? val.to!(P[i].Type) // use kwargs
599                     : P[i].Default; // use default value
600             }
601         }
602     }
603 
604     return cast(RetTuple) dArgs;
605 }
606 
607 
608 private alias Type(alias A) = typeof(A);
609 
610 
611 /**
612    The C API implementation of a Python method F of aggregate type T
613  */
614 struct PythonMethod(T, alias F) {
615     static extern(C) PyObject* _py_method_impl(PyObject* self,
616                                                PyObject* args,
617                                                PyObject* kwargs)
618         nothrow
619     {
620         return noThrowable!(callDlangFunction!(T, F))(self, args, kwargs);
621     }
622 }
623 
624 
625 private void mutateSelf(T)(PyObject* self, auto ref T dAggregate) {
626 
627     import python.conv.d_to_python: toPython;
628     import python.raw: pyDecRef;
629 
630     auto newSelf = {
631         return self is null ? self : toPython(dAggregate);
632     }();
633     scope(exit) {
634         if(self !is null) pyDecRef(newSelf);
635     }
636     auto pyClassSelf = cast(PythonClass!T*) self;
637     auto pyClassNewSelf = cast(PythonClass!T*) newSelf;
638 
639     static foreach(i; 0 .. PythonClass!T.fieldNames.length) {
640         if(self !is null)
641             pyClassSelf._set_impl!i(self, pyClassNewSelf._get_impl!i(newSelf));
642     }
643 
644 }
645 
646 
647 /**
648    The C API implementation that calls a D function F.
649  */
650 struct PythonFunction(alias F) {
651     static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
652         return noThrowable!(callDlangFunction!(void, F))(self, args, kwargs);
653     }
654 }
655 
656 
657 private auto noThrowable(alias F, A...)(auto ref A args) {
658     import python.raw: PyErr_SetString, PyExc_RuntimeError;
659     import std.string: toStringz;
660     import std.traits: ReturnType;
661 
662     try {
663         return F(args);
664     } catch(Exception e) {
665         PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz);
666         return ReturnType!F.init;
667     } catch(Error e) {
668         import std.conv: text;
669         try
670             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz);
671         catch(Exception _)
672             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz);
673 
674         return ReturnType!F.init;
675     }
676 }
677 
678 
679 class ArgsException: Exception {
680     import std.exception: basicExceptionCtors;
681     mixin basicExceptionCtors;
682 }
683 
684 private PyObject* callDlangFunction(T, alias F)(PyObject* self, PyObject* args, PyObject *kwargs) {
685 
686     import python.raw: PyTuple_Size;
687     import python.conv: toPython, to;
688     import mirror.traits: Parameters, NumDefaultParameters, NumRequiredParameters;
689     import std.traits: variadicFunctionStyle, Variadic, functionAttributes,
690         FunctionAttribute, moduleName, isCallable, StdParameters = Parameters;
691     import std.conv: text;
692     import std.exception: enforce;
693     import std.meta: AliasSeq;
694 
695     enum identifier = __traits(identifier, F);
696     enum isCtor = isUserAggregate!T && identifier == "__ctor";
697     enum isMethod = isUserAggregate!T && identifier != "__ctor";
698 
699     static if(is(T == void)) { // regular function
700         enum parent = moduleName!F;
701         mixin(`static import `, parent, `;`);
702         mixin(`alias Parent = `, parent, `;`);
703     } else static if(isMethod) {
704         enum parent =  "dAggregate";
705         alias Parent = T;
706     } else static if(isCallable!T) {
707         // nothing to do here
708     } else static if(isCtor) {
709         alias Parent = T;
710     } else
711         static assert(false, __FUNCTION__ ~ " does not know how to handle " ~ T.stringof);
712 
713     static if(is(T == void))
714         enum callMixin = `auto ret = callDlangFunction!F(dArgs);`;
715     else static if(isMethod)
716         enum callMixin = `auto ret = callDlangFunction!((StdParameters!overload dArgs) => ` ~ parent ~ `.` ~ identifier ~ `(dArgs))(dArgs);`;
717     else static if(isCtor) {
718         static if(is(T == class))
719             enum callMixin = `auto ret = callDlangFunction!((StdParameters!overload dArgs) => new T(dArgs))(dArgs);`;
720         else
721             enum callMixin = `auto ret = callDlangFunction!((StdParameters!overload dArgs) => T(dArgs))(dArgs);`;
722     } else static if(isCallable!T && !isUserAggregate!T)
723         enum callMixin = `auto ret = callDlangFunction!F(dArgs);`;
724     else
725         static assert(false);
726 
727     static if(__traits(compiles, __traits(getOverloads, Parent, identifier))) {
728         alias candidates = __traits(getOverloads, Parent, identifier);
729         // Deal with possible template instantiation functions.
730         // If it's a free function (T is void), then there must be at least
731         // one overload. The only reason for there to not be one is because
732         // it's a function template.
733         static if(is(T == void) && candidates.length == 0)
734             alias overloads = AliasSeq!F;
735         else
736             alias overloads = candidates;
737     } else
738         alias overloads = AliasSeq!F;
739 
740     static foreach(overload; overloads) {{
741         enum numDefaults = NumDefaultParameters!overload;
742         enum numRequired = NumRequiredParameters!overload;
743         enum isVariadic = variadicFunctionStyle!overload == Variadic.typesafe;
744         enum isMemberFunction = !__traits(isStaticFunction, overload) && !is(T == void);
745 
746         static if(isUserAggregate!T && isMemberFunction && !isCtor)
747             assert(self !is null,
748                    "Cannot call PythonMethod!" ~ identifier ~ " on null self");
749 
750         static if(functionAttributes!overload & FunctionAttribute.const_)
751             alias Aggregate = const T;
752         else static if(functionAttributes!overload & FunctionAttribute.immutable_)
753             alias Aggregate = immutable T;
754         else
755             alias Aggregate = Unqual!T;
756 
757         static if(isUserAggregate!T) { // member function, static or not
758             // self could be null for static member functions
759             auto dAggregate = self is null ? Aggregate.init : self.to!Aggregate;
760         }
761 
762         try {
763             const numArgs = args is null ? 0 : PyTuple_Size(args);
764             if(!isVariadic)
765                 enforce!ArgumentConversionException(
766                     numArgs >= numRequired
767                     && numArgs <= Parameters!overload.length,
768                     text("Received ", numArgs, " parameters but ",
769                          identifier, " takes ", Parameters!overload.length));
770 
771             auto dArgs = pythonArgsToDArgs!(isVariadic, Parameters!overload)(args, kwargs);
772             mixin(callMixin);
773 
774             static if(isUserAggregate!T && isMemberFunction && !isConstMemberFunction!overload) {
775                 mutateSelf(self, dAggregate);
776             }
777 
778             return ret;
779         } catch(ArgumentConversionException _) {
780             // only using this to weed out incompatible overloads
781         }
782     }}
783 
784     throw new Exception("Could not find suitable overload for `" ~ identifier ~ "`");
785 }
786 
787 
788 class ArgumentConversionException: Exception {
789     import std.exception: basicExceptionCtors;
790     mixin basicExceptionCtors;
791 }
792 
793 
794 private PyObject* callDlangFunction(alias F, A)(auto ref A argTuple) {
795     import python.raw: pyIncRef, pyNone;
796     import python.conv: toPython;
797     import std.traits: ReturnType;
798 
799     // TODO - side-effects on parameters?
800     static if(is(ReturnType!F == void)) {
801         F(argTuple.expand);
802         pyIncRef(pyNone);
803         return pyNone;
804     } else {
805         auto dret = F(argTuple.expand);
806         return dret.toPython;
807     }
808 }
809 
810 
811 /**
812    Creates an instance of a Python class that is equivalent to the D type `T`.
813    Return PyObject*.
814  */
815 PyObject* pythonClass(T)(auto ref T dobj) {
816 
817     import python.conv: toPython;
818     import python.raw: pyObjectNew;
819     import std.traits: isPointer, PointerTarget;
820 
821     static if(is(T == class) || isPointer!T) {
822         if(dobj is null)
823             throw new Exception("Cannot create Python class from null D object");
824     }
825 
826     static if(isPointer!T)
827         alias Type = PointerTarget!T;
828     else
829         alias Type = T;
830 
831     auto _type = PythonType!Type.pyType;
832 
833     auto ret = pyObjectNew!(PythonClass!Type)(PythonType!Type.pyType);
834 
835     static foreach(fieldName; PythonType!Type.fieldNames) {
836         static if(isPublic!(T, fieldName))
837             mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`);
838     }
839 
840     return cast(PyObject*) ret;
841 }
842 
843 
844 private template isPublic(T, string memberName) {
845 
846     static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) {
847         enum protection = __traits(getProtection, __traits(getMember, T, memberName));
848         enum isPublic = protection == "public" || protection == "export";
849     } else
850         enum isPublic = false;
851 }
852 
853 /**
854    OOP types register factory functions here, indexed by the fully qualified
855    name of the type. This allows us to construct D class types from the
856    runtime types of Python values.
857  */
858 Object delegate(PyObject*)[string] gFactory;
859 
860 /**
861    A Python class that mirrors the D type `T`.
862    For instance, this struct:
863    ----------
864    struct Foo {
865        int i;
866        string s;
867    }
868    ----------
869 
870    Will generate a Python class called `Foo` with two members, and trying to
871    assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python
872    will raise `TypeError`.
873  */
874 struct PythonClass(T) if(isUserAggregate!T) {
875     import python.raw: PyObjectHead, PyGetSetDef;
876     import std.traits: Unqual;
877 
878     alias fieldNames = PythonType!(Unqual!T).fieldNames;
879     alias fieldTypes = PythonType!(Unqual!T).fieldTypes;
880 
881     // Every python object must have this
882     mixin PyObjectHead;
883 
884     // Field members
885     // Generate a python object field for every field in T
886     static foreach(fieldName; fieldNames) {
887         mixin(`PyObject* `, fieldName, `;`);
888     }
889 
890     static if(is(T == class)) {
891         static this() {
892             import std.traits: fullyQualifiedName;
893 
894             gFactory[fullyQualifiedName!(Unqual!T)] = (PyObject* value) {
895                 import python.conv.python_to_d: to;
896 
897                 auto pyclass = cast(PythonClass!T*) value;
898                 auto ret = userAggregateInit!(Unqual!T);
899 
900                 static foreach(fieldName; fieldNames) {{
901                     alias Field = typeof(__traits(getMember, ret, fieldName));
902                     __traits(getMember, ret, fieldName) = __traits(getMember, pyclass, fieldName).to!Field;
903                 }}
904 
905                 return cast(Object) ret;
906             };
907         }
908     }
909 
910     // The function pointer for PyGetSetDef.get
911     private static extern(C) PyObject* _get_impl(int FieldIndex)
912                                                 (PyObject* self_, void* closure = null)
913         nothrow
914         in(self_ !is null)
915     {
916         import python.raw: pyIncRef;
917 
918         auto self = cast(PythonClass*) self_;
919 
920         auto impl() {
921             auto field = self.getField!FieldIndex;
922             assert(field !is null, "Cannot increase reference count on null field");
923             pyIncRef(field);
924 
925             return field;
926         }
927 
928         return noThrowable!impl;
929     }
930 
931     // The function pointer for PyGetSetDef.set
932     static extern(C) int _set_impl(int FieldIndex)
933                                   (PyObject* self_, PyObject* value, void* closure = null)
934         nothrow
935         in(self_ !is null)
936     {
937         import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError;
938 
939         if(value is null) {
940             enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex];
941             PyErr_SetString(PyExc_TypeError, deleteErrStr);
942             return -1;
943         }
944 
945         static if(__traits(compiles, checkPythonType!(fieldTypes[FieldIndex])(value))) {
946             if(!checkPythonType!(fieldTypes[FieldIndex])(value)) {
947                 return -1;
948             }
949         } else {
950             pragma(msg, "WARNING: cannot check python type for field #", FieldIndex, " of ", T);
951             // uncomment below to see compilation failure
952             // checkPythonType!(fieldTypes[FieldIndex])(value);
953         }
954 
955         auto impl() {
956             auto self = cast(PythonClass!T*) self_;
957             auto tmp = self.getField!FieldIndex;
958 
959             pyIncRef(value);
960             mixin(`self.`, fieldNames[FieldIndex], ` = value;`);
961             pyDecRef(tmp);
962 
963             return 0;
964         }
965 
966         return noThrowable!impl;
967     }
968 
969     PyObject* getField(int FieldIndex)() {
970 
971         auto impl()() {
972             mixin(`return this.`, fieldNames[FieldIndex], `;`);
973         }
974 
975         static if(__traits(compiles, impl!()()))
976             return impl;
977         else {
978             import std.traits: fullyQualifiedName;
979             import std.conv: text;
980 
981             enum msg = text("cannot implement ", fullyQualifiedName!T, ".getField!", FieldIndex);
982             pragma(msg, "WARNING: ", msg);
983             throw new Exception(msg);
984         }
985     }
986 
987     static extern(C) PyObject* propertyGet(alias F)
988                                           (PyObject* self_, void* closure = null)
989         nothrow
990         in(self_ !is null)
991     {
992         return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/);
993     }
994 
995     static extern(C) int propertySet(alias F)
996                                     (PyObject* self_, PyObject* value, void* closure = null)
997         nothrow
998         in(self_ !is null)
999     {
1000         import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef;
1001 
1002         auto args = PyTuple_New(1);
1003         PyTuple_SetItem(args, 0, value);
1004         scope(exit) pyDecRef(args);
1005 
1006         PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/);
1007 
1008         return 0;
1009     }
1010 }
1011 
1012 
1013 PyObject* pythonCallable(T)(T callable) {
1014     import python.raw: pyObjectNew;
1015 
1016     auto ret = pyObjectNew!(PythonCallable!T)(PythonType!T.pyType);
1017     ret._callable = callable;
1018 
1019     return cast(PyObject*) ret;
1020 }
1021 
1022 
1023 private struct PythonCallable(T) if(isCallable!T) {
1024 
1025     import std.traits: hasMember;
1026 
1027     static if(hasMember!(T, "opCall")) {
1028         private static extern(C) PyObject* _py_call(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
1029             return PythonMethod!(T, T.opCall)._py_method_impl(self, args, kwargs);
1030         }
1031     } else {
1032         /**
1033            Reserves space for a callable to be stored in a PyObject struct so that it
1034            can later be called.
1035         */
1036 
1037         import python.raw: PyObjectHead;
1038 
1039         // Every python object must have this
1040         mixin PyObjectHead;
1041 
1042         private T _callable;
1043 
1044         private static extern(C) PyObject* _py_call(PyObject* self_, PyObject* args, PyObject* kwargs)
1045             nothrow
1046             in(self_ !is null)
1047             do
1048         {
1049             import std.traits: Parameters, ReturnType;
1050             auto self = cast(PythonCallable!T*) self_;
1051             assert(self._callable !is null, "Cannot have null callable");
1052             return noThrowable!(callDlangFunction!(T, (Parameters!T args) => self._callable(args)))(self_, args, kwargs);
1053         }
1054     }
1055 }
1056 
1057 private bool isConstMemberFunction(alias F)() {
1058     import std.traits: functionAttributes, FunctionAttribute;
1059     return cast(bool) (functionAttributes!F & FunctionAttribute.const_);
1060 }
1061 
1062 
1063 private template PythonUnaryOperator(T, string op) {
1064     static extern(C) PyObject* _py_un_op(PyObject* self) nothrow {
1065         return noThrowable!({
1066             import python.conv.python_to_d: to;
1067             import python.conv.d_to_python: toPython;
1068             import std.traits: Parameters;
1069 
1070             static assert(Parameters!(T.opUnary!op).length == 0, "opUnary can't take any parameters");
1071 
1072             return self.to!T.opUnary!op.toPython;
1073         });
1074     }
1075 }
1076 
1077 
1078 private template PythonBinaryOperator(T, BinaryOperator operator) {
1079 
1080     static extern(C) int _py_in_func(PyObject* lhs, PyObject* rhs)
1081         nothrow
1082         in(operator.op == "in")
1083     {
1084         import python.conv.python_to_d: to;
1085         import python.conv.d_to_python: toPython;
1086         import std.traits: Parameters, hasMember;
1087 
1088         alias inParams(U) = Parameters!(U.opBinaryRight!(operator.op));
1089 
1090         static if(__traits(compiles, inParams!T))
1091             alias parameters = inParams!T;
1092         else
1093             alias parameters = void;
1094 
1095         static if(is(typeof(T.init.opBinaryRight!(operator.op)(parameters.init)): bool)) {
1096             return noThrowable!({
1097 
1098                 static assert(parameters.length == 1, "opBinaryRight!in must have one parameter");
1099                 alias Arg = parameters[0];
1100 
1101                 auto this_ = lhs.to!T;
1102                 auto dArg  = rhs.to!Arg;
1103 
1104                 const ret = this_.opBinaryRight!(operator.op)(dArg);
1105                 // See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
1106                 return ret ? 1 : 0;
1107             });
1108         } else {
1109             // Error. See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
1110             return -1;
1111         }
1112     }
1113 
1114     static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow {
1115         return _py_ter_func(lhs, rhs, null);
1116     }
1117 
1118     // Should only be for `^^` because in Python the function is ternary
1119     static extern(C) PyObject* _py_ter_func(PyObject* lhs_, PyObject* rhs_, PyObject* extra) nothrow {
1120         import python.conv.python_to_d: to;
1121         import python.conv.d_to_python: toPython;
1122         import mirror.traits: BinOpDir, functionName;
1123         import std.traits: Parameters;
1124         import std.exception: enforce;
1125         import std.conv: text;
1126 
1127         return noThrowable!({
1128 
1129             PyObject* self, pArg;
1130 
1131             if(lhs_.isInstanceOf!T) {
1132                 self = lhs_;
1133                 pArg = rhs_;
1134             } else if(rhs_.isInstanceOf!T) {
1135                 self = rhs_;
1136                 pArg = lhs_;
1137             } else
1138                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
1139 
1140             PyObject* impl(BinOpDir dir)() {
1141 
1142                 enum funcName = functionName(dir);
1143 
1144                 static if(operator.dirs & dir) {
1145                     mixin(`alias parameters = Parameters!(T.`, funcName, `!(operator.op));`);
1146                     static assert(parameters.length == 1, "Binary operators must take one parameter");
1147                     alias Arg = parameters[0];
1148 
1149                     auto this_ = self.to!T;
1150                     auto dArg  = pArg.to!Arg;
1151                     mixin(`return this_.`, funcName, `!(operator.op)(dArg).toPython;`);
1152                 } else
1153                     throw new Exception(text(T.stringof, " does not support ", funcName, " with self on ", dir));
1154             }
1155 
1156             if(lhs_.isInstanceOf!T)   // self is on the left hand side
1157                 return impl!(BinOpDir.left);
1158             else if(rhs_.isInstanceOf!T)   // self is on the right hand side
1159                 return impl!(BinOpDir.right);
1160             else
1161                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
1162         });
1163     }
1164 }
1165 
1166 private template PythonAssignOperator(T, string op) {
1167 
1168     static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow {
1169         return _py_ter_func(lhs, rhs, null);
1170     }
1171 
1172     // Should only be for `^^` because in Python the function is ternary
1173     static extern(C) PyObject* _py_ter_func(PyObject* lhs, PyObject* rhs, PyObject* extra) nothrow {
1174         import python.conv.python_to_d: to;
1175         import python.conv.d_to_python: toPython;
1176         import std.traits: Parameters;
1177 
1178         PyObject* impl() {
1179             alias parameters = Parameters!(T.opOpAssign!op);
1180             static assert(parameters.length == 1, "Assignment operators must take one parameter");
1181 
1182             auto dObj = lhs.to!T;
1183             dObj.opOpAssign!op(rhs.to!(parameters[0]));
1184             return dObj.toPython;
1185         }
1186 
1187         return noThrowable!impl;
1188     }
1189 }
1190 
1191 
1192 private template PythonOpCmp(T) {
1193     static extern(C) PyObject* _py_cmp(PyObject* lhs, PyObject* rhs, int opId) nothrow {
1194         import python.raw: Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE;
1195         import python.conv.python_to_d: to;
1196         import python.conv.d_to_python: toPython;
1197         import std.conv: text;
1198         import std.traits: Unqual, Parameters;
1199 
1200         return noThrowable!({
1201 
1202             alias parameters = Parameters!(T.opCmp);
1203             static assert(parameters.length == 1, T.stringof ~ ".opCmp must have exactly one parameter");
1204 
1205             const cmp = lhs.to!(Unqual!T).opCmp(rhs.to!(Unqual!(parameters[0])));
1206 
1207             const dRes = {
1208                 switch(opId) {
1209                     default: throw new Exception(text("Unknown opId for opCmp: ", opId));
1210                     case Py_LT: return cmp < 0;
1211                     case Py_LE: return cmp <= 0;
1212                     case Py_EQ: return cmp == 0;
1213                     case Py_NE: return cmp !=0;
1214                     case Py_GT: return cmp > 0;
1215                     case Py_GE: return cmp >=0;
1216                 }
1217             }();
1218 
1219             return dRes.toPython;
1220        });
1221     }
1222 }
1223 
1224 
1225 private template PythonSubscript(T) {
1226 
1227     static extern(C) PyObject* _py_index(PyObject* self, PyObject* key) nothrow {
1228         import python.raw: pyIndexCheck, pySliceCheck, PyObject_Repr, PyObject_Length,
1229             Py_ssize_t, PySlice_GetIndices;
1230         import python.conv.python_to_d: to;
1231         import python.conv.d_to_python: toPython;
1232         import std.traits: Parameters, Unqual, hasMember, fullyQualifiedName;
1233         import std.meta: Filter, AliasSeq;
1234 
1235         PyObject* impl() {
1236             static if(hasMember!(T, "opIndex")) {
1237                 if(pyIndexCheck(key)) {
1238                     static if(__traits(compiles, Parameters!(T.opIndex))) {
1239                         alias parameters = Parameters!(T.opIndex);
1240                         static if(parameters.length == 1)
1241                             return self.to!(Unqual!T).opIndex(key.to!(parameters[0])).toPython;
1242                         else
1243                             throw new Exception("Don't know how to handle opIndex with more than one parameter");
1244                     }
1245                 } else if(pySliceCheck(key)) {
1246 
1247                     enum hasTwoParams(alias F) = Parameters!F.length == 2;
1248 
1249                     static if(!hasMember!(T, "opSlice")) {
1250                         throw new Exception(fullyQualifiedName!T ~ " has no opSlice");
1251                     } else {
1252 
1253                         alias twoParamOpSlices = Filter!(hasTwoParams, __traits(getOverloads, T, "opSlice"));
1254 
1255                         static if(twoParamOpSlices.length > 0) {
1256 
1257                             static assert(twoParamOpSlices.length == 1);
1258                             alias opSlice = twoParamOpSlices[0];
1259 
1260                             const len = PyObject_Length(self);
1261                             Py_ssize_t start, stop, step;
1262                             const indicesRet = PySlice_GetIndices(key, len, &start, &stop, &step);
1263 
1264                             if(indicesRet < 0)
1265                                 throw new Exception("Could not get slice indices for key '" ~ PyObject_Repr(key).to!string ~ "'");
1266 
1267                             if(step != 1)
1268                                 throw new Exception("Slice steps other than 1 not supported in D: " ~ PyObject_Repr(key).to!string);
1269 
1270                             auto dObj = self.to!T;
1271                             return dObj[start .. stop].toPython;
1272 
1273                         } else {
1274                             throw new Exception(T.stringof ~ " cannot be sliced by " ~ PyObject_Repr(key).to!string);
1275                         }
1276 
1277                         assert(0, "Error in slicing " ~ T.stringof ~ " with " ~ PyObject_Repr(key).to!string);
1278                     }
1279                 } else
1280                     throw new Exception(T.stringof ~ " failed pyIndexCheck and pySliceCheck for key '" ~ PyObject_Repr(key).to!string ~ "'");
1281                 assert(0);
1282             } else
1283                 throw new Exception(fullyQualifiedName!T ~ " has no opIndex");
1284         }
1285 
1286         return noThrowable!impl;
1287     }
1288 }
1289 
1290 
1291 /**
1292    Implement a Python iterator for D type T.
1293    We get a D slice from it, convert it to a Python list,
1294    then return its iterator.
1295  */
1296 private template PythonIter(T) {
1297 
1298     static extern(C) PyObject* _py_iter(PyObject* self) nothrow {
1299         import python.raw: PyObject_GetIter;
1300         import python.conv.d_to_python: toPython;
1301         import python.conv.python_to_d: to;
1302         import std.array;
1303         import std.traits: fullyQualifiedName;
1304 
1305         PyObject* impl() {
1306             static if(__traits(compiles, T.init[].array[0])) {
1307                 auto dObj = self.to!T;
1308                 auto list = dObj[].array.toPython;
1309                 return PyObject_GetIter(list);
1310             } else {
1311                 throw new Exception("Cannot get an array from " ~ fullyQualifiedName!T ~ "[]");
1312             }
1313         }
1314 
1315         return noThrowable!impl;
1316     }
1317 }
1318 
1319 
1320 private template PythonIndexAssign(T) {
1321 
1322     static extern(C) int _py_index_assign(PyObject* self, PyObject* key, PyObject* val) nothrow {
1323 
1324         import python.conv.python_to_d: to;
1325         import python.conv.d_to_python: toPython;
1326         import python.raw: pyIndexCheck, pySliceCheck, PyObject_Repr, PyObject_Length, PySlice_GetIndices, Py_ssize_t;
1327         import std.traits: Parameters, Unqual;
1328         import std.conv: to;
1329         import std.meta: Filter, AliasSeq;
1330 
1331         int impl() {
1332             if(pyIndexCheck(key)) {
1333                 static if(__traits(compiles, Parameters!(T.opIndexAssign))) {
1334                     alias parameters = Parameters!(T.opIndexAssign);
1335                     static if(parameters.length == 2) {
1336                         auto dObj = self.to!(Unqual!T);
1337                         dObj.opIndexAssign(val.to!(parameters[0]), key.to!(parameters[1]));
1338                         mutateSelf(self, dObj);
1339                         return 0;
1340                     } else
1341                         //throw new Exception("Don't know how to handle opIndex with more than one parameter");
1342                         return -1;
1343                 } else
1344                     return -1;
1345             } else if(pySliceCheck(key)) {
1346 
1347                 enum hasThreeParams(alias F) = Parameters!F.length == 3;
1348                 alias threeParamOps = Filter!(hasThreeParams,
1349                                               AliasSeq!(
1350                                                   __traits(getOverloads, T, "opIndexAssign"),
1351                                                   __traits(getOverloads, T, "opSliceAssign"),
1352                                               )
1353                 );
1354 
1355                 static if(threeParamOps.length > 0) {
1356 
1357                     static assert(threeParamOps.length == 1);
1358                     alias opIndexAssign = threeParamOps[0];
1359                     alias parameters = Parameters!opIndexAssign;
1360 
1361                     const len = PyObject_Length(self);
1362                     Py_ssize_t start, stop, step;
1363                     const indicesRet = PySlice_GetIndices(key, len, &start, &stop, &step);
1364 
1365                     if(indicesRet < 0)
1366                         return -1;
1367 
1368                     if(step != 1)
1369                         return -1;
1370 
1371                     auto dObj = self.to!(Unqual!T);
1372                     mixin(`dObj.`, __traits(identifier, opIndexAssign), `(val.to!(parameters[0]), start, stop);`);
1373                     mutateSelf(self, dObj);
1374                     return 0;
1375                 } else {
1376                     return -1;
1377                 }
1378             } else
1379                 return -1;
1380         }
1381 
1382         return noThrowable!impl;
1383     }
1384 }
1385 
1386 private bool isInstanceOf(T)(PyObject* obj) {
1387     import python.raw: PyObject_IsInstance;
1388     return cast(bool) PyObject_IsInstance(obj, cast(PyObject*) PythonType!T.pyType);
1389 }
1390 
1391 
1392 private bool checkPythonType(T)(PyObject* value) if(isArray!T) {
1393     import python.raw: pySequenceCheck;
1394     const ret = pySequenceCheck(value);
1395     if(!ret) setPyErrTypeString!"sequence";
1396     return ret;
1397 }
1398 
1399 
1400 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) {
1401     import python.raw: pyIntCheck, pyLongCheck;
1402     const ret = pyLongCheck(value) || pyIntCheck(value);
1403     if(!ret) setPyErrTypeString!"long";
1404     return ret;
1405 }
1406 
1407 
1408 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) {
1409     import python.raw: pyFloatCheck;
1410     const ret = pyFloatCheck(value);
1411     if(!ret) setPyErrTypeString!"float";
1412     return ret;
1413 }
1414 
1415 
1416 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) {
1417     import python.raw: pyDateTimeCheck;
1418     const ret = pyDateTimeCheck(value);
1419     if(!ret) setPyErrTypeString!"DateTime";
1420     return ret;
1421 }
1422 
1423 
1424 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) {
1425     import python.raw: pyDateCheck;
1426     const ret = pyDateCheck(value);
1427     if(!ret) setPyErrTypeString!"Date";
1428     return ret;
1429 }
1430 
1431 
1432 private bool checkPythonType(T)(PyObject* value) if(isAssociativeArray!T) {
1433     import python.raw: pyMappingCheck;
1434     const ret = pyMappingCheck(value);
1435     if(!ret) setPyErrTypeString!"dict";
1436     return ret;
1437 }
1438 
1439 
1440 private bool checkPythonType(T)(PyObject* value) if(isUserAggregate!T) {
1441     return true;  // FIXMME
1442 }
1443 
1444 
1445 private bool checkPythonType(T)(PyObject* value) if(isSomeFunction!T) {
1446     import python.raw: pyCallableCheck;
1447     const ret = pyCallableCheck(value);
1448     if(!ret) setPyErrTypeString!"callable";
1449     return ret;
1450 }
1451 
1452 
1453 private void setPyErrTypeString(string type)() @trusted @nogc nothrow {
1454     import python.raw: PyErr_SetString, PyExc_TypeError;
1455     enum str = "must be a " ~ type;
1456     PyErr_SetString(PyExc_TypeError, &str[0]);
1457 }
1458 
1459 // Generalises T.init for classes since null isn't a value we want to use
1460 T userAggregateInit(T)() {
1461     static if(is(T == class)) {
1462         auto buffer = new void[__traits(classInstanceSize, T)];
1463         // this is needed for the vtable to work
1464         buffer[] = typeid(T).initializer[];
1465         return cast(T) buffer.ptr;
1466     } else
1467         return T.init;
1468 }