diff --git a/infrastructure/pyd/class_wrap.d b/infrastructure/pyd/class_wrap.d index bd51b75..31aa628 100644 --- a/infrastructure/pyd/class_wrap.d +++ b/infrastructure/pyd/class_wrap.d @@ -213,8 +213,8 @@ template wrapped_get(T, alias Fn) { /// A generic wrapper around a "getter" property. extern(C) PyObject* func(PyObject* self, void* closure) { - // func_wrap already catches exceptions - return func_wrap!(Fn, 0, T, property_parts!(Fn).getter_type).func(self, null); + // method_wrap already catches exceptions + return method_wrap!(T, Fn, property_parts!(Fn).getter_type).func(self, null); } } @@ -228,7 +228,7 @@ template wrapped_set(T, alias Fn) { scope(exit) Py_DECREF(temp_tuple); Py_INCREF(value); PyTuple_SetItem(temp_tuple, 0, value); - PyObject* res = func_wrap!(Fn, 1, T, property_parts!(Fn).setter_type).func(self, temp_tuple); + PyObject* res = method_wrap!(T, Fn, property_parts!(Fn).setter_type).func(self, temp_tuple); // If we get something back, we need to DECREF it. if (res) Py_DECREF(res); // If we don't, propagate the exception @@ -261,13 +261,13 @@ template wrapped_class(T, char[] classname = symbolnameof!(T)) { * fn_t = The type of the function. It is only useful to specify this * if more than one function has the same name as this one. */ - template def(alias fn, char[] name = symbolnameof!(fn), fn_t=typeof(&fn), uint MIN_ARGS = minArgs!(fn, fn_t)) { + template def(alias fn, char[] name = symbolnameof!(fn), fn_t=typeof(&fn)) { pragma(msg, "class.def: " ~ name); static void def() { static PyMethodDef empty = { null, null, 0, null }; alias wrapped_method_list!(T) list; list[length-1].ml_name = name ~ \0; - list[length-1].ml_meth = &func_wrap!(fn, MIN_ARGS, T, fn_t).func; + list[length-1].ml_meth = &method_wrap!(T, fn, fn_t).func; list[length-1].ml_flags = METH_VARARGS; list[length-1].ml_doc = ""; list ~= empty; @@ -412,7 +412,7 @@ void finalize_class(CLS) (CLS cls) { } // opCall static if (is(typeof(&T.opCall))) { - type.tp_call = cast(ternaryfunc)&func_wrap!(T.opCall, minArgs!(T.opCall), T).func; + type.tp_call = cast(ternaryfunc)&method_wrap!(T, T.opCall, typeof(&T.opCall)).func; } // If a ctor wasn't supplied, try the default. diff --git a/infrastructure/pyd/ctor_wrap.d b/infrastructure/pyd/ctor_wrap.d index c3a3c47..cd7368d 100644 --- a/infrastructure/pyd/ctor_wrap.d +++ b/infrastructure/pyd/ctor_wrap.d @@ -68,8 +68,17 @@ template wrapped_ctors(T, C ...) { foreach(i, arg; c) { alias ParameterTypeTuple!(typeof(arg)) Ctor; if (Ctor.length == len) { - alias call_ctor!(T, ParameterTypeTuple!(typeof(arg))) fn; - WrapPyObject_SetObj(self, py_call(&fn, args)); + auto fn = &call_ctor!(T, ParameterTypeTuple!(typeof(arg))); + if (fn is null) { + PyErr_SetString(PyExc_RuntimeError, "Couldn't get pointer to class ctor redirect."); + return -1; + } + T t = applyPyTupleToDelegate(fn, args); + if (t is null) { + PyErr_SetString(PyExc_RuntimeError, "Class ctor redirect didn't return a class instance!"); + return -1; + } + WrapPyObject_SetObj(self, t); return 0; } } diff --git a/infrastructure/pyd/def.d b/infrastructure/pyd/def.d index 2eda0b7..8f3d02a 100644 --- a/infrastructure/pyd/def.d +++ b/infrastructure/pyd/def.d @@ -79,7 +79,7 @@ template def(alias fn, char[] name = symbolnameof!(fn), fn_t=typeof(&fn), uint M alias module_global_methods list; list[length-1].ml_name = name ~ \0; - list[length-1].ml_meth = &func_wrap!(fn, MIN_ARGS, void, fn_t).func; + list[length-1].ml_meth = &function_wrap!(fn, MIN_ARGS, fn_t).func; list[length-1].ml_flags = METH_VARARGS; list[length-1].ml_doc = ""; list ~= empty; diff --git a/infrastructure/pyd/func_wrap.d b/infrastructure/pyd/func_wrap.d index b581f2b..37e916c 100644 --- a/infrastructure/pyd/func_wrap.d +++ b/infrastructure/pyd/func_wrap.d @@ -34,9 +34,10 @@ private { import std.string; import std.traits; + import std.stdio; } -// Builds a Python callable object from a delegate or function pointer. +// Builds a callable Python object from a delegate or function pointer. PyObject* PydFunc_FromDelegate(T) (T dg) { alias wrapped_class_type!(T) type; alias wrapped_class_object!(T) obj; @@ -90,38 +91,101 @@ void setWrongArgsError(int gotArgs, uint minArgs, uint maxArgs, char[] funcName= PyErr_SetString(PyExc_TypeError, str ~ \0); } -// Calls the passed function with the passed Python tuple. -ReturnType!(fn_t) py_call(fn_t, PY)(fn_t fn, PY* args) { +// Calls callable alias fn with PyTuple args. +ReturnType!(fn_t) applyPyTupleToAlias(alias fn, fn_t, uint MIN_ARGS) (PyObject* args) { alias ParameterTypeTuple!(fn_t) T; const uint MAX_ARGS = T.length; alias ReturnType!(fn_t) RT; - int ARGS = 0; + int argCount = 0; // This can make it more convenient to call this with 0 args. if (args !is null) { - ARGS = PyObject_Length(args); + argCount = PyObject_Length(args); } // Sanity check! - if (ARGS != MAX_ARGS) { - setWrongArgsError(ARGS, MAX_ARGS, MAX_ARGS); + if (argCount < MIN_ARGS || argCount > MAX_ARGS) { + setWrongArgsError(argCount, MIN_ARGS, MAX_ARGS); handle_exception(); } + static if (MIN_ARGS == 0) { + if (argCount == 0) { + return fn(); + } + } + T t; + foreach(i, arg; t) { + const uint argNum = i+1; + if (i < argCount) { + t[i] = d_type!(typeof(arg))(PyTuple_GetItem(args, i)); + } + static if (argNum >= MIN_ARGS && argNum <= MAX_ARGS) { + if (argNum == argCount) { + return fn(t[0 .. argNum]); + break; + } + } + } + // This should never get here. + throw new Exception("applyPyTupleToAlias reached end! argCount = " ~ toString(argCount)); + static if (!is(RT == void)) + return ReturnType!(fn_t).init; +} + +// wraps applyPyTupleToAlias to return a PyObject* +PyObject* pyApplyToAlias(alias fn, fn_t, uint MIN_ARGS) (PyObject* args) { + static if (is(ReturnType!(fn_t) == void)) { + applyPyTupleToAlias!(fn, fn_t, MIN_ARGS)(args); + Py_INCREF(Py_None); + return Py_None; + } else { + return _py( applyPyTupleToAlias!(fn, fn_t, MIN_ARGS)(args) ); + } +} + +ReturnType!(dg_t) applyPyTupleToDelegate(dg_t) (dg_t dg, PyObject* args) { + alias ParameterTypeTuple!(dg_t) T; + const uint ARGS = T.length; + alias ReturnType!(dg_t) RT; + + int argCount = 0; + // This can make it more convenient to call this with 0 args. + if (args !is null) { + argCount = PyObject_Length(args); + } + + // Sanity check! + if (argCount != ARGS) { + setWrongArgsError(argCount, ARGS, ARGS); + handle_exception(); + } + + static if (ARGS == 0) { + if (argCount == 0) { + return dg(); + } + } T t; foreach(i, arg; t) { t[i] = d_type!(typeof(arg))(PyTuple_GetItem(args, i)); } + return dg(t); +} - static if (is(RT == void)) { - fn(t); - return; +// wraps applyPyTupleToDelegate to return a PyObject* +PyObject* pyApplyToDelegate(dg_t) (dg_t dg, PyObject* args) { + static if (is(ReturnType!(dg_t) == void)) { + applyPyTupleToDelegate(dg, args); + Py_INCREF(Py_None); + return Py_None; } else { - return fn(t); + return _py( applyPyTupleToDelegate(dg, args) ); } } template wrapped_func_call(fn_t) { + const uint ARGS = ParameterTypeTuple!(fn_t).length; alias ReturnType!(fn_t) RT; // The entry for the tp_call slot of the PydFunc types. // (Or: What gets called when you pass a delegate or function pointer to @@ -136,89 +200,45 @@ template wrapped_func_call(fn_t) { fn_t fn = (cast(wrapped_class_object!(fn_t)*)self).d_obj; return exception_catcher({ - static if (is(RT == void)) { - py_call(fn, args); - Py_INCREF(Py_None); - return Py_None; - } else { - return _py( py_call(fn, args) ); - } + return pyApplyToDelegate(fn, args); }); } } -// This is a handy shortcut that allows us to wrap a function alias directly -// with a PyCFunction. -template func_wrap(alias real_fn, uint MIN_ARGS, C=void, fn_t=typeof(&real_fn)) { +// Wraps a function alias with a PyCFunction. +template function_wrap(alias real_fn, uint MIN_ARGS, fn_t=typeof(&real_fn)) { alias ParameterTypeTuple!(fn_t) Info; const uint MAX_ARGS = Info.length; alias ReturnType!(fn_t) RT; - // Wraps py_call to return a PyObject* - PyObject* py_py_call(fn_t, PY)(fn_t fn, PY* args) { - static if (is(RT == void)) { - py_call(fn, args); - Py_INCREF(Py_None); - return Py_None; - } else { - return _py( py_call(fn, args) ); - } - } - - // Calls py_py_call with the proper function contained in a tuple - // returned from tuples.func_range. - PyObject* tuple_py_call(PY, T ...)(PY* args, T t) { - int argCount = 0; - if (args !is null) - argCount = PyObject_Length(args); - - static if (MIN_ARGS == 0) { - if (argCount == 0) - return py_py_call(&firstArgs!(real_fn, 0, fn_t), args); - } - foreach (i, arg; t) { - if (ParameterTypeTuple!(typeof(arg)).length == argCount) { - return py_py_call(arg, args); - } - } + extern (C) + PyObject* func(PyObject* self, PyObject* args) { + return exception_catcher(delegate PyObject*() { + return pyApplyToAlias!(real_fn, fn_t, MIN_ARGS)(args); + }); } +} - extern (C) +// Wraps a member function alias with a PyCFunction. +template method_wrap(C, alias real_fn, fn_t=typeof(&real_fn)) { + alias ParameterTypeTuple!(fn_t) Info; + const uint ARGS = Info.length; + alias ReturnType!(fn_t) RT; + extern(C) PyObject* func(PyObject* self, PyObject* args) { - // For some reason, D can't infer the return type of this function - // literal... return exception_catcher(delegate PyObject*() { - // If C is specified, then this is a method call. We need to pull out - // the object in self and turn the member function alias real_fn - // into a delegate. This conversion is done with a dirty hack; see - // dg_convert.d. - static if (!is(C == void)) { - static assert (MIN_ARGS == MAX_ARGS, "Default arguments with member functions are not supported."); - // Didn't pass a "self" parameter! Ack! - if (self is null) { - PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a 'self' parameter."); - return null; - } - C instance = (cast(wrapped_class_object!(C)*)self).d_obj; - fn_to_dg!(fn_t) fn = dg_wrapper!(C, fn_t)(instance, &real_fn); - static if (is(ReturnType!(typeof(fn)) == void)) { - py_call(fn, args); - Py_INCREF(Py_None); - return Py_None; - } else { - return _py( py_call(fn, args) ); - } - // If C is not specified, then this is just a normal function call. - } else { - alias defaultsTupleT!(real_fn, MIN_ARGS, fn_t).type T; - T t; - defaultsTuple!(real_fn, MIN_ARGS, fn_t)(delegate void(T tu) { - foreach(i, arg; tu) { - t[i] = arg; - } - }); - return tuple_py_call(args, t); + // Didn't pass a "self" parameter! Ack! + if (self is null) { + PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a 'self' parameter."); + return null; + } + C instance = (cast(wrapped_class_object!(C)*)self).d_obj; + if (instance is null) { + PyErr_SetString(PyExc_ValueError, "Wrapped class instance is null!"); + return null; } + fn_to_dg!(fn_t) dg = dg_wrapper!(C, fn_t)(instance, &real_fn); + return pyApplyToDelegate(dg, args); }); } } diff --git a/infrastructure/pyd/make_object.d b/infrastructure/pyd/make_object.d index b4f4141..da95ef7 100644 --- a/infrastructure/pyd/make_object.d +++ b/infrastructure/pyd/make_object.d @@ -143,9 +143,11 @@ PyObject* _py(T) (T t) { return WrapPyObject_FromObject(t); } // If it's not a wrapped type, fall through to the exception. - // This just passes the argument right back through without changing - // its reference count. + // The function expects to be passed a borrowed reference and return an + // owned reference. Thus, if passed a PyObject*, this will increment the + // reference count. } else static if (is(T : PyObject*)) { + Py_INCREF(t); return t; } PyErr_SetString(PyExc_RuntimeError, "D conversion function _py failed with type " ~ typeid(T).toString()); @@ -177,8 +179,6 @@ PyObject* PyTuple_FromItems(T ...)(T t) { * * Calling this with a PydObject will return back a reference to the very same * PydObject. - * - * Calling this with a PyObject* will "steal" the reference. */ PydObject py(T) (T t) { static if(is(T : PydObject)) { diff --git a/infrastructure/pyd/op_wrap.d b/infrastructure/pyd/op_wrap.d index 9a54830..fbd8d99 100644 --- a/infrastructure/pyd/op_wrap.d +++ b/infrastructure/pyd/op_wrap.d @@ -146,8 +146,8 @@ template opfunc_binary_wrap(T, alias opfn) { template opfunc_unary_wrap(T, alias opfn) { extern(C) PyObject* func(PyObject* self) { - // func_wrap takes care of exception handling - return func_wrap!(opfn, 0, T).func(self, null); + // method_wrap takes care of exception handling + return method_wrap!(T, opfn, typeof(&opfn)).func(self, null); } } @@ -204,7 +204,7 @@ template opindex_mapping_pyfunc(T) { setWrongArgsError(args, ARGS, ARGS); return null; } - return func_wrap!(T.opIndex, ARGS, T).func(self, key); + return method_wrap!(T, T.opIndex, typeof(&T.opIndex)).func(self, key); } } } @@ -236,7 +236,7 @@ template opindexassign_mapping_pyfunc(T) { Py_INCREF(PyTuple_GetItem(key, i-1)); PyTuple_SetItem(temp, i, PyTuple_GetItem(key, i-1)); } - func_wrap!(T.opIndexAssign, ARGS, T).func(self, temp); + method_wrap!(T, T.opIndexAssign, typeof(&T.opIndexAssign)).func(self, temp); return 0; } } else {