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 pyd.util.typelist;
35 import pyd.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 pyd.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 pyd.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 pyd.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 pyd.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 string getBinarySlot(string op) {
600     version(Python_3_0_Or_Later) {
601         if (op == "/") return "type.tp_as_number.nb_true_divide";
602         if (op == "/=") return "type.tp_as_number.nb_inplace_true_divide";
603     }
604     return binaryslots[op];
605 }
606 
607 bool IsPyBinary(string op) {
608     foreach(_op, slot; binaryslots) {
609         if (op[$-1] != '=' && op == _op) return true;
610     }
611     return false;
612 }
613 bool IsPyAsg(string op0) {
614     auto op = op0~"=";
615     foreach(_op, slot; binaryslots) {
616         if (op == _op) return true;
617     }
618     return false;
619 }
620 
621 enum unaryslots = [
622     "+": "type.tp_as_number.nb_positive",
623     "-": "type.tp_as_number.nb_negative",
624     "~": "type.tp_as_number.nb_invert",
625 ];
626 
627 bool IsPyUnary(string op) {
628     foreach(_op, slot; unaryslots) {
629         if(op == _op) return true;
630     }
631     return false;
632 }
633 
634 // string mixin to initialize tp_as_number or tp_as_sequence or tp_as_mapping
635 // if necessary. Scope mixed in must have these variables:
636 //  slot: a value from binaryslots or unaryslots
637 //  type: a PyObjectType.
638 string autoInitializeMethods() {
639     return q{
640         import std.algorithm: countUntil;
641         static if(countUntil(slot, "tp_as_number") != -1) {
642             if(type.tp_as_number is null)
643                 type.tp_as_number = new PyNumberMethods;
644         }else static if(countUntil(slot, "tp_as_sequence") != -1) {
645             if(type.tp_as_sequence is null)
646                 type.tp_as_sequence = new PySequenceMethods;
647         }else static if(countUntil(slot, "tp_as_mapping") != -1) {
648             if(type.tp_as_mapping is null)
649                 type.tp_as_mapping = new PyMappingMethods;
650         }
651     };
652 }
653 
654 private struct Guess{}
655 
656 struct BinaryOperatorX(string _op, bool isR, rhs_t) {
657     enum op = _op;
658     enum isRight = isR;
659 
660     static if(isR) enum nom = "opBinaryRight";
661     else enum nom = "opBinary";
662 
663     enum bool needs_shim = false;
664 
665     template Inner(C) {
666         import std.string: format;
667 
668         enum fn_str1 = "Alias!(C."~nom~"!(op))";
669         enum fn_str2 = "C."~nom~"!(op,rhs_t)";
670         enum string OP = op;
671         static if(!__traits(hasMember, C, nom)) {
672             static assert(0, C.stringof ~ " has no "~(isR ?"reflected ":"")~
673                     "binary operator overloads");
674         }
675         template Alias(alias fn) {
676             alias fn Alias;
677         }
678         static if(is(typeof(mixin(fn_str1)) == function)) {
679             static if(_op == "/") {
680                 pragma(msg, "getted here 1");
681                 pragma(msg, C.stringof);
682             }
683             alias ParameterTypeTuple!(typeof(mixin(fn_str1)))[0] RHS_T;
684             alias ReturnType!(typeof(mixin(fn_str1))) RET_T;
685             mixin("alias " ~ fn_str1 ~ " FN;");
686             static if(!is(rhs_t == Guess))
687                 static assert(is(RHS_T == rhs_t),
688                         format("expected typeof(rhs) = %s, found %s",
689                             rhs_t.stringof, RHS_T.stringof));
690         }else static if(is(rhs_t == Guess)) {
691             static assert(false,
692                     format("Operator %s: Cannot determine type of rhs", op));
693         } else static if(is(typeof(mixin(fn_str2)) == function)) {
694             alias rhs_t RHS_T;
695             alias ReturnType!(typeof(mixin(fn_str2))) RET_T;
696             mixin("alias "~fn_str2~" FN;");
697         } else static assert(false, "Cannot get operator overload");
698     }
699 
700     static void call(string classname, T)() {
701         // can't handle __op__ __rop__ pairs here
702     }
703 
704     template shim(size_t i, T) {
705         // bah
706         enum shim = "";
707     }
708 }
709 
710 /**
711 Wrap a binary operator overload.
712 
713 Example:
714 ---
715 class Foo{
716     int _j;
717     int opBinary(string op)(int i) if(op == "+"){
718         return i+_j;
719     }
720     int opBinaryRight(string op)(int i) if(op == "+"){
721         return i+_j;
722     }
723 }
724 
725 class_wrap!(Foo,
726     OpBinary!("+"),
727     OpBinaryRight!("+"));
728 ---
729 
730 Params:
731     op = Operator to wrap
732     rhs_t = (optional) Type of opBinary's parameter for disambiguation if
733     there are multiple overloads.
734 Bugs:
735     Issue 8602 prevents disambiguation for case X opBinary(string op, T)(T t);
736   */
737 template OpBinary(string op, rhs_t = Guess) if(IsPyBinary(op) && op != "in"){
738     alias BinaryOperatorX!(op, false, rhs_t) OpBinary;
739 }
740 
741 /// ditto
742 template OpBinaryRight(string op, lhs_t = Guess) if(IsPyBinary(op)) {
743     alias BinaryOperatorX!(op, true, lhs_t) OpBinaryRight;
744 }
745 
746 /**
747   Wrap a unary operator overload.
748 */
749 struct OpUnary(string _op) if(IsPyUnary(_op)) {
750     enum op = _op;
751     enum bool needs_shim = false;
752 
753     template Inner(C) {
754         enum string OP = op;
755         static if(!__traits(hasMember, C, "opUnary")) {
756             static assert(0, C.stringof ~ " has no unary operator overloads");
757         }
758         static if(is(typeof(C.init.opUnary!(op)) == function)) {
759             alias ReturnType!(C.opUnary!(op)) RET_T;
760             alias C.opUnary!(op) FN;
761         } else static assert(false, "Cannot get operator overload");
762     }
763     static void call(string classname, T)() {
764         alias PydTypeObject!T type;
765         enum slot = unaryslots[op];
766         mixin(autoInitializeMethods());
767         mixin(slot ~ " = &opfunc_unary_wrap!(T, Inner!T .FN).func;");
768     }
769     template shim(size_t i,T) {
770         // bah
771         enum shim = "";
772     }
773 }
774 
775 /**
776   Wrap an operator assignment overload.
777 
778 Example:
779 ---
780 class Foo{
781     int _j;
782     void opOpAssign(string op)(int i) if(op == "+"){
783         _j = i;
784     }
785 }
786 
787 class_wrap!(Foo,
788     OpAssign!("+"));
789 ---
790 Params:
791     _op = Base operator to wrap
792     rhs_t = (optional) Type of opOpAssign's parameter for disambiguation if
793     there are multiple overloads.
794 */
795 struct OpAssign(string _op, rhs_t = Guess) if(IsPyAsg(_op)) {
796     enum op = _op~"=";
797 
798     enum bool needs_shim = false;
799 
800     template Inner(C) {
801         import std.string: format;
802 
803         enum string OP = op;
804         static if(!__traits(hasMember, C, "opOpAssign")) {
805             static assert(0, C.stringof ~ " has no operator assignment overloads");
806         }
807         static if(is(typeof(C.init.opOpAssign!(_op)) == function)) {
808             alias ParameterTypeTuple!(typeof(C.opOpAssign!(_op)))[0] RHS_T;
809             alias ReturnType!(typeof(C.opOpAssign!(_op))) RET_T;
810             alias C.opOpAssign!(_op) FN;
811             static if(!is(rhs_t == Guess))
812                 static assert(is(RHS_T == rhs_t),
813                         format("expected typeof(rhs) = %s, found %s",
814                             rhs_t.stringof, RHS_T.stringof));
815         }else static if(is(rhs_t == Guess)) {
816             static assert(false, "Cannot determine type of rhs");
817         } else static if(is(typeof(C.opOpAssign!(_op,rhs_t)) == function)) {
818             alias rhs_t RHS_T;
819             alias ReturnType!(typeof(C.opOpAssign!(_op,rhs_t))) RET_T;
820             alias C.opOpAssign!(_op,rhs_t) FN;
821         } else static assert(false, "Cannot get operator assignment overload");
822     }
823     static void call(string classname, T)() {
824         alias PydTypeObject!T type;
825         enum slot = getBinarySlot(op);
826         mixin(autoInitializeMethods());
827         alias CW!(TypeTuple!(OpAssign)) OpAsg;
828         alias CW!(TypeTuple!()) Nop;
829         static if(op == "^^=")
830             mixin(slot ~ " = &powopasg_wrap!(T, Inner!T.FN).func;");
831         else
832             mixin(slot ~ " = &binopasg_wrap!(T, Inner!T.FN).func;");
833     }
834 
835     template shim(size_t i,T) {
836         // bah
837         enum shim = "";
838     }
839 }
840 
841 // struct types could probably take any parameter type
842 // class types must take Object
843 /**
844   Wrap opCmp.
845 
846 Params:
847     _rhs_t = (optional) Type of opCmp's parameter for disambiguation if there
848     are multiple overloads (for classes it will always be Object).
849   */
850 struct OpCompare(_rhs_t = Guess) {
851     enum bool needs_shim = false;
852 
853 
854     template Inner(C) {
855         import std.string: format;
856         static if(is(_rhs_t == Guess) && is(C == class)) {
857             alias Object rhs_t;
858         }else {
859             alias _rhs_t rhs_t;
860         }
861         static if(!__traits(hasMember, C, "opCmp")) {
862             static assert(0, C.stringof ~ " has no comparison operator overloads");
863         }
864         static if(!is(typeof(C.init.opCmp) == function)) {
865             static assert(0, format("why is %s.opCmp not a function?",C));
866         }
867         alias TypeTuple!(__traits(getOverloads, C, "opCmp")) Overloads;
868         static if(is(rhs_t == Guess) && Overloads.length > 1) {
869             static assert(0, format("Cannot choose between %s", Overloads));
870         }else static if(Overloads.length == 1) {
871             static if(!is(rhs_t == Guess) &&
872                 !is(ParameterTypeTuple!(Overloads[0])[0] == rhs_t)) {
873                 static assert(0, format("%s.opCmp: expected param %s, got %s",
874                             C, rhs_t, ParameterTypeTuple!(Overloads[0])));
875             }else{
876                 alias Overloads[0] FN;
877             }
878         }else{
879             template IsDesiredOverload(alias fn) {
880                 enum bool IsDesiredOverload = is(ParameterTypeTuple!(fn)[0] == rhs_t);
881             }
882             alias Filter!(IsDesiredOverload, Overloads) Overloads1;
883             static assert(Overloads1.length == 1,
884                     format("Cannot choose between %s", Overloads1));
885             alias Overloads1[0] FN;
886         }
887     }
888     static void call(string classname, T)() {
889         alias PydTypeObject!T type;
890         alias ApplyConstness!(T, constness!(typeof(Inner!T.FN))) cT;
891         type.tp_richcompare = &rich_opcmp_wrap!(cT, Inner!T.FN).func;
892     }
893     template shim(size_t i,T) {
894         // bah
895         enum shim = "";
896     }
897 }
898 
899 /**
900   Wrap opIndex, opIndexAssign.
901 
902 Params:
903     index_t = (optional) Types of opIndex's parameters for disambiguation if
904     there are multiple overloads.
905 */
906 struct OpIndex(index_t...) {
907     enum bool needs_shim = false;
908     template Inner(C) {
909         import std.string: format;
910 
911         static if(!__traits(hasMember, C, "opIndex")) {
912             static assert(0, C.stringof ~ " has no index operator overloads");
913         }
914         static if(is(typeof(C.init.opIndex) == function)) {
915             alias TypeTuple!(__traits(getOverloads, C, "opIndex")) Overloads;
916             static if(index_t.length == 0 && Overloads.length > 1) {
917                 static assert(0,
918                         format("%s.opIndex: Cannot choose between %s",
919                             C.stringof,Overloads.stringof));
920             }else static if(index_t.length == 0) {
921                 alias Overloads[0] FN;
922             }else{
923                 template IsDesiredOverload(alias fn) {
924                     enum bool IsDesiredOverload = is(ParameterTypeTuple!fn == index_t);
925                 }
926                 alias Filter!(IsDesiredOverload, Overloads) Overloads1;
927                 static assert(Overloads1.length == 1,
928                         format("%s.opIndex: Cannot choose between %s",
929                             C.stringof,Overloads1.stringof));
930                 alias Overloads1[0] FN;
931             }
932         }else static if(is(typeof(C.init.opIndex!(index_t)) == function)) {
933             alias C.opIndex!(index_t) FN;
934         }else{
935             static assert(0,
936                     format("cannot get a handle on %s.opIndex", C.stringof));
937         }
938     }
939     static void call(string classname, T)() {
940         /*
941         alias PydTypeObject!T type;
942         enum slot = "type.tp_as_mapping.mp_subscript";
943         mixin(autoInitializeMethods());
944         mixin(slot ~ " = &opindex_wrap!(T, Inner!T.FN).func;");
945         */
946     }
947     template shim(size_t i,T) {
948         // bah
949         enum shim = "";
950     }
951 }
952 
953 /// ditto
954 struct OpIndexAssign(index_t...) {
955     static assert(index_t.length != 1,
956             "opIndexAssign must have at least 2 parameters");
957     enum bool needs_shim = false;
958     template Inner(C) {
959         import std.string: format;
960 
961         static if(!__traits(hasMember, C, "opIndexAssign")) {
962             static assert(0, C.stringof ~ " has no index operator overloads");
963         }
964         static if(is(typeof(C.init.opIndex) == function)) {
965             alias TypeTuple!(__traits(getOverloads, C, "opIndexAssign")) Overloads;
966             template IsValidOverload(alias fn) {
967                 enum bool IsValidOverload = ParameterTypeTuple!fn.length >= 2;
968             }
969             alias Filter!(IsValidOverload, Overloads) VOverloads;
970             static if(VOverloads.length == 0 && Overloads.length != 0)
971                 static assert(0,
972                         "opIndexAssign must have at least 2 parameters");
973             static if(index_t.length == 0 && VOverloads.length > 1) {
974                 static assert(0,
975                         format("%s.opIndexAssign: Cannot choose between %s",
976                             C.stringof,VOverloads.stringof));
977             }else static if(index_t.length == 0) {
978                 alias VOverloads[0] FN;
979             }else{
980                 template IsDesiredOverload(alias fn) {
981                     enum bool IsDesiredOverload = is(ParameterTypeTuple!fn == index_t);
982                 }
983                 alias Filter!(IsDesiredOverload, VOverloads) Overloads1;
984                 static assert(Overloads1.length == 1,
985                         format("%s.opIndex: Cannot choose between %s",
986                             C.stringof,Overloads1.stringof));
987                 alias Overloads1[0] FN;
988             }
989         }else static if(is(typeof(C.init.opIndexAssign!(index_t)) == function)) {
990             alias C.opIndexAssign!(index_t) FN;
991         }else{
992             static assert(0,
993                     format("cannot get a handle on %s.opIndexAssign", C.stringof));
994         }
995     }
996     static void call(string classname, T)() {
997         /*
998         alias PydTypeObject!T type;
999         enum slot = "type.tp_as_mapping.mp_ass_subscript";
1000         mixin(autoInitializeMethods());
1001         mixin(slot ~ " = &opindexassign_wrap!(T, Inner!T.FN).func;");
1002         */
1003     }
1004     template shim(size_t i,T) {
1005         // bah
1006         enum shim = "";
1007     }
1008 }
1009 
1010 /**
1011   Wrap opSlice.
1012 
1013   Requires signature
1014 ---
1015 Foo.opSlice(Py_ssize_t, Py_ssize_t);
1016 ---
1017  This is a limitation of the C/Python API.
1018   */
1019 struct OpSlice() {
1020     enum bool needs_shim = false;
1021     template Inner(C) {
1022         import std.string: format;
1023 
1024         static if(!__traits(hasMember, C, "opSlice")) {
1025             static assert(0, C.stringof ~ " has no slice operator overloads");
1026         }
1027         static if(is(typeof(C.init.opSlice) == function)) {
1028             alias TypeTuple!(__traits(getOverloads, C, "opSlice")) Overloads;
1029             template IsDesiredOverload(alias fn) {
1030                 enum bool IsDesiredOverload = is(ParameterTypeTuple!fn ==
1031                         TypeTuple!(Py_ssize_t,Py_ssize_t));
1032             }
1033             alias Filter!(IsDesiredOverload, Overloads) Overloads1;
1034             static assert(Overloads1.length != 0,
1035                     format("%s.opSlice: must have overload %s",
1036                         C.stringof,TypeTuple!(Py_ssize_t,Py_ssize_t).stringof));
1037             static assert(Overloads1.length == 1,
1038                     format("%s.opSlice: cannot choose between %s",
1039                         C.stringof,Overloads1.stringof));
1040             alias Overloads1[0] FN;
1041         }else{
1042             static assert(0, format("cannot get a handle on %s.opSlice",
1043                         C.stringof));
1044         }
1045     }
1046     static void call(string classname, T)() {
1047         /*
1048         alias PydTypeObject!T type;
1049         enum slot = "type.tp_as_sequence.sq_slice";
1050         mixin(autoInitializeMethods());
1051         mixin(slot ~ " = &opslice_wrap!(T, Inner!T.FN).func;");
1052         */
1053     }
1054     template shim(size_t i,T) {
1055         // bah
1056         enum shim = "";
1057     }
1058 }
1059 
1060 /**
1061   Wrap opSliceAssign.
1062 
1063   Requires signature
1064 ---
1065 Foo.opSliceAssign(Value,Py_ssize_t, Py_ssize_t);
1066 ---
1067  This is a limitation of the C/Python API.
1068   */
1069 struct OpSliceAssign(rhs_t = Guess) {
1070     enum bool needs_shim = false;
1071     template Inner(C) {
1072         static if(!__traits(hasMember, C, "opSliceAssign")) {
1073             static assert(0, C.stringof ~ " has no slice assignment operator overloads");
1074         }
1075         static if(is(typeof(C.init.opSliceAssign) == function)) {
1076             alias TypeTuple!(__traits(getOverloads, C, "opSliceAssign")) Overloads;
1077             template IsDesiredOverload(alias fn) {
1078                 alias ParameterTypeTuple!fn ps;
1079                 enum bool IsDesiredOverload =
1080                     is(ps[1..3] == TypeTuple!(Py_ssize_t,Py_ssize_t));
1081             }
1082             alias Filter!(IsDesiredOverload, Overloads) Overloads1;
1083             static assert(Overloads1.length != 0,
1084                     format("%s.opSliceAssign: must have overload %s",
1085                         C.stringof,TypeTuple!(Guess,Py_ssize_t,Py_ssize_t).stringof));
1086             static if(is(rhs_t == Guess)) {
1087                 static assert(Overloads1.length == 1,
1088                         format("%s.opSliceAssign: cannot choose between %s",
1089                             C.stringof,Overloads1.stringof));
1090                 alias Overloads1[0] FN;
1091             }else{
1092                 template IsDesiredOverload2(alias fn) {
1093                     alias ParameterTypeTuple!fn ps;
1094                     enum bool IsDesiredOverload2 = is(ps[0] == rhs_t);
1095                 }
1096                 alias Filter!(IsDesiredOverload2, Overloads1) Overloads2;
1097                 static assert(Overloads2.length == 1,
1098                         format("%s.opSliceAssign: cannot choose between %s",
1099                             C.stringof,Overloads2.stringof));
1100                 alias Overloads2[0] FN;
1101             }
1102         }else{
1103             static assert(0, format("cannot get a handle on %s.opSlice",
1104                         C.stringof));
1105         }
1106     }
1107     static void call(string classname, T)() {
1108         /*
1109         alias PydTypeObject!T type;
1110         enum slot = "type.tp_as_sequence.sq_ass_slice";
1111         mixin(autoInitializeMethods());
1112         mixin(slot ~ " = &opsliceassign_wrap!(T, Inner!T.FN).func;");
1113         */
1114     }
1115     template shim(size_t i,T) {
1116         // bah
1117         enum shim = "";
1118     }
1119 }
1120 
1121 /**
1122   wrap opCall. The parameter types of opCall must be specified.
1123 */
1124 struct OpCall(Args_t...) {
1125     enum bool needs_shim = false;
1126 
1127     template Inner(T) {
1128         import std.string: format;
1129 
1130         alias TypeTuple!(__traits(getOverloads, T, "opCall")) Overloads;
1131         template IsDesiredOverload(alias fn) {
1132             alias ParameterTypeTuple!fn ps;
1133             enum bool IsDesiredOverload = is(ps == Args_t);
1134         }
1135         alias Filter!(IsDesiredOverload, Overloads) VOverloads;
1136         static if(VOverloads.length == 0) {
1137             static assert(0,
1138                     format("%s.opCall: cannot find signature %s", T.stringof,
1139                         Args_t.stringof));
1140         }else static if(VOverloads.length == 1){
1141             alias VOverloads[0] FN;
1142         }else static assert(0,
1143                 format("%s.%s: cannot choose between %s", T.stringof, nom,
1144                     VOverloads.stringof));
1145     }
1146     static void call(string classname, T)() {
1147         alias PydTypeObject!T type;
1148         alias Inner!T.FN fn;
1149         alias ApplyConstness!(T, constness!(typeof(fn))) cT;
1150         type.tp_call = &opcall_wrap!(cT, fn).func;
1151     }
1152     template shim(size_t i,T) {
1153         // bah
1154         enum shim = "";
1155     }
1156 }
1157 
1158 /**
1159   Wraps Foo.length or another function as python's ___len__ function.
1160 
1161   Requires signature
1162 ---
1163 Py_ssize_t length();
1164 ---
1165   This is a limitation of the C/Python API.
1166   */
1167 template Len() {
1168     alias _Len!() Len;
1169 }
1170 
1171 /// ditto
1172 template Len(alias fn) {
1173     alias _Len!(fn) Len;
1174 }
1175 
1176 struct _Len(fnt...) {
1177     enum bool needs_shim = false;
1178     template Inner(T) {
1179         import std.string: format;
1180 
1181         static if(fnt.length == 0) {
1182             enum nom = "length";
1183         }else{
1184             enum nom = __traits(identifier, fnt[0]);
1185         }
1186         alias TypeTuple!(__traits(getOverloads, T, nom)) Overloads;
1187         template IsDesiredOverload(alias fn) {
1188             alias ParameterTypeTuple!fn ps;
1189             alias ReturnType!fn rt;
1190             enum bool IsDesiredOverload = isImplicitlyConvertible!(rt,Py_ssize_t) && ps.length == 0;
1191         }
1192         alias Filter!(IsDesiredOverload, Overloads) VOverloads;
1193         static if(VOverloads.length == 0 && Overloads.length != 0) {
1194             static assert(0,
1195                     format("%s.%s must have signature %s", T.stringof, nom,
1196                         (Py_ssize_t function()).stringof));
1197         }else static if(VOverloads.length == 1){
1198             alias VOverloads[0] FN;
1199         }else static assert(0,
1200                 format("%s.%s: cannot choose between %s", T.stringof, nom,
1201                     VOverloads.stringof));
1202     }
1203     static void call(string classname, T)() {
1204         alias PydTypeObject!T type;
1205         enum slot = "type.tp_as_sequence.sq_length";
1206         mixin(autoInitializeMethods());
1207         mixin(slot ~ " = &length_wrap!(T, Inner!T.FN).func;");
1208     }
1209     template shim(size_t i,T) {
1210         // bah
1211         enum shim = "";
1212     }
1213 }
1214 
1215 
1216 template param1(C) {
1217     template param1(T) {alias ParameterTypeTuple!(T.Inner!C .FN)[0] param1; }
1218 }
1219 
1220 enum IsOp(A) = __traits(hasMember, A, "op");
1221 
1222 template IsUn(A) {
1223     import std.algorithm: startsWith;
1224     enum IsUn = A.stringof.startsWith("OpUnary!");
1225 }
1226 
1227 template IsBin(T...) {
1228     import std.algorithm: startsWith;
1229     static if(T[0].stringof.startsWith("BinaryOperatorX!"))
1230         enum bool IsBin = !T[0].isRight;
1231     else
1232         enum bool IsBin = false;
1233 }
1234 template IsBinR(T...) {
1235     import std.algorithm: startsWith;
1236     static if(T[0].stringof.startsWith("BinaryOperatorX!"))
1237         enum IsBinR = T[0].isRight;
1238     else
1239         enum IsBinR = false;
1240 }
1241 
1242 // handle all operator overloads. Ops must only contain operator overloads.
1243 struct Operators(Ops...) {
1244     import pyd.util.replace: Replace;
1245     enum bool needs_shim = false;
1246 
1247     template BinOp(string op, T) {
1248         enum IsThisOp(A) = A.op == op;
1249         alias Filter!(IsThisOp, Ops) Ops0;
1250         alias Filter!(IsBin, Ops0) OpsL;
1251         alias staticMap!(param1!T, OpsL) OpsLparams;
1252         static assert(OpsL.length <= 1,
1253                 Replace!("Cannot overload $T1 $OP x with types $T2",
1254                     "$OP", op, "$T1", T.stringof, "$T2",  OpsLparams.stringof));
1255         alias Filter!(IsBinR, Ops0) OpsR;
1256         alias staticMap!(param1, OpsR) OpsRparams;
1257         static assert(OpsR.length <= 1,
1258                 Replace!("Cannot overload x $OP $T1 with types $T2",
1259                     "$OP", op, "$T1", T.stringof, "$T2",  OpsRparams.stringof));
1260         static assert(op[$-1] != '=' || OpsR.length == 0,
1261                 "Cannot reflect assignment operator");
1262 
1263         static void call() {
1264             static if(OpsL.length + OpsR.length != 0) {
1265                 alias PydTypeObject!T type;
1266                 enum slot = getBinarySlot(op);
1267                 mixin(autoInitializeMethods());
1268                 static if(op == "in") {
1269                     mixin(slot ~ " = &inop_wrap!(T, CW!OpsL, CW!OpsR).func;");
1270                 }else static if(op == "^^" || op == "^^=") {
1271                     mixin(slot ~ " = &powop_wrap!(T, CW!OpsL, CW!OpsR).func;");
1272                 }else {
1273                     mixin(slot ~ " = &binop_wrap!(T, CW!OpsL, CW!OpsR).func;");
1274                 }
1275             }
1276         }
1277 
1278     }
1279     struct UnOp(string op, T) {
1280         import pyd.util.replace: Replace;
1281         enum IsThisOp(A) = A.op == op;
1282         alias Filter!(IsUn, Filter!(IsThisOp, Ops)) Ops1;
1283         static assert(Ops1.length <= 1,
1284                 Replace!("Cannot have overloads of $OP$T1",
1285                     "$OP", op, "$T1", T.stringof));
1286         static void call() {
1287             static if(Ops1.length != 0) {
1288                 alias PydTypeObject!T type;
1289                 alias Ops1[0] Ops1_0;
1290                 alias Ops1_0.Inner!T .FN fn;
1291                 enum slot = unaryslots[op];
1292                 mixin(autoInitializeMethods());
1293                 mixin(slot ~ " = &opfunc_unary_wrap!(T, fn).func;");
1294             }
1295         }
1296     }
1297 
1298     static void call(T)() {
1299         enum GetOp(A) = A.op;
1300         alias NoDuplicates!(staticMap!(GetOp, Ops)) str_op_tuple;
1301         enum binops = binaryslots.keys();
1302         foreach(_op; str_op_tuple) {
1303             BinOp!(_op, T).call(); // noop if op is unary
1304             UnOp!(_op, T).call(); // noop if op is binary
1305         }
1306     }
1307 }
1308 
1309 struct Constructors(string classname, Ctors...) {
1310     enum bool needs_shim = true;
1311 
1312     static void call(T, Shim)() {
1313         alias PydTypeObject!T type;
1314         alias NewParamT!T U;
1315         static if(Ctors.length) {
1316             type.tp_init = &wrapped_ctors!(classname, T, Shim, Ctors).func;
1317         }else {
1318             // If a ctor wasn't supplied, try the default.
1319             // If the default ctor isn't available, and no ctors were supplied,
1320             // then this class cannot be instantiated from Python.
1321             // (Structs always use the default ctor.)
1322             static if (is(typeof(new U))) {
1323                 static if (is(U == class)) {
1324                     type.tp_init = &wrapped_init!(Shim).init;
1325                 } else {
1326                     type.tp_init = &wrapped_struct_init!(U).init;
1327                 }
1328             }
1329         }
1330     }
1331 }
1332 
1333 template IsDef(string pyname) {
1334     template IsDef(Params...) {
1335         import std.algorithm: startsWith;
1336         static if(Params[0].stringof.startsWith("Def!") &&
1337                 __traits(hasMember,Params[0], "funcname")) {
1338             enum bool IsDef = (Params[0].funcname == pyname);
1339         }else{
1340             enum bool IsDef = false;
1341         }
1342     }
1343 }
1344 struct Iterator(Params...) {
1345     alias Filter!(IsDef!"__iter__", Params) Iters;
1346     alias Filter!(IsDef!"next", Params) Nexts;
1347     enum bool needs_shim = false;
1348     static void call(T)() {
1349         alias PydTypeObject!T type;
1350         import std.range;
1351         static if(Iters.length == 1 && (Nexts.length == 1 || isInputRange!(ReturnType!(Iters[0].func)))) {
1352             version(Python_3_0_Or_Later) {
1353             }else{
1354                 type.tp_flags |= Py_TPFLAGS_HAVE_ITER;
1355             }
1356             type.tp_iter = &opiter_wrap!(T, Iters[0].func).func;
1357             static if(Nexts.length == 1)
1358                 type.tp_iternext = &opiter_wrap!(T, Nexts[0].func).func;
1359         }
1360     }
1361 }
1362 
1363 template IsOpIndex(P...) {
1364     import std.algorithm: startsWith;
1365     enum bool IsOpIndex = P[0].stringof.startsWith("OpIndex!");
1366 }
1367 template IsOpIndexAssign(P...) {
1368     import std.algorithm: startsWith;
1369     enum bool IsOpIndexAssign = P[0].stringof.startsWith("OpIndexAssign!");
1370 }
1371 template IsOpSlice(P...) {
1372     import std.algorithm: startsWith;
1373     enum bool IsOpSlice = P[0].stringof.startsWith("OpSlice!");
1374 }
1375 template IsOpSliceAssign(P...) {
1376     import std.algorithm: startsWith;
1377     enum bool IsOpSliceAssign = P[0].stringof.startsWith("OpSliceAssign!");
1378 }
1379 template IsLen(P...) {
1380     import std.algorithm: startsWith;
1381     enum bool IsLen = P[0].stringof.startsWith("Len!");
1382 }
1383 /*
1384    Extended slice syntax goes through mp_subscript, mp_ass_subscript,
1385    not sq_slice, sq_ass_slice.
1386 
1387 TODO: Python's extended slicing is more powerful than D's. We should expose
1388 this.
1389 */
1390 struct IndexSliceMerge(Params...) {
1391     alias Filter!(IsOpIndex, Params) OpIndexs;
1392     alias Filter!(IsOpIndexAssign, Params) OpIndexAssigns;
1393     alias Filter!(IsOpSlice, Params) OpSlices;
1394     alias Filter!(IsOpSliceAssign, Params) OpSliceAssigns;
1395     alias Filter!(IsLen, Params) Lens;
1396 
1397     static assert(OpIndexs.length <= 1);
1398     static assert(OpIndexAssigns.length <= 1);
1399     static assert(OpSlices.length <= 1);
1400     static assert(OpSliceAssigns.length <= 1);
1401 
1402     static void call(T)() {
1403         alias PydTypeObject!T type;
1404         static if(OpIndexs.length + OpSlices.length) {
1405             {
1406                 enum slot = "type.tp_as_mapping.mp_subscript";
1407                 mixin(autoInitializeMethods());
1408                 mixin(slot ~ " = &op_func!(T);");
1409             }
1410         }
1411         static if(OpIndexAssigns.length + OpSliceAssigns.length) {
1412             {
1413                 enum slot = "type.tp_as_mapping.mp_ass_subscript";
1414                 mixin(autoInitializeMethods());
1415                 mixin(slot ~ " = &ass_func!(T);");
1416             }
1417         }
1418     }
1419 
1420 
1421     static extern(C) PyObject* op_func(T)(PyObject* self, PyObject* key) {
1422         import std.string: format;
1423 
1424         static if(OpIndexs.length) {
1425             version(Python_2_5_Or_Later) {
1426                 Py_ssize_t i;
1427                 if(!PyIndex_Check(key)) goto slice;
1428                 i = PyNumber_AsSsize_t(key, PyExc_IndexError);
1429             }else{
1430                 C_long i;
1431                 if(!PyInt_Check(key)) goto slice;
1432                 i = PyLong_AsLong(key);
1433             }
1434             if(i == -1 && PyErr_Occurred()) {
1435                 return null;
1436             }
1437             alias OpIndexs[0] OpIndex0;
1438             return opindex_wrap!(T, OpIndex0.Inner!T.FN).func(self, key);
1439         }
1440 slice:
1441         static if(OpSlices.length) {
1442             if(PySlice_Check(key)) {
1443                 Py_ssize_t len = PyObject_Length(self);
1444                 Py_ssize_t start, stop, step, slicelength;
1445                 if(PySlice_GetIndicesEx(key, len,
1446                             &start, &stop, &step, &slicelength) < 0) {
1447                     return null;
1448                 }
1449                 if(step != 1) {
1450                     PyErr_SetString(PyExc_TypeError,
1451                             "slice steps not supported in D");
1452                     return null;
1453                 }
1454                 alias OpSlices[0] OpSlice0;
1455                 return opslice_wrap!(T, OpSlice0.Inner!T.FN).func(
1456                         self, start, stop);
1457             }
1458         }
1459         PyErr_SetString(PyExc_TypeError, format(
1460                     "index type '%s' not supported\0", to!string(key.ob_type.tp_name)).ptr);
1461         return null;
1462     }
1463 
1464     static extern(C) int ass_func(T)(PyObject* self, PyObject* key,
1465             PyObject* val) {
1466         import std.string: format;
1467 
1468         static if(OpIndexAssigns.length) {
1469             version(Python_2_5_Or_Later) {
1470                 Py_ssize_t i;
1471                 if(!PyIndex_Check(key)) goto slice;
1472                 i = PyNumber_AsSsize_t(key, PyExc_IndexError);
1473             }else{
1474                 C_long i;
1475                 if(!PyInt_Check(key)) goto slice;
1476                 i = PyLong_AsLong(key);
1477             }
1478             if(i == -1 && PyErr_Occurred()) {
1479                 return -1;
1480             }
1481             alias OpIndexAssigns[0] OpIndexAssign0;
1482             return opindexassign_wrap!(T, OpIndexAssign0.Inner!T.FN).func(
1483                     self, key, val);
1484         }
1485 slice:
1486         static if(OpSliceAssigns.length) {
1487             if(PySlice_Check(key)) {
1488                 Py_ssize_t len = PyObject_Length(self);
1489                 Py_ssize_t start, stop, step, slicelength;
1490                 if(PySlice_GetIndicesEx(key, len,
1491                             &start, &stop, &step, &slicelength) < 0) {
1492                     return -1;
1493                 }
1494                 if(step != 1) {
1495                     PyErr_SetString(PyExc_TypeError,
1496                             "slice steps not supported in D");
1497                     return -1;
1498                 }
1499                 alias OpSliceAssigns[0] OpSliceAssign0;
1500                 return opsliceassign_wrap!(T, OpSliceAssign0.Inner!T.FN).func(
1501                         self, start, stop, val);
1502             }
1503         }
1504         PyErr_SetString(PyExc_TypeError, format(
1505                     "assign index type '%s' not supported\0", to!string(key.ob_type.tp_name)).ptr);
1506         return -1;
1507     }
1508 }
1509 
1510 /*
1511 Params: each param is a Type which supports the interface
1512 
1513 Param.needs_shim == false => Param.call!(pyclassname, T)
1514 or
1515 Param.needs_shim == true => Param.call!(pyclassname,T, Shim)
1516 
1517     performs appropriate mutations to the PyTypeObject
1518 
1519 Param.shim!(i,T) for i : Params[i] == Param
1520 
1521     generates a string to be mixed in to Shim type
1522 
1523 where T is the type being wrapped, Shim is the wrapped type
1524 
1525 */
1526 
1527 /**
1528   Wrap a class.
1529 
1530 Parameters:
1531     T = The class being wrapped.
1532     $(BR)
1533     Params = Mixture of definitions of members of T to be wrapped and
1534     optional arguments.
1535     $(BR)
1536     Concerning optional arguments, accepts 
1537     $(BR) 
1538     PyName!(pyname)
1539     The name of the class as it will appear in Python. Defaults to
1540     T's name in D
1541     $(BR)
1542     ModuleName!(modulename):
1543     The name of the python module in which the wrapped class
1544             resides. Defaults to "".
1545     $(BR)
1546     Docstring!(docstring):
1547     The class's docstring. Defaults to "".
1548   */
1549 void wrap_class(T, Params...)() {
1550     alias Args!("","", __traits(identifier,T), "",Params) args;
1551     _wrap_class!(T, args.pyname, args.docstring, args.modulename, args.rem).wrap_class();
1552 }
1553 template _wrap_class(_T, string name, string docstring, string modulename, Params...) {
1554     import std.conv;
1555     import pyd.util.typelist;
1556     static if (is(_T == class)) {
1557         //pragma(msg, "wrap_class: " ~ name);
1558         alias pyd.make_wrapper.make_wrapper!(_T, Params).wrapper shim_class;
1559         //alias W.wrapper shim_class;
1560         alias _T T;
1561     } else {
1562         //pragma(msg, "wrap_struct: '" ~ name ~ "'");
1563         alias void shim_class;
1564         alias _T* T;
1565     }
1566     void wrap_class() {
1567         if(!Pyd_Module_p(modulename)) {
1568             if(should_defer_class_wrap(modulename, name)) {
1569                 defer_class_wrap(modulename, name,  toDelegate(&wrap_class));
1570                 return;
1571             }
1572         }
1573         alias PydTypeObject!(T) type;
1574         init_PyTypeObject!T(type);
1575 
1576         foreach (param; Params) {
1577             static if (param.needs_shim) {
1578                 param.call!(name, T, shim_class)();
1579             } else {
1580                 param.call!(name,T)();
1581             }
1582         }
1583 
1584         assert(Pyd_Module_p(modulename) !is null, "Must initialize module '" ~ modulename ~ "' before wrapping classes.");
1585         string module_name = to!string(PyModule_GetName(Pyd_Module_p(modulename)));
1586 
1587         //////////////////
1588         // Basic values //
1589         //////////////////
1590         Py_SET_TYPE(&type, &PyType_Type);
1591         type.tp_basicsize = PyObject.sizeof;
1592         type.tp_doc       = (docstring ~ "\0").ptr;
1593         version(Python_3_0_Or_Later) {
1594             type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
1595         }else{
1596             type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES;
1597         }
1598         //type.tp_repr      = &wrapped_repr!(T).repr;
1599         type.tp_methods   = wrapped_method_list!(T).ptr;
1600         type.tp_name      = (module_name ~ "." ~ name ~ "\0").ptr;
1601 
1602         /////////////////
1603         // Inheritance //
1604         /////////////////
1605         // Inherit classes from their wrapped superclass.
1606         static if (is(T B == super)) {
1607             foreach (C; B) {
1608                 static if (is(C == class) && !is(C == Object)) {
1609                     if (is_wrapped!(C)) {
1610                         type.tp_base = &PydTypeObject!(C);
1611                     }
1612                 }
1613             }
1614         }
1615 
1616         ////////////////////////
1617         // Operator overloads //
1618         ////////////////////////
1619 
1620         Operators!(Filter!(IsOp, Params)).call!T();
1621         // its just that simple.
1622 
1623         IndexSliceMerge!(Params).call!T();
1624         // indexing and slicing aren't exactly simple.
1625 
1626         //////////////////////////
1627         // Constructor wrapping //
1628         //////////////////////////
1629         Constructors!(name, Filter!(IsInit, Params)).call!(T, shim_class)();
1630 
1631         //////////////////////////
1632         // Iterator wrapping    //
1633         //////////////////////////
1634         Iterator!(Params).call!(T)();
1635 
1636 
1637         //////////////////
1638         // Finalization //
1639         //////////////////
1640         if (PyType_Ready(&type) < 0) {
1641             throw new Exception("Couldn't ready wrapped type!");
1642         }
1643         Py_INCREF(cast(PyObject*)&type);
1644         PyModule_AddObject(Pyd_Module_p(modulename), (name~"\0").ptr, cast(PyObject*)&type);
1645 
1646         is_wrapped!(T) = true;
1647         static if (is(T == class)) {
1648             is_wrapped!(shim_class) = true;
1649             wrapped_classes[T.classinfo] = &type;
1650             wrapped_classes[shim_class.classinfo] = &type;
1651         }
1652     }
1653 }
1654