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 pyd.util.typelist; 36 import pyd.util.typeinfo; 37 import pyd.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 pyd.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 pyd.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 pyd.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 pyd.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 pyd.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 pyd.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