1 /*
2 Copyright (c) 2006 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   Mostly internal utilities.
25   */
26 module pyd.func_wrap;
27 
28 import deimos.python.Python;
29 import std.algorithm: max;
30 import std.exception: enforce;
31 import std.range;
32 import std.conv;
33 import std.compiler;
34 import std.string: format;
35 import util.typelist;
36 import util.typeinfo;
37 import util.replace: Replace;
38 
39 import pyd.def;
40 import pyd.references;
41 import pyd.class_wrap;
42 import pyd.exception;
43 import pyd.make_object;
44 
45 import std.traits;
46 
47 template hasFunctionAttrs(T) {
48     static if(isDelegate!T || isFunctionPointer!T) {
49         enum bool hasFunctionAttrs = functionAttributes!T !=
50             FunctionAttribute.none;
51     }else{
52         enum bool hasFunctionAttrs = false;
53     }
54 }
55 
56 template StripFunctionAttributes(F) {
57     static if(hasFunctionAttrs!F) {
58         alias StripFunctionAttributes = SetFunctionAttributes!(F,
59                 functionLinkage!F,
60                 StrippedFunctionAttributes);
61     }else{
62         alias StripFunctionAttributes = F;
63     }
64 }
65 
66 static if(version_major == 2 && version_minor >= 67) {
67     enum StrippedFunctionAttributes = FunctionAttribute.system;
68 }else{
69     enum StrippedFunctionAttributes = FunctionAttribute.none;
70 }
71 
72 // Builds a callable Python object from a delegate or function pointer.
73 void PydWrappedFunc_Ready(S)() {
74     alias T = StripFunctionAttributes!S;
75     alias PydTypeObject!(T) type;
76     alias wrapped_class_object!(T) obj;
77     if (!is_wrapped!(T)) {
78         init_PyTypeObject!T(type);
79         Py_SET_TYPE(&type, &PyType_Type);
80         type.tp_basicsize = obj.sizeof;
81         type.tp_name = "PydFunc".ptr;
82         type.tp_flags = Py_TPFLAGS_DEFAULT;
83 
84         type.tp_call = &wrapped_func_call!(T).call;
85 
86         PyType_Ready(&type);
87         is_wrapped!T = true;
88     }
89 }
90 
91 void setWrongArgsError(Py_ssize_t gotArgs, size_t minArgs, size_t maxArgs, string funcName="") {
92 
93     string argStr(size_t args) {
94         string temp = to!string(args) ~ " argument";
95         if (args > 1) {
96             temp ~= "s";
97         }
98         return temp;
99     }
100     string str = (funcName == ""?"function":funcName~"()") ~ "takes";
101 
102     if (minArgs == maxArgs) {
103         if (minArgs == 0) {
104             str ~= "no arguments";
105         } else {
106             str ~= "exactly " ~ argStr(minArgs);
107         }
108     }
109     else if (gotArgs < minArgs) {
110         str ~= "at least " ~ argStr(minArgs);
111     } else {
112         str ~= "at most " ~ argStr(maxArgs);
113     }
114     str ~= " (" ~ to!string(gotArgs) ~ " given)";
115 
116     PyErr_SetString(PyExc_TypeError, (str ~ "\0").dup.ptr);
117 }
118 
119 // compose a tuple without cast-breaking constness
120 // example:
121 // ----------
122 // alias TupleComposer!(immutable(int), immutable(string)) T1;
123 // T1* t = new T1(1);
124 // t = t.put!1("foo");
125 // // t.fields is a thing now
126 struct TupleComposer(Ts...) {
127     Ts fields;
128 
129     TupleComposer!Ts* put(size_t i)(Ts[i] val) {
130         static if(isAssignable!(Ts[i])) {
131             fields[i] = val;
132             return &this;
133         }else{
134             return new TupleComposer(fields[0 .. i], val);
135         }
136 
137     }
138 }
139 
140 // Calls callable alias fn with PyTuple args.
141 // kwargs may be null, args may not
142 ReturnType!fn applyPyTupleToAlias(alias fn, string fname)(PyObject* args, PyObject* kwargs) {
143     alias ParameterTypeTuple!fn T;
144     enum size_t MIN_ARGS = minArgs!fn;
145     alias maxArgs!fn MaxArgs;
146     alias ReturnType!fn RT;
147     bool argsoverwrote = false;
148 
149     Py_ssize_t argCount = 0;
150     // This can make it more convenient to call this with 0 args.
151     if(kwargs !is null && PyObject_Length(kwargs) > 0) {
152         args = arrangeNamedArgs!(fn,fname)(args, kwargs);
153         Py_ssize_t newlen = PyObject_Length(args);
154         argsoverwrote = true;
155     }
156     scope(exit) if(argsoverwrote) Py_DECREF(args);
157     if (args !is null) {
158         argCount += PyObject_Length(args);
159     }
160 
161     // Sanity check!
162     if (!supportsNArgs!(fn)(argCount)) {
163         setWrongArgsError(cast(int) argCount, MIN_ARGS,
164                 (MaxArgs.hasMax ? MaxArgs.max:-1));
165         handle_exception();
166     }
167 
168     static if (MaxArgs.vstyle == Variadic.no && MIN_ARGS == 0) {
169         if (argCount == 0) {
170             return fn();
171         }
172     }
173     auto t = new TupleComposer!(MaxArgs.ps)();
174     foreach(i, arg; t.fields) {
175         enum size_t argNum = i+1;
176         static if(MaxArgs.vstyle == Variadic.no) {
177 			//https://issues.dlang.org/show_bug.cgi?id=17192
178 			//alias ParameterDefaultValueTuple!fn Defaults;
179 			import util.typeinfo : WorkaroundParameterDefaults;
180 			alias Defaults = WorkaroundParameterDefaults!fn;
181             if (i < argCount) {
182                 auto bpobj =  PyTuple_GetItem(args, cast(Py_ssize_t) i);
183                 if(bpobj) {
184                     auto pobj = Py_XINCREF(bpobj);
185                     t = t.put!i(python_to_d!(typeof(arg))(pobj));
186                     Py_DECREF(pobj);
187                 }else{
188                     static if(!is(Defaults[i] == void)) {
189                         t = t.put!i(Defaults[i]);
190                     }else{
191                         // should never happen
192                         enforce(0, "python non-keyword arg is NULL!");
193                     }
194                 }
195             }
196             static if (argNum >= MIN_ARGS &&
197                     (!MaxArgs.hasMax || argNum <= MaxArgs.max)) {
198                 if (argNum == argCount) {
199                     return fn(t.fields[0 .. argNum]);
200                 }
201             }
202         }else static if(MaxArgs.vstyle == Variadic.typesafe) {
203             static if (argNum < t.fields.length) {
204                 auto pobj = Py_XINCREF(PyTuple_GetItem(args, cast(Py_ssize_t) i));
205                 t = t.put!i(python_to_d!(typeof(arg))(pobj));
206                 Py_DECREF(pobj);
207             }else static if(argNum == t.fields.length) {
208                 alias Unqual!(ElementType!(typeof(t.fields[i]))) elt_t;
209                 auto varlen = argCount-i;
210                 if(varlen == 1) {
211                     auto  pobj = Py_XINCREF(PyTuple_GetItem(args, cast(Py_ssize_t) i));
212                     if(PyList_Check(pobj)) {
213                         try{
214                             t = t.put!i(cast(typeof(t.fields[i])) python_to_d!(elt_t[])(pobj));
215                         }catch(PythonException e) {
216                             t = t.put!i(cast(typeof(t.fields[i])) [python_to_d!elt_t(pobj)]);
217                         }
218                     }else{
219                         t = t.put!i(cast(typeof(t.fields[i])) [python_to_d!elt_t(pobj)]);
220                     }
221                     Py_DECREF(pobj);
222                 }else{
223                     elt_t[] vars = new elt_t[](argCount-i);
224                     foreach(j; i .. argCount) {
225                         auto  pobj = Py_XINCREF(PyTuple_GetItem(args, cast(Py_ssize_t) j));
226                         vars[j-i] = python_to_d!(elt_t)(pobj);
227                         Py_DECREF(pobj);
228                     }
229                     t = t.put!i(cast(typeof(t.fields[i])) vars);
230                 }
231                 return fn(t.fields);
232                 break;
233             }
234         }else static assert(0);
235     }
236     // This should never get here.
237     throw new Exception("applyPyTupleToAlias reached end! argCount = " ~ to!string(argCount));
238 }
239 
240 // wraps applyPyTupleToAlias to return a PyObject*
241 // kwargs may be null, args may not.
242 PyObject* pyApplyToAlias(alias fn, string fname) (PyObject* args, PyObject* kwargs) {
243     static if (is(ReturnType!fn == void)) {
244         applyPyTupleToAlias!(fn,fname)(args, kwargs);
245         return Py_INCREF(Py_None());
246     } else {
247         return d_to_python( applyPyTupleToAlias!(fn,fname)(args, kwargs) );
248     }
249 }
250 
251 ReturnType!(dg_t) applyPyTupleToDelegate(dg_t) (dg_t dg, PyObject* args) {
252     alias ParameterTypeTuple!(dg_t) T;
253     enum size_t ARGS = T.length;
254     alias ReturnType!(dg_t) RT;
255 
256     Py_ssize_t argCount = 0;
257     // This can make it more convenient to call this with 0 args.
258     if (args !is null) {
259         argCount = PyObject_Length(args);
260     }
261 
262     // Sanity check!
263     if (!supportsNArgs!(dg,dg_t)(argCount)) {
264         setWrongArgsError(argCount, ARGS, ARGS);
265         handle_exception();
266     }
267 
268     static if (ARGS == 0) {
269         if (argCount == 0) {
270             return dg();
271         }
272     }
273     T t;
274     foreach(i, arg; t) {
275         auto pi = Py_XINCREF(PyTuple_GetItem(args, cast(Py_ssize_t) i));
276         t[i] = python_to_d!(typeof(arg))(pi);
277         Py_DECREF(pi);
278     }
279     return dg(t);
280 }
281 
282 // wraps applyPyTupleToDelegate to return a PyObject*
283 PyObject* pyApplyToDelegate(dg_t) (dg_t dg, PyObject* args) {
284     static if (is(ReturnType!(dg_t) == void)) {
285         applyPyTupleToDelegate(dg, args);
286         return Py_INCREF(Py_None());
287     } else {
288         return d_to_python( applyPyTupleToDelegate(dg, args) );
289     }
290 }
291 
292 template wrapped_func_call(fn_t) {
293     enum size_t ARGS = ParameterTypeTuple!(fn_t).length;
294     alias ReturnType!(fn_t) RT;
295     // The entry for the tp_call slot of the PydFunc types.
296     // (Or: What gets called when you pass a delegate or function pointer to
297     // Python.)
298     extern(C)
299     PyObject* call(PyObject* self, PyObject* args, PyObject* kwds) {
300         if (self is null) {
301             PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a function pointer.");
302             return null;
303         }
304 
305         return exception_catcher(delegate PyObject*() {
306             fn_t fn = get_d_reference!fn_t(self);
307             return pyApplyToDelegate(fn, args);
308         });
309     }
310 }
311 
312 // Wraps a function alias with a PyCFunctionWithKeywords.
313 template function_wrap(alias real_fn, string fnname) {
314     alias ParameterTypeTuple!real_fn Info;
315     enum size_t MAX_ARGS = Info.length;
316     alias ReturnType!real_fn RT;
317 
318     extern (C)
319     PyObject* func(PyObject* self, PyObject* args, PyObject* kwargs) {
320         return exception_catcher(delegate PyObject*() {
321             import thread = pyd.thread;
322             thread.ensureAttached();
323             return pyApplyToAlias!(real_fn,fnname)(args, kwargs);
324         });
325     }
326 }
327 
328 // Wraps a member function alias with a PyCFunction.
329 // func's args and kwargs may each be null.
330 template method_wrap(C, alias real_fn, string fname) {
331     static assert(constCompatible(constness!C, constness!(typeof(real_fn))),
332             format("constness mismatch instance: %s function: %s",
333                 C.stringof, typeof(real_fn).stringof));
334     alias ParameterTypeTuple!real_fn Info;
335     enum size_t ARGS = Info.length;
336     alias ReturnType!real_fn RT;
337     extern(C)
338     PyObject* func(PyObject* self, PyObject* args, PyObject* kwargs) {
339         return exception_catcher(delegate PyObject*() {
340             // Didn't pass a "self" parameter! Ack!
341             if (self is null) {
342                 PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a 'self' parameter.");
343                 return null;
344             }
345             C instance = get_d_reference!C(self);
346             if (instance is null) {
347                 PyErr_SetString(PyExc_ValueError, "Wrapped class instance is null!");
348                 return null;
349             }
350 
351             Py_ssize_t arglen = args is null ? 0 : PyObject_Length(args);
352             enforce(arglen != -1);
353             PyObject* self_and_args = PyTuple_New(cast(Py_ssize_t) arglen+1);
354             scope(exit) {
355                 Py_XDECREF(self_and_args);
356             }
357             enforce(self_and_args);
358             PyTuple_SetItem(self_and_args, 0, self);
359             Py_INCREF(self);
360             foreach(i; 0 .. arglen) {
361                 auto pobj = Py_XINCREF(PyTuple_GetItem(args, cast(Py_ssize_t) i));
362                 PyTuple_SetItem(self_and_args, cast(Py_ssize_t) i+1, pobj);
363             }
364             alias memberfunc_to_func!(C,real_fn).func func;
365             return pyApplyToAlias!(func,fname)(self_and_args, kwargs);
366         });
367     }
368 }
369 
370 template method_dgwrap(C, alias real_fn) {
371     alias ParameterTypeTuple!real_fn Info;
372     enum size_t ARGS = Info.length;
373     alias ReturnType!real_fn RT;
374     extern(C)
375     PyObject* func(PyObject* self, PyObject* args) {
376         return exception_catcher(delegate PyObject*() {
377             // Didn't pass a "self" parameter! Ack!
378             if (self is null) {
379                 PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a 'self' parameter.");
380                 return null;
381             }
382             C instance = get_d_reference!C(self);
383             if (instance is null) {
384                 PyErr_SetString(PyExc_ValueError, "Wrapped class instance is null!");
385                 return null;
386             }
387             auto dg = dg_wrapper!(C, typeof(&real_fn))(instance, &real_fn);
388             return pyApplyToDelegate(dg, args);
389         });
390     }
391 }
392 
393 
394 template memberfunc_to_func(T, alias memfn) {
395     alias ReturnType!memfn Ret;
396     alias ParameterTypeTuple!memfn PS;
397     alias ParameterIdentifierTuple!memfn ids;
398 	//https://issues.dlang.org/show_bug.cgi?id=17192
399     //alias ParameterDefaultValueTuple!memfn dfs;
400 	import util.typeinfo : WorkaroundParameterDefaults;
401     alias dfs = WorkaroundParameterDefaults!memfn;
402     enum params = getparams!(memfn,"PS","dfs");
403     enum t = gensym!ids();
404 
405     mixin(Replace!(q{
406         Ret func(T $t, $params) {
407             auto dg = dg_wrapper($t, &memfn);
408             return dg($ids);
409         }
410     }, "$params", params, "$fn", __traits(identifier, memfn), "$t",t,
411        "$ids",Join!(",",ids)));
412 
413 }
414 
415 string gensym(Taken...)() {
416     bool ok(string s) {
417         bool _ok = true;
418         foreach(t; Taken) {
419             if(s == t) _ok = false;
420         }
421         return _ok;
422     }
423     foreach(c; 'a' .. 'z'+1) {
424         string s = to!string(cast(char)c);
425         if (ok(s)) return s;
426     }
427     // teh heck? wat kind of function takes more than 26 user-typed params?
428     int i = 0;
429     while(true) {
430         string s = format("_%s",i);
431         if (ok(s)) return s;
432         i++;
433     }
434 }
435 
436 //-----------------------------------------------------------------------------
437 // And now the reverse operation: wrapping a Python callable with a delegate.
438 // These rely on a whole collection of nasty templates, but the result is both
439 // flexible and pretty fast.
440 // (Sadly, wrapping a Python callable with a regular function is not quite
441 // possible.)
442 //-----------------------------------------------------------------------------
443 // The steps involved when calling this function are as follows:
444 // 1) An instance of PydWrappedFunc is made, and the callable placed within.
445 // 2) The delegate type Dg is broken into its constituent parts.
446 // 3) These parts are used to get the proper overload of PydWrappedFunc.fn
447 // 4) A delegate to PydWrappedFunc.fn is returned.
448 // 5) When fn is called, it attempts to cram the arguments into the callable.
449 //    If Python objects to this, an exception is raised. Note that this means
450 //    any error in converting the callable to a given delegate can only be
451 //    detected at runtime.
452 
453 Dg PydCallable_AsDelegate(Dg) (PyObject* c) {
454     return _pycallable_asdgT!(Dg).func(c);
455 }
456 
457 private template _pycallable_asdgT(Dg) if(is(Dg == delegate)) {
458     alias ParameterTypeTuple!(Dg) Info;
459     alias ReturnType!(Dg) Tr;
460 
461     Dg func(PyObject* c) {
462         static if(isImmutableFunction!Dg) {
463             auto f = cast(immutable) new PydWrappedFunc(c);
464             return &f.fn_i!(Tr,Info);
465         }else static if(isConstFunction!Dg) {
466             auto f = new const(PydWrappedFunc)(c);
467             return &f.fn_c!(Tr,Info);
468         }else{
469             auto f = new PydWrappedFunc(c);
470             return &f.fn!(Tr,Info);
471         }
472     }
473 }
474 
475 private
476 class PydWrappedFunc {
477     PyObject* callable;
478 
479     this(PyObject* c) {
480         callable = c;
481         Py_INCREF(c);
482     }
483 
484     ~this() {
485         if(callable && !Py_Finalize_called) {
486             Py_DECREF(callable);
487         }
488         callable = null;
489     }
490 
491     Tr fn(Tr, T ...) (T t) {
492         PyObject* ret = call(t);
493         if (ret is null) handle_exception();
494         scope(exit) Py_DECREF(ret);
495         return python_to_d!(Tr)(ret);
496     }
497     Tr fn_c(Tr, T ...) (T t) const {
498         PyObject* ret = call_c(t);
499         if (ret is null) handle_exception();
500         scope(exit) Py_DECREF(ret);
501         return python_to_d!(Tr)(ret);
502     }
503     Tr fn_i(Tr, T ...) (T t) immutable {
504         PyObject* ret = call_i(t);
505         if (ret is null) handle_exception();
506         scope(exit) Py_DECREF(ret);
507         return python_to_d!(Tr)(ret);
508     }
509 
510     PyObject* call(T ...) (T t) {
511         enum size_t ARGS = T.length;
512         PyObject* pyt = PyTuple_FromItems(t);
513         if (pyt is null) return null;
514         scope(exit) Py_DECREF(pyt);
515         return PyObject_CallObject(callable, pyt);
516     }
517     PyObject* call_c(T ...) (T t) const {
518         enum size_t ARGS = T.length;
519         PyObject* pyt = PyTuple_FromItems(t);
520         if (pyt is null) return null;
521         scope(exit) Py_DECREF(pyt);
522         return PyObject_CallObject(cast(PyObject*) callable, pyt);
523     }
524     PyObject* call_i(T ...) (T t) immutable {
525         enum size_t ARGS = T.length;
526         PyObject* pyt = PyTuple_FromItems(t);
527         if (pyt is null) return null;
528         scope(exit) Py_DECREF(pyt);
529         return PyObject_CallObject(cast(PyObject*) callable, pyt);
530     }
531 }
532 
533 PyObject* arrangeNamedArgs(alias fn, string fname)(PyObject* args, PyObject* kwargs) {
534     alias ParameterIdentifierTuple!fn ids;
535     string[] allfnnames = new string[](ids.length);
536     size_t[string] allfnnameset;
537     foreach(i,id; ids) {
538         allfnnames[i] = id;
539         allfnnameset[id] = i;
540     }
541     alias variadicFunctionStyle!fn vstyle;
542     size_t firstDefaultValueIndex = ids.length;
543     static if(vstyle == Variadic.no) {
544         //https://issues.dlang.org/show_bug.cgi?id=17192
545         //alias ParameterDefaultValueTuple!fn Defaults;
546 		import util.typeinfo : WorkaroundParameterDefaults;
547         alias Defaults = WorkaroundParameterDefaults!fn;
548         foreach(i, v; Defaults) {
549             static if(!is(v == void)) {
550                 firstDefaultValueIndex = i;
551                 break;
552             }
553         }
554     }
555 
556     Py_ssize_t arglen = PyObject_Length(args);
557     enforce(arglen != -1);
558     Py_ssize_t kwarglen = PyObject_Length(kwargs);
559     enforce(kwarglen != -1);
560     // variadic args might give us a count greater than ids.length
561     // (but in that case there should be no kwargs)
562     auto allargs = PyTuple_New(cast(Py_ssize_t)
563             max(ids.length, arglen+kwarglen));
564 
565     foreach(i; 0 .. arglen) {
566         auto pobj = Py_XINCREF(PyTuple_GetItem(args, cast(Py_ssize_t) i));
567         PyTuple_SetItem(allargs, cast(Py_ssize_t) i, pobj);
568     }
569     PyObject* keys = PyDict_Keys(kwargs);
570     enforce(keys);
571     for(size_t _n = 0; _n < kwarglen; _n++) {
572         PyObject* pkey = PySequence_GetItem(keys, cast(Py_ssize_t) _n);
573         auto name = python_to_d!string(pkey);
574         if(name !in allfnnameset) {
575             enforce(false, format("%s() got an unexpected keyword argument '%s'",fname, name));
576 
577 
578         }
579         size_t n = allfnnameset[name];
580         auto bval = PyDict_GetItem(kwargs, pkey);
581         if(bval) {
582             auto val = Py_XINCREF(bval);
583             PyTuple_SetItem(allargs, cast(Py_ssize_t) n, val);
584         }else if(vstyle == Variadic.no && n >= firstDefaultValueIndex) {
585             // ok, we can get the default value
586         }else{
587             enforce(false, format("argument '%s' is NULL! <%s, %s, %s, %s>",
588                         name, n, firstDefaultValueIndex, ids.length,
589                         vstyle == Variadic.no));
590         }
591     }
592     Py_DECREF(keys);
593     return allargs;
594 }
595 
596 template minNumArgs_impl(alias fn, fnT) {
597     alias ParameterTypeTuple!(fnT) Params;
598 	//https://issues.dlang.org/show_bug.cgi?id=17192
599     //alias ParameterDefaultValueTuple!(fn) Defaults;
600 	import util.typeinfo : WorkaroundParameterDefaults;
601 	alias Defaults = WorkaroundParameterDefaults!fn;
602     alias variadicFunctionStyle!fn vstyle;
603     static if(Params.length == 0) {
604         // handle func(), func(...)
605         enum res = 0;
606     }else static if(vstyle == Variadic.typesafe){
607         // handle func(nondefault T1 t1, nondefault T2 t2, etc, TN[]...)
608         enum res = Params.length-1;
609     }else {
610         size_t count_nondefault() {
611             size_t result = 0;
612             foreach(i, v; Defaults) {
613                 static if(is(v == void)) {
614                     result ++;
615                 }else break;
616             }
617             return result;
618         }
619         enum res = count_nondefault();
620     }
621 }
622 
623 /**
624   Finds the minimal number of arguments a given function needs to be provided
625  */
626 template minArgs(alias fn, fnT = typeof(&fn)) {
627     enum size_t minArgs = minNumArgs_impl!(fn, fnT).res;
628 }
629 
630 /**
631   Finds the maximum number of arguments a given function may be provided
632   and/or whether the function has a maximum number of arguments.
633   */
634 template maxArgs(alias fn, fn_t = typeof(&fn)) {
635     alias variadicFunctionStyle!fn vstyle;
636     alias ParameterTypeTuple!fn ps;
637     /// _
638     enum bool hasMax = vstyle == Variadic.no;
639     /// _
640     enum size_t max = ps.length;
641 }
642 
643 /**
644   Determines at runtime whether the function can be given n arguments.
645   */
646 bool supportsNArgs(alias fn, fn_t = typeof(&fn))(size_t n) {
647     if(n < minArgs!(fn,fn_t)) {
648         return false;
649     }
650     alias variadicFunctionStyle!fn vstyle;
651     alias ParameterTypeTuple!fn ps;
652 	//https://issues.dlang.org/show_bug.cgi?id=17192
653     //alias ParameterDefaultValueTuple!fn defaults;
654 	import util.typeinfo : WorkaroundParameterDefaults;
655 	alias defaults = WorkaroundParameterDefaults!fn;
656     static if(vstyle == Variadic.no) {
657         return (n >= minArgs!(fn,fn_t) && n <= maxArgs!(fn,fn_t).max);
658     }else static if(vstyle == Variadic.c) {
659         return true;
660     }else static if(vstyle == Variadic.d) {
661         return true;
662     }else static if(vstyle == Variadic.typesafe) {
663         return true;
664     }else static assert(0);
665 }
666 
667 /**
668   Get the parameters of function as a string.
669 
670   pt_alias refers to an alias of ParameterTypeTuple!fn
671   visible to wherever you want to mix in the results.
672   pd_alias refers to an alias of ParameterDefaultValueTuple!fn
673   visible to wherever you want to mix in the results.
674 Example:
675 ---
676 void foo(int i, int j=2) {
677 }
678 
679 static assert(getparams!(foo,"P","Pd") == "P[0] i, P[1] j = Pd[1]");
680 ---
681   */
682 template getparams(alias fn, string pt_alias, string pd_alias) {
683     alias ParameterIdentifierTuple!fn Pi;
684     //https://issues.dlang.org/show_bug.cgi?id=17192
685     //alias ParameterDefaultValueTuple!fn Pd;
686     import util.typeinfo : WorkaroundParameterDefaults;
687     alias Pd = WorkaroundParameterDefaults!fn;
688     enum var = variadicFunctionStyle!fn;
689 
690     string inner() {
691         static if(var == Variadic.c || var == Variadic.d) {
692             return "...";
693         }else{
694             string ret = "";
695             foreach(size_t i, id; Pi) {
696                 ret ~= format("%s[%s] %s", pt_alias, i, id);
697                 static if(!is(Pd[i] == void)) {
698                     ret ~= format(" = %s[%s]", pd_alias, i);
699                 }
700                 static if(i != Pi.length-1) {
701                     ret ~= ", ";
702                 }
703             }
704             static if(var == Variadic.typesafe) {
705                 ret ~= "...";
706             }
707             return ret;
708         }
709     }
710 
711     enum getparams = inner();
712 
713 }
714 
715 template isImmutableFunction(T...) if (T.length == 1) {
716     alias funcTarget!T func_t;
717     enum isImmutableFunction = is(func_t == immutable);
718 }
719 template isConstFunction(T...) if (T.length == 1) {
720     alias funcTarget!T func_t;
721     enum isConstFunction = is(func_t == const);
722 }
723 template isMutableFunction(T...) if (T.length == 1) {
724     alias funcTarget!T func_t;
725     enum isMutableFunction = !is(func_t == inout) && !is(func_t == const) && !is(func_t == immutable);
726 }
727 template isWildcardFunction(T...) if (T.length == 1) {
728     alias funcTarget!T func_t;
729     enum isWildcardFunction = is(func_t == inout);
730 }
731 template isSharedFunction(T...) if (T.length == 1) {
732     alias funcTarget!T func_t;
733     enum isSharedFunction = is(func_t == shared);
734 }
735 
736 template funcTarget(T...) if(T.length == 1) {
737     static if(isPointer!(T[0]) && is(PointerTarget!(T[0]) == function)) {
738         alias PointerTarget!(T[0]) funcTarget;
739     }else static if(is(T[0] == function)) {
740         alias T[0] funcTarget;
741     }else static if(is(T[0] == delegate)) {
742         alias PointerTarget!(typeof((T[0]).init.funcptr)) funcTarget;
743     }else static assert(false);
744 }
745 
746 string tattrs_to_string(fn_t)() {
747     string s;
748     if(isConstFunction!fn_t) {
749         s ~= " const";
750     }
751     if(isImmutableFunction!fn_t) {
752         s ~= " immutable";
753     }
754     if(isSharedFunction!fn_t) {
755         s ~= " shared";
756     }
757     if(isWildcardFunction!fn_t) {
758         s ~= " inout";
759     }
760     return s;
761 }
762 
763 bool constnessMatch2(fn...)(Constness c) if(fn.length == 1) {
764     static if(isImmutableFunction!(fn)) return c == Constness.Immutable;
765     static if(isMutableFunction!(fn)) return c == Constness.Mutable;
766     static if(isConstFunction!(fn)) return c != Constness.Wildcard;
767     else return false;
768 }
769 
770 /*
771  * some more or less dirty hacks for converting
772  * between function and delegate types. As of DMD 0.174, the language has
773  * built-in support for hacking apart delegates like this. Hooray!
774  */
775 
776 template fn_to_dgT(Fn) {
777     alias ParameterTypeTuple!(Fn) T;
778     alias ReturnType!(Fn) Ret;
779 
780     mixin("alias Ret delegate(T) " ~ tattrs_to_string!(Fn)() ~ " type;");
781 }
782 
783 /**
784  * This template converts a function type into an equivalent delegate type.
785  */
786 template fn_to_dg(Fn) {
787     alias fn_to_dgT!(Fn).type fn_to_dg;
788 }
789 
790 /**
791  * This template function converts a pointer to a member function into a
792  * delegate.
793  */
794 auto dg_wrapper(T, Fn) (T t, Fn fn) {
795     fn_to_dg!(Fn) dg;
796     dg.ptr = cast(void*) t;
797     static if(variadicFunctionStyle!fn == Variadic.typesafe) {
798         // trying to stuff a Ret function(P[]...) into a Ret function(P[])
799         // it'll totally work!
800         dg.funcptr = cast(typeof(dg.funcptr)) fn;
801     }else{
802         dg.funcptr = fn;
803     }
804 
805     return dg;
806 }
807