1 /*
2 Copyright 2006, 2007 Kirk McDonald
3 
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
5 this software and associated documentation files (the "Software"), to deal in
6 the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 of the Software, and to permit persons to whom the Software is furnished to do
9 so, subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 */
22 
23 /**
24   Contains utilities for wrapping D classes.
25 */
26 module pyd.class_wrap;
27 
28 import deimos.python.Python;
29 
30 import std.traits;
31 import std.conv;
32 import std.functional;
33 import std.typetuple;
34 import util.typelist;
35 import util.typeinfo;
36 import pyd.references;
37 import pyd.ctor_wrap;
38 import pyd.def;
39 import pyd.exception;
40 import pyd.func_wrap;
41 import pyd.make_object;
42 import pyd.make_wrapper;
43 import pyd.op_wrap;
44 import pyd.struct_wrap;
45 
46 version(Pyd_with_StackThreads) static assert(0, "sorry - stackthreads are gone");
47 
48 PyTypeObject*[ClassInfo] wrapped_classes;
49 template shim_class(T) {
50     PyTypeObject* shim_class;
51 }
52 
53 // kill
54 template wrapped_class_object(T) {
55     alias PyObject wrapped_class_object;
56 }
57 
58 void init_PyTypeObject(T)(ref PyTypeObject tipo) {
59     Py_SET_REFCNT(&tipo, 1);
60     tipo.tp_dealloc = &wrapped_methods!(T).wrapped_dealloc;
61     tipo.tp_new = &wrapped_methods!(T).wrapped_new;
62 }
63 
64 
65 
66 // The list of wrapped methods for this class.
67 template wrapped_method_list(T) {
68     PyMethodDef[] wrapped_method_list = [
69         { null, null, 0, null }
70     ];
71 }
72 
73 // The list of wrapped properties for this class.
74 template wrapped_prop_list(T) {
75     static PyGetSetDef[] wrapped_prop_list = [
76         { null, null, null, null, null }
77     ];
78 }
79 
80 //-///////////////////
81 // STANDARD METHODS //
82 //-///////////////////
83 
84 // Various wrapped methods
85 template wrapped_methods(T) {
86     /// The generic "__new__" method
87     extern(C)
88     PyObject* wrapped_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
89         return type.tp_alloc(type, 0);
90     }
91 
92     // The generic dealloc method.
93     extern(C)
94     void wrapped_dealloc(PyObject* self) {
95         // EMN: the *&%^^%! generic dealloc method is triggering a call to
96         //  *&^%*%(! malloc for that delegate during a @(*$76*&!
97         //  garbage collection
98         //  Solution: don't use a *&%%^^! delegate in a destructor!
99         static struct StackDelegate{
100             PyObject* x;
101             void dg() {
102                 remove_pyd_mapping!T(x);
103                 x.ob_type.tp_free(x);
104             }
105         }
106         StackDelegate x;
107         x.x = self;
108         exception_catcher_nogc(&x.dg);
109     }
110 }
111 
112 // why we no use method_wrap ?
113 template wrapped_repr(T, alias fn) {
114     import std..string: format;
115 
116     static assert(constCompatible(constness!T, constness!(typeof(fn))),
117             format("constness mismatch instance: %s function: %s",
118                 T.stringof, typeof(fn).stringof));
119     alias dg_wrapper!(T, typeof(&fn)) get_dg;
120     /// The default repr method calls the class's toString.
121     extern(C)
122     PyObject* repr(PyObject* self) {
123         return exception_catcher(delegate PyObject*() {
124             auto dg = get_dg(get_d_reference!T(self), &fn);
125             return d_to_python(dg());
126         });
127     }
128 }
129 
130 private template ID(A){ alias A ID; }
131 private struct CW(A...){ alias A C; }
132 
133 template IsProperty(alias T) {
134     enum bool IsProperty =
135         (functionAttributes!(T) & FunctionAttribute.property) != 0;
136 }
137 
138 template IsGetter(alias T) {
139     enum bool IsGetter = ParameterTypeTuple!T .length == 0 &&
140         !is(ReturnType!T == void);
141 }
142 
143 template IsSetter(RT) {
144     template IsSetter(alias T) {
145         enum bool IsSetter = ParameterTypeTuple!T .length == 1 &&
146                 is(ParameterTypeTuple!(T)[0] == RT);
147     }
148 }
149 template IsAnySetter(alias T) {
150     enum bool IsAnySetter = ParameterTypeTuple!T .length == 1;
151 }
152 
153 // This template gets an alias to a property and derives the types of the
154 // getter form and the setter form. It requires that the getter form return the
155 // same type that the setter form accepts.
156 struct property_parts(alias p, string _mode) {
157     import std.algorithm: countUntil;
158     import std..string: format;
159 
160     alias ID!(__traits(parent, p)) Parent;
161     enum nom = __traits(identifier, p);
162     alias TypeTuple!(__traits(getOverloads, Parent, nom)) Overloads;
163     static if(_mode == "" || countUntil(_mode, "r") != -1) {
164         alias Filter!(IsGetter,Overloads) Getters;
165         static if(_mode == "" && Getters.length == 0) {
166             enum isgproperty = false;
167             enum rmode = "";
168         }else {
169             static assert(Getters.length != 0,
170                     format!("can't find property %s.%s getter",
171                         Parent.stringof, nom));
172             static assert(Getters.length == 1,
173                     format!("can't handle property overloads of %s.%s getter (types %s)",
174                         Parent.stringof, nom, staticMap!(ReturnType,Getters).stringof));
175             alias Getters[0] GetterFn;
176             alias typeof(&GetterFn) getter_type;
177             enum isgproperty = IsProperty!GetterFn;
178             enum rmode = "r";
179         }
180     }else {
181         enum isgproperty = false;
182         enum rmode = "";
183     }
184     //enum bool pred1 = _mode == "" || countUntil(_mode, "w") != -1;
185     static if(_mode == "" || countUntil(_mode, "w") != -1) {
186         static if(rmode == "r") {
187             alias Filter!(IsSetter!(ReturnType!getter_type), Overloads) Setters;
188         }else {
189             alias Filter!(IsAnySetter, Overloads) Setters;
190         }
191 
192         //enum bool pred2 = _mode == "" && Setters.length == 0;
193         static if(_mode == "" && Setters.length == 0) {
194             enum bool issproperty = false;
195             enum string wmode = "";
196         }else{
197             static assert(Setters.length != 0, format("can't find property %s.%s setter", Parent.stringof, nom));
198             static assert(Setters.length == 1,
199                 format("can't handle property overloads of %s.%s setter %s",
200                     Parent.stringof, nom, Setters.stringof));
201             alias Setters[0] SetterFn;
202             alias typeof(&SetterFn) setter_type;
203             static if(rmode == "r") {
204                 static assert(!(IsProperty!GetterFn ^ IsProperty!(Setters[0])),
205                         format("%s.%s: getter and setter must both be @property or not @property",
206                             Parent.stringof, nom));
207             }
208             enum issproperty = IsProperty!SetterFn;
209             enum wmode = "w";
210         }
211     }else{
212         enum issproperty = false;
213         enum wmode = "";
214     }
215 
216     static if(rmode != "") {
217         alias ReturnType!(GetterFn) Type;
218     }else static if(wmode != "") {
219         alias ParameterTypeTuple!(SetterFn)[0] Type;
220     }
221 
222     enum mode = rmode ~ wmode;
223     enum bool isproperty = isgproperty || issproperty;
224 }
225 
226 //
227 template wrapped_get(string fname, T, Parts) {
228     // A generic wrapper around a "getter" property.
229     extern(C)
230     PyObject* func(PyObject* self, void* closure) {
231         // method_wrap already catches exceptions
232         return method_wrap!(T, Parts.GetterFn, fname).func(self, null, null);
233     }
234 }
235 
236 //
237 template wrapped_set(string fname, T, Parts) {
238     // A generic wrapper around a "setter" property.
239     extern(C)
240     int func(PyObject* self, PyObject* value, void* closure) {
241         PyObject* temp_tuple = PyTuple_New(1);
242         if (temp_tuple is null) return -1;
243         scope(exit) Py_DECREF(temp_tuple);
244         Py_INCREF(value);
245         PyTuple_SetItem(temp_tuple, 0, value);
246         PyObject* res = method_wrap!(T, Parts.SetterFn, fname).func(self, temp_tuple, null);
247         // If we get something back, we need to DECREF it.
248         if (res) Py_DECREF(res);
249         // If we don't, propagate the exception
250         else return -1;
251         // Otherwise, all is well.
252         return 0;
253     }
254 }
255 
256 //-///////////////////////////
257 // CLASS WRAPPING INTERFACE //
258 //-///////////////////////////
259 
260 //enum ParamType { Def, StaticDef, Property, Init, Parent, Hide, Iter, AltIter }
261 struct DoNothing {
262     static void call(string classname, T) () {}
263 }
264 
265 /**
266 Wraps a member function of the class.
267 
268 Supports default arguments, typesafe variadic arguments, and python's
269 keyword arguments.
270 
271 Params:
272 fn = The member function to wrap.
273 Options = Optional parameters. Takes Docstring!(docstring), PyName!(pyname),
274 and fn_t.
275 fn_t = The type of the function. It is only useful to specify this
276        if more than one function has the same name as this one.
277 pyname = The name of the function as it will appear in Python. Defaults to
278 fn's name in D
279 docstring = The function's docstring. Defaults to "".
280 */
281 struct Def(alias fn, Options...) {
282     alias Args!("","", __traits(identifier,fn), "",Options) args;
283     static if(args.rem.length) {
284         alias args.rem[0] fn_t;
285     }else {
286         alias typeof(&fn) fn_t;
287     }
288     mixin _Def!(fn, args.pyname, fn_t, args.docstring);
289 }
290 
291 template _Def(alias _fn, string name, fn_t, string docstring) {
292     alias def_selector!(_fn,fn_t).FN func;
293     static assert(!__traits(isStaticFunction, func)); // TODO
294     static assert((functionAttributes!fn_t & (
295                     FunctionAttribute.nothrow_|
296                     FunctionAttribute.pure_|
297                     FunctionAttribute.trusted|
298                     FunctionAttribute.safe)) == 0,
299             "pyd currently does not support pure, nothrow, @trusted, or @safe member functions");
300     alias /*StripSafeTrusted!*/fn_t func_t;
301     enum realname = __traits(identifier,func);
302     enum funcname = name;
303     enum min_args = minArgs!(func);
304     enum bool needs_shim = false;
305 
306     static void call(string classname, T) () {
307         alias ApplyConstness!(T, constness!(typeof(func))) cT;
308         static PyMethodDef empty = { null, null, 0, null };
309         alias wrapped_method_list!(T) list;
310         list[$-1].ml_name = (name ~ "\0").ptr;
311         list[$-1].ml_meth = cast(PyCFunction) &method_wrap!(cT, func, classname ~ "." ~ name).func;
312         list[$-1].ml_flags = METH_VARARGS | METH_KEYWORDS;
313         list[$-1].ml_doc = (docstring~"\0").ptr;
314         list ~= empty;
315         // It's possible that appending the empty item invalidated the
316         // pointer in the type struct, so we renew it here.
317         PydTypeObject!(T).tp_methods = list.ptr;
318     }
319     template shim(size_t i, T) {
320         import util.replace: Replace;
321         enum shim = Replace!(q{
322             alias Params[$i] __pyd_p$i;
323             $override ReturnType!(__pyd_p$i.func_t) $realname(ParameterTypeTuple!(__pyd_p$i.func_t) t) $attrs {
324                 return __pyd_get_overload!("$realname", __pyd_p$i.func_t).func!(ParameterTypeTuple!(__pyd_p$i.func_t))("$name", t);
325             }
326             alias T.$realname $realname;
327         }, "$i",i,"$realname",realname, "$name", name,
328         "$attrs", attrs_to_string(functionAttributes!func_t) ~ " " ~ tattrs_to_string!(func_t)(),
329         "$override",
330         // todo: figure out what's going on here
331         (variadicFunctionStyle!func == Variadic.no ? "override":""));
332     }
333 }
334 
335 /**
336 Wraps a static member function of the class. Similar to pyd.def.def
337 
338 Supports default arguments, typesafe variadic arguments, and python's
339 keyword arguments.
340 
341 Params:
342 fn = The member function to wrap.
343 Options = Optional parameters. Takes Docstring!(docstring), PyName!(pyname),
344 and fn_t
345 fn_t = The type of the function. It is only useful to specify this
346        if more than one function has the same name as this one.
347 pyname = The name of the function as it will appear in Python. Defaults to fn's
348 name in D.
349 docstring = The function's docstring. Defaults to "".
350 */
351 struct StaticDef(alias fn, Options...) {
352     alias Args!("","", __traits(identifier,fn), "",Options) args;
353     static if(args.rem.length) {
354         alias args.rem[0] fn_t;
355     }else {
356         alias typeof(&fn) fn_t;
357     }
358     mixin _StaticDef!(fn, args.pyname, fn_t, args.docstring);
359 }
360 
361 mixin template _StaticDef(alias fn, string name, fn_t, string docstring) {
362     alias def_selector!(fn,fn_t).FN func;
363     static assert(__traits(isStaticFunction, func)); // TODO
364     alias /*StripSafeTrusted!*/fn_t func_t;
365     enum funcname = name;
366     enum bool needs_shim = false;
367     static void call(string classname, T) () {
368         //pragma(msg, "class.static_def: " ~ name);
369         static PyMethodDef empty = { null, null, 0, null };
370         alias wrapped_method_list!(T) list;
371         list[$-1].ml_name = (name ~ "\0").ptr;
372         list[$-1].ml_meth = cast(PyCFunction) &function_wrap!(func, classname ~ "." ~ name).func;
373         list[$-1].ml_flags = METH_VARARGS | METH_STATIC | METH_KEYWORDS;
374         list[$-1].ml_doc = (docstring~"\0").ptr;
375         list ~= empty;
376         PydTypeObject!(T).tp_methods = list.ptr;
377     }
378     template shim(size_t i,T) {
379         enum shim = "";
380     }
381 }
382 
383 /**
384 Wraps a property of the class.
385 
386 Params:
387 fn = The property to wrap.
388 Options = Optional parameters. Takes Docstring!(docstring), PyName!(pyname),
389 and Mode!(mode)
390 pyname = The name of the property as it will appear in Python. Defaults to
391 fn's name in D.
392 mode = specifies whether this property is readable, writable. possible values
393 are "r", "w", "rw", and "" (in the latter case, automatically determine which
394 mode to use based on availability of getter and setter forms of fn). Defaults
395 to "".
396 docstring = The function's docstring. Defaults to "".
397 */
398 struct Property(alias fn, Options...) {
399     alias Args!("","", __traits(identifier,fn), "",Options) args;
400     static assert(args.rem.length == 0, "Propery takes no other parameter");
401     mixin _Property!(fn, args.pyname, args.mode, args.docstring);
402 }
403 
404 template _Property(alias fn, string pyname, string _mode, string docstring) {
405     import std.algorithm: countUntil;
406     alias property_parts!(fn, _mode) parts;
407 
408     static if(parts.isproperty) {
409         mixin _Member!(parts.nom, pyname, parts.mode, docstring, parts);
410 
411         template shim(size_t i, T) {
412             enum shim = "";
413         }
414     }else {
415         static if(countUntil(parts.mode,"r") != -1) {
416             alias parts.getter_type get_t;
417         }
418         static if(countUntil(parts.mode,"w") != -1) {
419             alias parts.setter_type set_t;
420         }
421         enum realname = __traits(identifier, fn);
422         enum funcname = pyname;
423         enum bool needs_shim = false;
424         static void call(string classname, T) () {
425             static PyGetSetDef empty = { null, null, null, null, null };
426             wrapped_prop_list!(T)[$-1].name = (pyname ~ "\0").dup.ptr;
427             static if (countUntil(parts.mode, "r") != -1) {
428                 alias ApplyConstness!(T, constness!(typeof(parts.GetterFn)))
429                     cT_g;
430                 wrapped_prop_list!(T)[$-1].get =
431                     &wrapped_get!(classname ~ "." ~ pyname, cT_g, parts).func;
432             }
433             static if (countUntil(parts.mode, "w") != -1) {
434                 alias ApplyConstness!(T, constness!(typeof(parts.SetterFn)))
435                     cT_s;
436                 wrapped_prop_list!(T)[$-1].set =
437                     &wrapped_set!(classname ~ "." ~ pyname,cT_s, parts).func;
438             }
439             wrapped_prop_list!(T)[$-1].doc = (docstring~"\0").dup.ptr;
440             wrapped_prop_list!(T)[$-1].closure = null;
441             wrapped_prop_list!(T) ~= empty;
442             // It's possible that appending the empty item invalidated the
443             // pointer in the type struct, so we renew it here.
444             PydTypeObject!(T).tp_getset =
445                 wrapped_prop_list!(T).ptr;
446         }
447         template shim(size_t i, T) {
448             import util.replace: Replace;
449             static if(countUntil(parts.mode, "r") != -1) {
450                 enum getter = Replace!(q{
451                 override ReturnType!(__pyd_p$i.get_t) $realname() {
452                     return __pyd_get_overload!("$realname", __pyd_p$i.get_t).func("$name");
453                 }
454                 } , "$i",i,"$realname",realname, "$name", pyname);
455             }else{
456                 enum getter = "";
457             }
458             static if(countUntil(parts.mode, "w") != -1) {
459                 enum setter = Replace!(q{
460                 override ReturnType!(__pyd_p$i.set_t) $realname(ParameterTypeTuple!(__pyd_p$i.set_t) t) {
461                     return __pyd_get_overload!("$realname", __pyd_p$i.set_t).func("$name", t);
462                 }
463                 }, "$i", i, "$realname",realname, "$name", pyname);
464             }else {
465                 enum setter = "";
466             }
467             enum shim = Replace!(q{
468                 alias Params[$i] __pyd_p$i;
469                 $getter
470                 $setter;
471             }, "$i",i, "$getter", getter, "$setter",setter);
472         }
473     }
474 }
475 
476 /**
477 Wraps a method as the class's ___repr__ in Python.
478 
479 Params:
480 fn = The property to wrap. Must have the signature string function().
481 */
482 struct Repr(alias _fn) {
483     alias def_selector!(_fn, string function()).FN fn;
484     enum bool needs_shim = false;
485     static void call(string classname, T)() {
486         alias ApplyConstness!(T, constness!(typeof(fn))) cT;
487         alias PydTypeObject!(T) type;
488         type.tp_repr = &wrapped_repr!(cT, fn).repr;
489     }
490     template shim(size_t i,T) {
491         enum shim = "";
492     }
493 }
494 
495 /**
496 Wraps the constructors of the class.
497 
498 This template takes a single specialization of the ctor template
499 (see ctor_wrap.d), which describes a constructor that the class
500 supports. The default constructor need not be
501 specified, and will always be available if the class supports it.
502 
503 Supports default arguments, typesafe variadic arguments, and python's
504 keyword arguments.
505 
506 Params:
507     cps = Parameter list of the constructor to be wrapped.
508 
509 Bugs:
510 This currently does not support having multiple constructors with
511 the same number of arguments.
512 */
513 struct Init(cps ...) {
514     alias cps CtorParams;
515     enum bool needs_shim = false;
516     template Inner(T) {
517         import std..string: format;
518 
519         alias NewParamT!T BaseT;
520         alias TypeTuple!(__traits(getOverloads, BaseT, "__ctor")) Overloads;
521         template IsDesired(alias ctor) {
522             alias ParameterTypeTuple!ctor ps;
523             enum bool IsDesired = is(ps == CtorParams);
524         }
525         alias Filter!(IsDesired, Overloads) VOverloads;
526         static if(VOverloads.length == 0) {
527             template concatumStrings(s...) {
528                 static if(s.length == 0) {
529                     enum concatumStrings = "";
530                 }else {
531                     enum concatumStrings = T.stringof ~ (ParameterTypeTuple!(s[0])).stringof ~ "\n" ~ concatumStrings!(s[1 .. $]); 
532                 }
533             }
534             alias allOverloadsString = concatumStrings!(Overloads);
535             static assert(false,
536                     format("%s: Cannot find constructor with params %s among\n %s",
537                         T.stringof, CtorParams.stringof, allOverloadsString));
538         }else{
539             alias VOverloads[0] FN;
540             alias ParameterTypeTuple!FN Pt;
541             //https://issues.dlang.org/show_bug.cgi?id=17192
542             //alias ParameterDefaultValueTuple!FN Pd;
543             import util.typeinfo : WorkaroundParameterDefaults;
544             alias Pd = WorkaroundParameterDefaults!FN;
545         }
546     }
547 
548     static void call(string classname, T)() {
549     }
550 
551     template shim(size_t i, T) {
552         import util.replace: Replace;
553         import std..string: format;
554         enum params = getparams!(Inner!T.FN,
555                 format("__pyd_p%s.Inner!T.Pt",i),
556                 format("__pyd_p%s.Inner!T.Pd",i));
557         alias ParameterIdentifierTuple!(Inner!T.FN) paramids;
558         enum shim = Replace!(q{
559             alias Params[$i] __pyd_p$i;
560             this($params) {
561                 super($ids);
562             }
563         }, "$i", i, "$params", params, "$ids", Join!(",", paramids));
564     }
565 }
566 
567 template IsInit(T) {
568     enum bool IsInit = __traits(hasMember, T, "CtorParams");
569 }
570 
571 enum binaryslots = [
572     "+": "type.tp_as_number.nb_add",
573     "+=": "type.tp_as_number.nb_inplace_add",
574     "-": "type.tp_as_number.nb_subtract",
575     "-=": "type.tp_as_number.nb_inplace_subtract",
576     "*": "type.tp_as_number.nb_multiply",
577     "*=": "type.tp_as_number.nb_inplace_multiply",
578     "/": "type.tp_as_number.nb_divide",
579     "/=": "type.tp_as_number.nb_inplace_divide",
580     "%": "type.tp_as_number.nb_remainder",
581     "%=": "type.tp_as_number.nb_inplace_remainder",
582     "^^": "type.tp_as_number.nb_power",
583     "^^=": "type.tp_as_number.nb_inplace_power",
584     "<<": "type.tp_as_number.nb_lshift",
585     "<<=": "type.tp_as_number.nb_inplace_lshift",
586     ">>": "type.tp_as_number.nb_rshift",
587     ">>=": "type.tp_as_number.nb_inplace_rshift",
588     "&": "type.tp_as_number.nb_and",
589     "&=": "type.tp_as_number.nb_inplace_and",
590     "^": "type.tp_as_number.nb_xor",
591     "^=": "type.tp_as_number.nb_inplace_xor",
592     "|": "type.tp_as_number.nb_or",
593     "|=": "type.tp_as_number.nb_inplace_or",
594     "~": "type.tp_as_sequence.sq_concat",
595     "~=": "type.tp_as_sequence.sq_inplace_concat",
596     "in": "type.tp_as_sequence.sq_contains",
597 ];
598 
599 bool IsPyBinary(string op) {
600     foreach(_op, slot; binaryslots) {
601         if (op[$-1] != '=' && op == _op) return true;
602     }
603     return false;
604 }
605 bool IsPyAsg(string op0) {
606     auto op = op0~"=";
607     foreach(_op, slot; binaryslots) {
608         if (op == _op) return true;
609     }
610     return false;
611 }
612 
613 enum unaryslots = [
614     "+": "type.tp_as_number.nb_positive",
615     "-": "type.tp_as_number.nb_negative",
616     "~": "type.tp_as_number.nb_invert",
617 ];
618 
619 bool IsPyUnary(string op) {
620     foreach(_op, slot; unaryslots) {
621         if(op == _op) return true;
622     }
623     return false;
624 }
625 
626 // string mixin to initialize tp_as_number or tp_as_sequence or tp_as_mapping
627 // if necessary. Scope mixed in must have these variables:
628 //  slot: a value from binaryslots or unaryslots
629 //  type: a PyObjectType.
630 string autoInitializeMethods() {
631     return q{
632         import std.algorithm: countUntil;
633         static if(countUntil(slot, "tp_as_number") != -1) {
634             if(type.tp_as_number is null)
635                 type.tp_as_number = new PyNumberMethods;
636         }else static if(countUntil(slot, "tp_as_sequence") != -1) {
637             if(type.tp_as_sequence is null)
638                 type.tp_as_sequence = new PySequenceMethods;
639         }else static if(countUntil(slot, "tp_as_mapping") != -1) {
640             if(type.tp_as_mapping is null)
641                 type.tp_as_mapping = new PyMappingMethods;
642         }
643     };
644 }
645 
646 private struct Guess{}
647 
648 struct BinaryOperatorX(string _op, bool isR, rhs_t) {
649     enum op = _op;
650     enum isRight = isR;
651 
652     static if(isR) enum nom = "opBinaryRight";
653     else enum nom = "opBinary";
654 
655     enum bool needs_shim = false;
656 
657     template Inner(C) {
658         import std..string: format;
659 
660         enum fn_str1 = "Alias!(C."~nom~"!(op))";
661         enum fn_str2 = "C."~nom~"!(op,rhs_t)";
662         enum string OP = op;
663         static if(!__traits(hasMember, C, nom)) {
664             static assert(0, C.stringof ~ " has no "~(isR ?"reflected ":"")~
665                     "binary operator overloads");
666         }
667         template Alias(alias fn) {
668             alias fn Alias;
669         }
670         static if(is(typeof(mixin(fn_str1)) == function)) {
671             alias ParameterTypeTuple!(typeof(mixin(fn_str1)))[0] RHS_T;
672             alias ReturnType!(typeof(mixin(fn_str1))) RET_T;
673             mixin("alias " ~ fn_str1 ~ " FN;");
674             static if(!is(rhs_t == Guess))
675                 static assert(is(RHS_T == rhs_t),
676                         format("expected typeof(rhs) = %s, found %s",
677                             rhs.stringof, RHS_T.stringof));
678         }else static if(is(rhs_t == Guess)) {
679             static assert(false,
680                     format("Operator %s: Cannot determine type of rhs", op));
681         } else static if(is(typeof(mixin(fn_str2)) == function)) {
682             alias rhs_t RHS_T;
683             alias ReturnType!(typeof(mixin(fn_str2))) RET_T;
684             mixin("alias "~fn_str2~" FN;");
685         } else static assert(false, "Cannot get operator overload");
686     }
687 
688     static void call(string classname, T)() {
689         // can't handle __op__ __rop__ pairs here
690     }
691 
692     template shim(size_t i, T) {
693         // bah
694         enum shim = "";
695     }
696 }
697 
698 /**
699 Wrap a binary operator overload.
700 
701 Example:
702 ---
703 class Foo{
704     int _j;
705     int opBinary(string op)(int i) if(op == "+"){
706         return i+_j;
707     }
708     int opBinaryRight(string op)(int i) if(op == "+"){
709         return i+_j;
710     }
711 }
712 
713 class_wrap!(Foo,
714     OpBinary!("+"),
715     OpBinaryRight!("+"));
716 ---
717 
718 Params:
719     op = Operator to wrap
720     rhs_t = (optional) Type of opBinary's parameter for disambiguation if
721     there are multiple overloads.
722 Bugs:
723     Issue 8602 prevents disambiguation for case X opBinary(string op, T)(T t);
724   */
725 template OpBinary(string op, rhs_t = Guess) if(IsPyBinary(op) && op != "in"){
726     alias BinaryOperatorX!(op, false, rhs_t) OpBinary;
727 }
728 
729 /// ditto
730 template OpBinaryRight(string op, lhs_t = Guess) if(IsPyBinary(op)) {
731     alias BinaryOperatorX!(op, true, lhs_t) OpBinaryRight;
732 }
733 
734 /**
735   Wrap a unary operator overload.
736 */
737 struct OpUnary(string _op) if(IsPyUnary(_op)) {
738     enum op = _op;
739     enum bool needs_shim = false;
740 
741     template Inner(C) {
742         enum string OP = op;
743         static if(!__traits(hasMember, C, "opUnary")) {
744             static assert(0, C.stringof ~ " has no unary operator overloads");
745         }
746         static if(is(typeof(C.init.opUnary!(op)) == function)) {
747             alias ReturnType!(C.opUnary!(op)) RET_T;
748             alias C.opUnary!(op) FN;
749         } else static assert(false, "Cannot get operator overload");
750     }
751     static void call(string classname, T)() {
752         alias PydTypeObject!T type;
753         enum slot = unaryslots[op];
754         mixin(autoInitializeMethods());
755         mixin(slot ~ " = &opfunc_unary_wrap!(T, Inner!T .FN).func;");
756     }
757     template shim(size_t i,T) {
758         // bah
759         enum shim = "";
760     }
761 }
762 
763 /**
764   Wrap an operator assignment overload.
765 
766 Example:
767 ---
768 class Foo{
769     int _j;
770     void opOpAssign(string op)(int i) if(op == "+"){
771         _j = i;
772     }
773 }
774 
775 class_wrap!(Foo,
776     OpAssign!("+"));
777 ---
778 Params:
779     op = Base operator to wrap
780     rhs_t = (optional) Type of opOpAssign's parameter for disambiguation if
781     there are multiple overloads.
782 */
783 struct OpAssign(string _op, rhs_t = Guess) if(IsPyAsg(_op)) {
784     enum op = _op~"=";
785 
786     enum bool needs_shim = false;
787 
788     template Inner(C) {
789         enum string OP = op;
790         static if(!__traits(hasMember, C, "opOpAssign")) {
791             static assert(0, C.stringof ~ " has no operator assignment overloads");
792         }
793         static if(is(typeof(C.init.opOpAssign!(_op)) == function)) {
794             alias ParameterTypeTuple!(typeof(C.opOpAssign!(_op)))[0] RHS_T;
795             alias ReturnType!(typeof(C.opOpAssign!(_op))) RET_T;
796             alias C.opOpAssign!(_op) FN;
797             static if(!is(rhs_t == Guess))
798                 static assert(is(RHS_T == rhs_t),
799                         format("expected typeof(rhs) = %s, found %s",
800                             rhs.stringof, RHS_T.stringof));
801         }else static if(is(rhs_t == Guess)) {
802             static assert(false, "Cannot determine type of rhs");
803         } else static if(is(typeof(C.opOpAssign!(_op,rhs_t)) == function)) {
804             alias rhs_t RHS_T;
805             alias ReturnType!(typeof(C.opOpAssign!(_op,rhs_t))) RET_T;
806             alias C.opOpAssign!(_op,rhs_t) FN;
807         } else static assert(false, "Cannot get operator assignment overload");
808     }
809     static void call(string classname, T)() {
810         alias PydTypeObject!T type;
811         enum slot = binaryslots[op];
812         mixin(autoInitializeMethods());
813         alias CW!(TypeTuple!(OpAssign)) OpAsg;
814         alias CW!(TypeTuple!()) Nop;
815         static if(op == "^^=")
816             mixin(slot ~ " = &powopasg_wrap!(T, Inner!T.FN).func;");
817         else
818             mixin(slot ~ " = &binopasg_wrap!(T, Inner!T.FN).func;");
819     }
820 
821     template shim(size_t i,T) {
822         // bah
823         enum shim = "";
824     }
825 }
826 
827 // struct types could probably take any parameter type
828 // class types must take Object
829 /**
830   Wrap opCmp.
831 
832 Params:
833     rhs_t = (optional) Type of opCmp's parameter for disambiguation if there
834     are multiple overloads (for classes it will always be Object).
835   */
836 struct OpCompare(_rhs_t = Guess) {
837     enum bool needs_shim = false;
838 
839 
840     template Inner(C) {
841         import std..string: format;
842         static if(is(_rhs_t == Guess) && is(C == class)) {
843             alias Object rhs_t;
844         }else {
845             alias _rhs_t rhs_t;
846         }
847         static if(!__traits(hasMember, C, "opCmp")) {
848             static assert(0, C.stringof ~ " has no comparison operator overloads");
849         }
850         static if(!is(typeof(C.init.opCmp) == function)) {
851             static assert(0, format("why is %s.opCmp not a function?",C));
852         }
853         alias TypeTuple!(__traits(getOverloads, C, "opCmp")) Overloads;
854         static if(is(rhs_t == Guess) && Overloads.length > 1) {
855             static assert(0, format("Cannot choose between %s", Overloads));
856         }else static if(Overloads.length == 1) {
857             static if(!is(rhs_t == Guess) &&
858                 !is(ParameterTypeTuple!(Overloads[0])[0] == rhs_t)) {
859                 static assert(0, format("%s.opCmp: expected param %s, got %s",
860                             C, rhs_t, ParameterTypeTuple!(Overloads[0])));
861             }else{
862                 alias Overloads[0] FN;
863             }
864         }else{
865             template IsDesiredOverload(alias fn) {
866                 enum bool IsDesiredOverload = is(ParameterTypeTuple!(fn)[0] == rhs_t);
867             }
868             alias Filter!(IsDesiredOverload, Overloads) Overloads1;
869             static assert(Overloads1.length == 1,
870                     format("Cannot choose between %s", Overloads1));
871             alias Overloads1[0] FN;
872         }
873     }
874     static void call(string classname, T)() {
875         alias PydTypeObject!T type;
876         alias ApplyConstness!(T, constness!(typeof(Inner!T.FN))) cT;
877         type.tp_richcompare = &rich_opcmp_wrap!(cT, Inner!T.FN).func;
878     }
879     template shim(size_t i,T) {
880         // bah
881         enum shim = "";
882     }
883 }
884 
885 /**
886   Wrap opIndex, opIndexAssign.
887 
888 Params:
889     index_t = (optional) Types of opIndex's parameters for disambiguation if
890     there are multiple overloads.
891 */
892 struct OpIndex(index_t...) {
893     enum bool needs_shim = false;
894     template Inner(C) {
895         import std..string: format;
896 
897         static if(!__traits(hasMember, C, "opIndex")) {
898             static assert(0, C.stringof ~ " has no index operator overloads");
899         }
900         static if(is(typeof(C.init.opIndex) == function)) {
901             alias TypeTuple!(__traits(getOverloads, C, "opIndex")) Overloads;
902             static if(index_t.length == 0 && Overloads.length > 1) {
903                 static assert(0,
904                         format("%s.opIndex: Cannot choose between %s",
905                             C.stringof,Overloads.stringof));
906             }else static if(index_t.length == 0) {
907                 alias Overloads[0] FN;
908             }else{
909                 template IsDesiredOverload(alias fn) {
910                     enum bool IsDesiredOverload = is(ParameterTypeTuple!fn == index_t);
911                 }
912                 alias Filter!(IsDesiredOverload, Overloads) Overloads1;
913                 static assert(Overloads1.length == 1,
914                         format("%s.opIndex: Cannot choose between %s",
915                             C.stringof,Overloads1.stringof));
916                 alias Overloads1[0] FN;
917             }
918         }else static if(is(typeof(C.init.opIndex!(index_t)) == function)) {
919             alias C.opIndex!(index_t) FN;
920         }else{
921             static assert(0,
922                     format("cannot get a handle on %s.opIndex", C.stringof));
923         }
924     }
925     static void call(string classname, T)() {
926         /*
927         alias PydTypeObject!T type;
928         enum slot = "type.tp_as_mapping.mp_subscript";
929         mixin(autoInitializeMethods());
930         mixin(slot ~ " = &opindex_wrap!(T, Inner!T.FN).func;");
931         */
932     }
933     template shim(size_t i,T) {
934         // bah
935         enum shim = "";
936     }
937 }
938 
939 /// ditto
940 struct OpIndexAssign(index_t...) {
941     static assert(index_t.length != 1,
942             "opIndexAssign must have at least 2 parameters");
943     enum bool needs_shim = false;
944     template Inner(C) {
945         import std..string: format;
946 
947         static if(!__traits(hasMember, C, "opIndexAssign")) {
948             static assert(0, C.stringof ~ " has no index operator overloads");
949         }
950         static if(is(typeof(C.init.opIndex) == function)) {
951             alias TypeTuple!(__traits(getOverloads, C, "opIndexAssign")) Overloads;
952             template IsValidOverload(alias fn) {
953                 enum bool IsValidOverload = ParameterTypeTuple!fn.length >= 2;
954             }
955             alias Filter!(IsValidOverload, Overloads) VOverloads;
956             static if(VOverloads.length == 0 && Overloads.length != 0)
957                 static assert(0,
958                         "opIndexAssign must have at least 2 parameters");
959             static if(index_t.length == 0 && VOverloads.length > 1) {
960                 static assert(0,
961                         format("%s.opIndexAssign: Cannot choose between %s",
962                             C.stringof,VOverloads.stringof));
963             }else static if(index_t.length == 0) {
964                 alias VOverloads[0] FN;
965             }else{
966                 template IsDesiredOverload(alias fn) {
967                     enum bool IsDesiredOverload = is(ParameterTypeTuple!fn == index_t);
968                 }
969                 alias Filter!(IsDesiredOverload, VOverloads) Overloads1;
970                 static assert(Overloads1.length == 1,
971                         format("%s.opIndex: Cannot choose between %s",
972                             C.stringof,Overloads1.stringof));
973                 alias Overloads1[0] FN;
974             }
975         }else static if(is(typeof(C.init.opIndexAssign!(index_t)) == function)) {
976             alias C.opIndexAssign!(index_t) FN;
977         }else{
978             static assert(0,
979                     format("cannot get a handle on %s.opIndexAssign", C.stringof));
980         }
981     }
982     static void call(string classname, T)() {
983         /*
984         alias PydTypeObject!T type;
985         enum slot = "type.tp_as_mapping.mp_ass_subscript";
986         mixin(autoInitializeMethods());
987         mixin(slot ~ " = &opindexassign_wrap!(T, Inner!T.FN).func;");
988         */
989     }
990     template shim(size_t i,T) {
991         // bah
992         enum shim = "";
993     }
994 }
995 
996 /**
997   Wrap opSlice.
998 
999   Requires signature
1000 ---
1001 Foo.opSlice(Py_ssize_t, Py_ssize_t);
1002 ---
1003  This is a limitation of the C/Python API.
1004   */
1005 struct OpSlice() {
1006     enum bool needs_shim = false;
1007     template Inner(C) {
1008         import std..string: format;
1009 
1010         static if(!__traits(hasMember, C, "opSlice")) {
1011             static assert(0, C.stringof ~ " has no slice operator overloads");
1012         }
1013         static if(is(typeof(C.init.opSlice) == function)) {
1014             alias TypeTuple!(__traits(getOverloads, C, "opSlice")) Overloads;
1015             template IsDesiredOverload(alias fn) {
1016                 enum bool IsDesiredOverload = is(ParameterTypeTuple!fn ==
1017                         TypeTuple!(Py_ssize_t,Py_ssize_t));
1018             }
1019             alias Filter!(IsDesiredOverload, Overloads) Overloads1;
1020             static assert(Overloads1.length != 0,
1021                     format("%s.opSlice: must have overload %s",
1022                         C.stringof,TypeTuple!(Py_ssize_t,Py_ssize_t).stringof));
1023             static assert(Overloads1.length == 1,
1024                     format("%s.opSlice: cannot choose between %s",
1025                         C.stringof,Overloads1.stringof));
1026             alias Overloads1[0] FN;
1027         }else{
1028             static assert(0, format("cannot get a handle on %s.opSlice",
1029                         C.stringof));
1030         }
1031     }
1032     static void call(string classname, T)() {
1033         /*
1034         alias PydTypeObject!T type;
1035         enum slot = "type.tp_as_sequence.sq_slice";
1036         mixin(autoInitializeMethods());
1037         mixin(slot ~ " = &opslice_wrap!(T, Inner!T.FN).func;");
1038         */
1039     }
1040     template shim(size_t i,T) {
1041         // bah
1042         enum shim = "";
1043     }
1044 }
1045 
1046 /**
1047   Wrap opSliceAssign.
1048 
1049   Requires signature
1050 ---
1051 Foo.opSliceAssign(Value,Py_ssize_t, Py_ssize_t);
1052 ---
1053  This is a limitation of the C/Python API.
1054   */
1055 struct OpSliceAssign(rhs_t = Guess) {
1056     enum bool needs_shim = false;
1057     template Inner(C) {
1058         static if(!__traits(hasMember, C, "opSliceAssign")) {
1059             static assert(0, C.stringof ~ " has no slice assignment operator overloads");
1060         }
1061         static if(is(typeof(C.init.opSliceAssign) == function)) {
1062             alias TypeTuple!(__traits(getOverloads, C, "opSliceAssign")) Overloads;
1063             template IsDesiredOverload(alias fn) {
1064                 alias ParameterTypeTuple!fn ps;
1065                 enum bool IsDesiredOverload =
1066                     is(ps[1..3] == TypeTuple!(Py_ssize_t,Py_ssize_t));
1067             }
1068             alias Filter!(IsDesiredOverload, Overloads) Overloads1;
1069             static assert(Overloads1.length != 0,
1070                     format("%s.opSliceAssign: must have overload %s",
1071                         C.stringof,TypeTuple!(Guess,Py_ssize_t,Py_ssize_t).stringof));
1072             static if(is(rhs_t == Guess)) {
1073                 static assert(Overloads1.length == 1,
1074                         format("%s.opSliceAssign: cannot choose between %s",
1075                             C.stringof,Overloads1.stringof));
1076                 alias Overloads1[0] FN;
1077             }else{
1078                 template IsDesiredOverload2(alias fn) {
1079                     alias ParameterTypeTuple!fn ps;
1080                     enum bool IsDesiredOverload2 = is(ps[0] == rhs_t);
1081                 }
1082                 alias Filter!(IsDesiredOverload2, Overloads1) Overloads2;
1083                 static assert(Overloads2.length == 1,
1084                         format("%s.opSliceAssign: cannot choose between %s",
1085                             C.stringof,Overloads2.stringof));
1086                 alias Overloads2[0] FN;
1087             }
1088         }else{
1089             static assert(0, format("cannot get a handle on %s.opSlice",
1090                         C.stringof));
1091         }
1092     }
1093     static void call(string classname, T)() {
1094         /*
1095         alias PydTypeObject!T type;
1096         enum slot = "type.tp_as_sequence.sq_ass_slice";
1097         mixin(autoInitializeMethods());
1098         mixin(slot ~ " = &opsliceassign_wrap!(T, Inner!T.FN).func;");
1099         */
1100     }
1101     template shim(size_t i,T) {
1102         // bah
1103         enum shim = "";
1104     }
1105 }
1106 
1107 /**
1108   wrap opCall. The parameter types of opCall must be specified.
1109 */
1110 struct OpCall(Args_t...) {
1111     enum bool needs_shim = false;
1112 
1113     template Inner(T) {
1114         import std..string: format;
1115 
1116         alias TypeTuple!(__traits(getOverloads, T, "opCall")) Overloads;
1117         template IsDesiredOverload(alias fn) {
1118             alias ParameterTypeTuple!fn ps;
1119             enum bool IsDesiredOverload = is(ps == Args_t);
1120         }
1121         alias Filter!(IsDesiredOverload, Overloads) VOverloads;
1122         static if(VOverloads.length == 0) {
1123             static assert(0,
1124                     format("%s.opCall: cannot find signature %s", T.stringof,
1125                         Args_t.stringof));
1126         }else static if(VOverloads.length == 1){
1127             alias VOverloads[0] FN;
1128         }else static assert(0,
1129                 format("%s.%s: cannot choose between %s", T.stringof, nom,
1130                     VOverloads.stringof));
1131     }
1132     static void call(string classname, T)() {
1133         alias PydTypeObject!T type;
1134         alias Inner!T.FN fn;
1135         alias ApplyConstness!(T, constness!(typeof(fn))) cT;
1136         type.tp_call = &opcall_wrap!(cT, fn).func;
1137     }
1138     template shim(size_t i,T) {
1139         // bah
1140         enum shim = "";
1141     }
1142 }
1143 
1144 /**
1145   Wraps Foo.length or another function as python's ___len__ function.
1146 
1147   Requires signature
1148 ---
1149 Py_ssize_t length();
1150 ---
1151   This is a limitation of the C/Python API.
1152   */
1153 template Len() {
1154     alias _Len!() Len;
1155 }
1156 
1157 /// ditto
1158 template Len(alias fn) {
1159     alias _Len!(fn) Len;
1160 }
1161 
1162 struct _Len(fnt...) {
1163     enum bool needs_shim = false;
1164     template Inner(T) {
1165         import std..string: format;
1166 
1167         static if(fnt.length == 0) {
1168             enum nom = "length";
1169         }else{
1170             enum nom = __traits(identifier, fnt[0]);
1171         }
1172         alias TypeTuple!(__traits(getOverloads, T, nom)) Overloads;
1173         template IsDesiredOverload(alias fn) {
1174             alias ParameterTypeTuple!fn ps;
1175             alias ReturnType!fn rt;
1176             enum bool IsDesiredOverload = isImplicitlyConvertible!(rt,Py_ssize_t) && ps.length == 0;
1177         }
1178         alias Filter!(IsDesiredOverload, Overloads) VOverloads;
1179         static if(VOverloads.length == 0 && Overloads.length != 0) {
1180             static assert(0,
1181                     format("%s.%s must have signature %s", T.stringof, nom,
1182                         (Py_ssize_t function()).stringof));
1183         }else static if(VOverloads.length == 1){
1184             alias VOverloads[0] FN;
1185         }else static assert(0,
1186                 format("%s.%s: cannot choose between %s", T.stringof, nom,
1187                     VOverloads.stringof));
1188     }
1189     static void call(string classname, T)() {
1190         alias PydTypeObject!T type;
1191         enum slot = "type.tp_as_sequence.sq_length";
1192         mixin(autoInitializeMethods());
1193         mixin(slot ~ " = &length_wrap!(T, Inner!T.FN).func;");
1194     }
1195     template shim(size_t i,T) {
1196         // bah
1197         enum shim = "";
1198     }
1199 }
1200 
1201 
1202 template param1(C) {
1203     template param1(T) {alias ParameterTypeTuple!(T.Inner!C .FN)[0] param1; }
1204 }
1205 
1206 enum IsOp(A) = __traits(hasMember, A, "op");
1207 
1208 template IsUn(A) {
1209     import std.algorithm: startsWith;
1210     enum IsUn = A.stringof.startsWith("OpUnary!");
1211 }
1212 
1213 template IsBin(T...) {
1214     import std.algorithm: startsWith;
1215     static if(T[0].stringof.startsWith("BinaryOperatorX!"))
1216         enum bool IsBin = !T[0].isRight;
1217     else
1218         enum bool IsBin = false;
1219 }
1220 template IsBinR(T...) {
1221     import std.algorithm: startsWith;
1222     static if(T[0].stringof.startsWith("BinaryOperatorX!"))
1223         enum IsBinR = T[0].isRight;
1224     else
1225         enum IsBinR = false;
1226 }
1227 
1228 // handle all operator overloads. Ops must only contain operator overloads.
1229 struct Operators(Ops...) {
1230     import util.replace: Replace;
1231     enum bool needs_shim = false;
1232 
1233     template BinOp(string op, T) {
1234         enum IsThisOp(A) = A.op == op;
1235         alias Filter!(IsThisOp, Ops) Ops0;
1236         alias Filter!(IsBin, Ops0) OpsL;
1237         alias staticMap!(param1!T, OpsL) OpsLparams;
1238         static assert(OpsL.length <= 1,
1239                 Replace!("Cannot overload $T1 $OP x with types $T2",
1240                     "$OP", op, "$T1", T.stringof, "$T2",  OpsLparams.stringof));
1241         alias Filter!(IsBinR, Ops0) OpsR;
1242         alias staticMap!(param1, OpsR) OpsRparams;
1243         static assert(OpsR.length <= 1,
1244                 Replace!("Cannot overload x $OP $T1 with types $T2",
1245                     "$OP", op, "$T1", T.stringof, "$T2",  OpsRparams.stringof));
1246         static assert(op[$-1] != '=' || OpsR.length == 0,
1247                 "Cannot reflect assignment operator");
1248 
1249         static void call() {
1250             static if(OpsL.length + OpsR.length != 0) {
1251                 alias PydTypeObject!T type;
1252                 enum slot = binaryslots[op];
1253                 mixin(autoInitializeMethods());
1254                 static if(op == "in") {
1255                     mixin(slot ~ " = &inop_wrap!(T, CW!OpsL, CW!OpsR).func;");
1256                 }else static if(op == "^^" || op == "^^=") {
1257                     mixin(slot ~ " = &powop_wrap!(T, CW!OpsL, CW!OpsR).func;");
1258                 }else {
1259                     mixin(slot ~ " = &binop_wrap!(T, CW!OpsL, CW!OpsR).func;");
1260                 }
1261             }
1262         }
1263 
1264     }
1265     struct UnOp(string op, T) {
1266         import util.replace: Replace;
1267         enum IsThisOp(A) = A.op == op;
1268         alias Filter!(IsUn, Filter!(IsThisOp, Ops)) Ops1;
1269         static assert(Ops1.length <= 1,
1270                 Replace!("Cannot have overloads of $OP$T1",
1271                     "$OP", op, "$T1", T.stringof));
1272         static void call() {
1273             static if(Ops1.length != 0) {
1274                 alias PydTypeObject!T type;
1275                 alias Ops1[0] Ops1_0;
1276                 alias Ops1_0.Inner!T .FN fn;
1277                 enum slot = unaryslots[op];
1278                 mixin(autoInitializeMethods());
1279                 mixin(slot ~ " = &opfunc_unary_wrap!(T, fn).func;");
1280             }
1281         }
1282     }
1283 
1284     static void call(T)() {
1285         enum GetOp(A) = A.op;
1286         alias NoDuplicates!(staticMap!(GetOp, Ops)) str_op_tuple;
1287         enum binops = binaryslots.keys();
1288         foreach(_op; str_op_tuple) {
1289             BinOp!(_op, T).call(); // noop if op is unary
1290             UnOp!(_op, T).call(); // noop if op is binary
1291         }
1292     }
1293 }
1294 
1295 struct Constructors(string classname, Ctors...) {
1296     enum bool needs_shim = true;
1297 
1298     static void call(T, Shim)() {
1299         alias PydTypeObject!T type;
1300         alias NewParamT!T U;
1301         static if(Ctors.length) {
1302             type.tp_init = &wrapped_ctors!(classname, T, Shim, Ctors).func;
1303         }else {
1304             // If a ctor wasn't supplied, try the default.
1305             // If the default ctor isn't available, and no ctors were supplied,
1306             // then this class cannot be instantiated from Python.
1307             // (Structs always use the default ctor.)
1308             static if (is(typeof(new U))) {
1309                 static if (is(U == class)) {
1310                     type.tp_init = &wrapped_init!(Shim).init;
1311                 } else {
1312                     type.tp_init = &wrapped_struct_init!(U).init;
1313                 }
1314             }
1315         }
1316     }
1317 }
1318 
1319 template IsDef(string pyname) {
1320     template IsDef(Params...) {
1321         import std.algorithm: startsWith;
1322         static if(Params[0].stringof.startsWith("Def!") &&
1323                 __traits(hasMember,Params[0], "funcname")) {
1324             enum bool IsDef = (Params[0].funcname == pyname);
1325         }else{
1326             enum bool IsDef = false;
1327         }
1328     }
1329 }
1330 struct Iterator(Params...) {
1331     alias Filter!(IsDef!"__iter__", Params) Iters;
1332     alias Filter!(IsDef!"next", Params) Nexts;
1333     enum bool needs_shim = false;
1334     static void call(T)() {
1335         alias PydTypeObject!T type;
1336         import std.range;
1337         static if(Iters.length == 1 && (Nexts.length == 1 || isInputRange!(ReturnType!(Iters[0].func)))) {
1338             version(Python_3_0_Or_Later) {
1339             }else{
1340                 type.tp_flags |= Py_TPFLAGS_HAVE_ITER;
1341             }
1342             type.tp_iter = &opiter_wrap!(T, Iters[0].func).func;
1343             static if(Nexts.length == 1)
1344                 type.tp_iternext = &opiter_wrap!(T, Nexts[0].func).func;
1345         }
1346     }
1347 }
1348 
1349 template IsOpIndex(P...) {
1350     import std.algorithm: startsWith;
1351     enum bool IsOpIndex = P[0].stringof.startsWith("OpIndex!");
1352 }
1353 template IsOpIndexAssign(P...) {
1354     import std.algorithm: startsWith;
1355     enum bool IsOpIndexAssign = P[0].stringof.startsWith("OpIndexAssign!");
1356 }
1357 template IsOpSlice(P...) {
1358     import std.algorithm: startsWith;
1359     enum bool IsOpSlice = P[0].stringof.startsWith("OpSlice!");
1360 }
1361 template IsOpSliceAssign(P...) {
1362     import std.algorithm: startsWith;
1363     enum bool IsOpSliceAssign = P[0].stringof.startsWith("OpSliceAssign!");
1364 }
1365 template IsLen(P...) {
1366     import std.algorithm: startsWith;
1367     enum bool IsLen = P[0].stringof.startsWith("Len!");
1368 }
1369 /*
1370    Extended slice syntax goes through mp_subscript, mp_ass_subscript,
1371    not sq_slice, sq_ass_slice.
1372 
1373 TODO: Python's extended slicing is more powerful than D's. We should expose
1374 this.
1375 */
1376 struct IndexSliceMerge(Params...) {
1377     alias Filter!(IsOpIndex, Params) OpIndexs;
1378     alias Filter!(IsOpIndexAssign, Params) OpIndexAssigns;
1379     alias Filter!(IsOpSlice, Params) OpSlices;
1380     alias Filter!(IsOpSliceAssign, Params) OpSliceAssigns;
1381     alias Filter!(IsLen, Params) Lens;
1382 
1383     static assert(OpIndexs.length <= 1);
1384     static assert(OpIndexAssigns.length <= 1);
1385     static assert(OpSlices.length <= 1);
1386     static assert(OpSliceAssigns.length <= 1);
1387 
1388     static void call(T)() {
1389         alias PydTypeObject!T type;
1390         static if(OpIndexs.length + OpSlices.length) {
1391             {
1392                 enum slot = "type.tp_as_mapping.mp_subscript";
1393                 mixin(autoInitializeMethods());
1394                 mixin(slot ~ " = &op_func!(T);");
1395             }
1396         }
1397         static if(OpIndexAssigns.length + OpSliceAssigns.length) {
1398             {
1399                 enum slot = "type.tp_as_mapping.mp_ass_subscript";
1400                 mixin(autoInitializeMethods());
1401                 mixin(slot ~ " = &ass_func!(T);");
1402             }
1403         }
1404     }
1405 
1406 
1407     static extern(C) PyObject* op_func(T)(PyObject* self, PyObject* key) {
1408         import std..string: format;
1409 
1410         static if(OpIndexs.length) {
1411             version(Python_2_5_Or_Later) {
1412                 Py_ssize_t i;
1413                 if(!PyIndex_Check(key)) goto slice;
1414                 i = PyNumber_AsSsize_t(key, PyExc_IndexError);
1415             }else{
1416                 C_long i;
1417                 if(!PyInt_Check(key)) goto slice;
1418                 i = PyLong_AsLong(key);
1419             }
1420             if(i == -1 && PyErr_Occurred()) {
1421                 return null;
1422             }
1423             alias OpIndexs[0] OpIndex0;
1424             return opindex_wrap!(T, OpIndex0.Inner!T.FN).func(self, key);
1425         }
1426 slice:
1427         static if(OpSlices.length) {
1428             if(PySlice_Check(key)) {
1429                 Py_ssize_t len = PyObject_Length(self);
1430                 Py_ssize_t start, stop, step, slicelength;
1431                 if(PySlice_GetIndicesEx(key, len,
1432                             &start, &stop, &step, &slicelength) < 0) {
1433                     return null;
1434                 }
1435                 if(step != 1) {
1436                     PyErr_SetString(PyExc_TypeError,
1437                             "slice steps not supported in D");
1438                     return null;
1439                 }
1440                 alias OpSlices[0] OpSlice0;
1441                 return opslice_wrap!(T, OpSlice0.Inner!T.FN).func(
1442                         self, start, stop);
1443             }
1444         }
1445         PyErr_SetString(PyExc_TypeError, format(
1446                     "index type '%s' not supported\0", to!string(key.ob_type.tp_name)).ptr);
1447         return null;
1448     }
1449 
1450     static extern(C) int ass_func(T)(PyObject* self, PyObject* key,
1451             PyObject* val) {
1452         import std..string: format;
1453 
1454         static if(OpIndexAssigns.length) {
1455             version(Python_2_5_Or_Later) {
1456                 Py_ssize_t i;
1457                 if(!PyIndex_Check(key)) goto slice;
1458                 i = PyNumber_AsSsize_t(key, PyExc_IndexError);
1459             }else{
1460                 C_long i;
1461                 if(!PyInt_Check(key)) goto slice;
1462                 i = PyLong_AsLong(key);
1463             }
1464             if(i == -1 && PyErr_Occurred()) {
1465                 return -1;
1466             }
1467             alias OpIndexAssigns[0] OpIndexAssign0;
1468             return opindexassign_wrap!(T, OpIndexAssign0.Inner!T.FN).func(
1469                     self, key, val);
1470         }
1471 slice:
1472         static if(OpSliceAssigns.length) {
1473             if(PySlice_Check(key)) {
1474                 Py_ssize_t len = PyObject_Length(self);
1475                 Py_ssize_t start, stop, step, slicelength;
1476                 if(PySlice_GetIndicesEx(key, len,
1477                             &start, &stop, &step, &slicelength) < 0) {
1478                     return -1;
1479                 }
1480                 if(step != 1) {
1481                     PyErr_SetString(PyExc_TypeError,
1482                             "slice steps not supported in D");
1483                     return -1;
1484                 }
1485                 alias OpSliceAssigns[0] OpSliceAssign0;
1486                 return opsliceassign_wrap!(T, OpSliceAssign0.Inner!T.FN).func(
1487                         self, start, stop, val);
1488             }
1489         }
1490         PyErr_SetString(PyExc_TypeError, format(
1491                     "assign index type '%s' not supported\0", to!string(key.ob_type.tp_name)).ptr);
1492         return -1;
1493     }
1494 }
1495 
1496 /*
1497 Params: each param is a Type which supports the interface
1498 
1499 Param.needs_shim == false => Param.call!(pyclassname, T)
1500 or
1501 Param.needs_shim == true => Param.call!(pyclassname,T, Shim)
1502 
1503     performs appropriate mutations to the PyTypeObject
1504 
1505 Param.shim!(i,T) for i : Params[i] == Param
1506 
1507     generates a string to be mixed in to Shim type
1508 
1509 where T is the type being wrapped, Shim is the wrapped type
1510 
1511 */
1512 
1513 /**
1514   Wrap a class.
1515 
1516 Params:
1517     T = The class being wrapped.
1518     Params = Mixture of definitions of members of T to be wrapped and
1519     optional arguments.
1520     Concerning optional arguments, accepts PyName!(pyname), ModuleName!(modulename), and Docstring!(docstring).
1521     pyname = The name of the class as it will appear in Python. Defaults to
1522     T's name in D
1523     modulename = The name of the python module in which the wrapped class
1524             resides. Defaults to "".
1525     docstring = The class's docstring. Defaults to "".
1526   */
1527 void wrap_class(T, Params...)() {
1528     alias Args!("","", __traits(identifier,T), "",Params) args;
1529     _wrap_class!(T, args.pyname, args.docstring, args.modulename, args.rem).wrap_class();
1530 }
1531 template _wrap_class(_T, string name, string docstring, string modulename, Params...) {
1532     import std.conv;
1533     import util.typelist;
1534     static if (is(_T == class)) {
1535         //pragma(msg, "wrap_class: " ~ name);
1536         alias pyd.make_wrapper.make_wrapper!(_T, Params).wrapper shim_class;
1537         //alias W.wrapper shim_class;
1538         alias _T T;
1539     } else {
1540         //pragma(msg, "wrap_struct: '" ~ name ~ "'");
1541         alias void shim_class;
1542         alias _T* T;
1543     }
1544     void wrap_class() {
1545         if(!Pyd_Module_p(modulename)) {
1546             if(should_defer_class_wrap(modulename, name)) {
1547                 defer_class_wrap(modulename, name,  toDelegate(&wrap_class));
1548                 return;
1549             }
1550         }
1551         alias PydTypeObject!(T) type;
1552         init_PyTypeObject!T(type);
1553 
1554         foreach (param; Params) {
1555             static if (param.needs_shim) {
1556                 param.call!(name, T, shim_class)();
1557             } else {
1558                 param.call!(name,T)();
1559             }
1560         }
1561 
1562         assert(Pyd_Module_p(modulename) !is null, "Must initialize module '" ~ modulename ~ "' before wrapping classes.");
1563         string module_name = to!string(PyModule_GetName(Pyd_Module_p(modulename)));
1564 
1565         //////////////////
1566         // Basic values //
1567         //////////////////
1568         Py_SET_TYPE(&type, &PyType_Type);
1569         type.tp_basicsize = PyObject.sizeof;
1570         type.tp_doc       = (docstring ~ "\0").ptr;
1571         version(Python_3_0_Or_Later) {
1572             type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
1573         }else{
1574             type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES;
1575         }
1576         //type.tp_repr      = &wrapped_repr!(T).repr;
1577         type.tp_methods   = wrapped_method_list!(T).ptr;
1578         type.tp_name      = (module_name ~ "." ~ name ~ "\0").ptr;
1579 
1580         /////////////////
1581         // Inheritance //
1582         /////////////////
1583         // Inherit classes from their wrapped superclass.
1584         static if (is(T B == super)) {
1585             foreach (C; B) {
1586                 static if (is(C == class) && !is(C == Object)) {
1587                     if (is_wrapped!(C)) {
1588                         type.tp_base = &PydTypeObject!(C);
1589                     }
1590                 }
1591             }
1592         }
1593 
1594         ////////////////////////
1595         // Operator overloads //
1596         ////////////////////////
1597 
1598         Operators!(Filter!(IsOp, Params)).call!T();
1599         // its just that simple.
1600 
1601         IndexSliceMerge!(Params).call!T();
1602         // indexing and slicing aren't exactly simple.
1603 
1604         //////////////////////////
1605         // Constructor wrapping //
1606         //////////////////////////
1607         Constructors!(name, Filter!(IsInit, Params)).call!(T, shim_class)();
1608 
1609         //////////////////////////
1610         // Iterator wrapping    //
1611         //////////////////////////
1612         Iterator!(Params).call!(T)();
1613 
1614 
1615         //////////////////
1616         // Finalization //
1617         //////////////////
1618         if (PyType_Ready(&type) < 0) {
1619             throw new Exception("Couldn't ready wrapped type!");
1620         }
1621         Py_INCREF(cast(PyObject*)&type);
1622         PyModule_AddObject(Pyd_Module_p(modulename), (name~"\0").ptr, cast(PyObject*)&type);
1623 
1624         is_wrapped!(T) = true;
1625         static if (is(T == class)) {
1626             is_wrapped!(shim_class) = true;
1627             wrapped_classes[T.classinfo] = &type;
1628             wrapped_classes[shim_class.classinfo] = &type;
1629         }
1630     }
1631 }
1632