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