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