self.declare_pyfunction(env)
self.analyse_signature(env)
self.return_type = self.entry.signature.return_type()
+ env.use_utility_code(raise_argtuple_too_long_utility_code)
+ env.use_utility_code(keyword_check_utility_code)
+ env.use_utility_code(check_double_keywords_utility_code)
if self.signature_has_generic_args():
if self.star_arg:
env.use_utility_code(get_stararg_utility_code)
- elif self.signature_has_generic_args():
- env.use_utility_code(raise_argtuple_too_long_utility_code)
if not self.signature_has_nongeneric_args():
- env.use_utility_code(get_keyword_string_check_utility_code)
+ env.use_utility_code(keyword_string_check_utility_code)
elif self.starstar_arg:
- env.use_utility_code(get_splitkeywords_utility_code)
+ env.use_utility_code(split_keywords_utility_code)
if self.num_required_kw_args:
- env.use_utility_code(get_checkkeywords_utility_code)
+ env.use_utility_code(check_keywords_utility_code)
def analyse_signature(self, env):
any_type_tests_needed = 0
if has_star_or_kw_args:
self.generate_stararg_getting_code(code)
- self.generate_argument_tuple_parsing_code(
+ self.generate_tuple_and_keyword_parsing_code(
positional_args, arg_formats, arg_addrs, code)
code.error_label = old_error_label
code.putln("return %s;" % self.error_value())
code.put_label(end_label)
- def generate_argument_tuple_parsing_code(self, positional_args,
+ def _generate_argument_tuple_parsing_code(self, positional_args,
arg_formats, arg_addrs, code):
# Unpack inplace if it's simple
if not self.num_required_kw_args:
code.putln('if (PyTuple_GET_SIZE(%s) > %s) {' % (Naming.args_cname, i))
closing += 1
item = "PyTuple_GET_ITEM(%s, %s)" % (Naming.args_cname, i)
- if arg.type.is_pyobject:
- if arg.is_generic:
- item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item)
- code.putln("%s = %s;" % (arg.entry.cname, item))
- else:
- func = arg.type.from_py_function
- if func:
- code.putln("%s = %s(%s); %s" % (
- arg.entry.cname,
- func,
- item,
- code.error_goto_if(arg.type.error_condition(arg.entry.cname), arg.pos)))
- else:
- error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type)
+ self.generate_arg_assignment(arg, item, code)
i += 1
for _ in range(closing):
code.putln('}')
if not self.num_required_kw_args:
code.putln('}')
+ def generate_arg_assignment(self, arg, item, code):
+ if arg.type.is_pyobject:
+ if arg.is_generic:
+ item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item)
+ code.putln("%s = %s;" % (arg.entry.cname, item))
+ else:
+ func = arg.type.from_py_function
+ if func:
+ code.putln("%s = %s(%s); %s" % (
+ arg.entry.cname,
+ func,
+ item,
+ code.error_goto_if(arg.type.error_condition(arg.entry.cname), arg.pos)))
+ else:
+ error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type)
+
def put_stararg_decrefs(self, code):
if self.star_arg:
code.put_decref(Naming.args_cname, py_object_type)
code.put_incref(Naming.args_cname, py_object_type)
code.put("%s = %s; " % (star_arg_cname, Naming.empty_tuple))
code.put_incref(Naming.empty_tuple, py_object_type)
- code.putln("}")
- code.putln("else {")
+ code.putln("} else {")
code.putln(
"if (unlikely(__Pyx_SplitStarArg(&%s, %d, &%s) < 0)) return %s;" % (
Naming.args_cname,
self.error_value()))
code.putln("}")
self.star_arg.entry.xdecref_cleanup = 0
- elif self.signature_has_generic_args():
+ else:
# make sure supernumerous positional arguments do not run
# into keyword-only arguments and provide a more helpful
# message than PyArg_ParseTupelAndKeywords()
if self.starstar_arg:
handle_error = 1
code.put(
- "if (unlikely(__Pyx_SplitKeywords(&%s, %s, &%s, %s) < 0)) " % (
+ 'if (unlikely(__Pyx_SplitKeywords(&%s, %s, &%s, %s, PyTuple_GET_SIZE(%s), "%s") < 0)) ' % (
Naming.kwds_cname,
Naming.kwdlist_cname,
self.starstar_arg.entry.cname,
- self.reqd_kw_flags_cname))
+ self.reqd_kw_flags_cname,
+ Naming.args_cname,
+ self.name.utf8encode()))
self.starstar_arg.entry.xdecref_cleanup = 0
elif self.num_required_kw_args:
handle_error = 1
- code.put("if (unlikely(__Pyx_CheckRequiredKeywords(%s, %s, %s) < 0)) " % (
+ code.put('if (unlikely(__Pyx_CheckRequiredKeywords(%s, %s, %s, PyTuple_GET_SIZE(%s), "%s") < 0)) ' % (
+ Naming.kwds_cname,
+ Naming.kwdlist_cname,
+ self.reqd_kw_flags_cname,
+ Naming.args_cname,
+ self.name.utf8encode()))
+ else:
+ # check that positional arguments are not passed as keywords also
+ handle_error = 1
+ code.put('if (unlikely(%s) && unlikely(__Pyx_CheckDoubleKeywords(%s, %s, PyTuple_GET_SIZE(%s), "%s") < 0)) ' % (
+ Naming.kwds_cname,
Naming.kwds_cname,
Naming.kwdlist_cname,
- self.reqd_kw_flags_cname))
+ Naming.args_cname,
+ self.name.utf8encode()))
if handle_error:
if self.star_arg:
else:
code.putln(error_return)
+ def generate_tuple_and_keyword_parsing_code(self, positional_args,
+ arg_formats, arg_addrs, code):
+ min_positional_args = self.num_required_args - self.num_required_kw_args
+ if len(self.args) > 0 and self.args[0].is_self_arg:
+ min_positional_args -= 1
+ max_positional_args = len(positional_args)
+
+ # just a quick check to start with
+ if not self.star_arg:
+ code.putln("if (unlikely(PyTuple_GET_SIZE(%s) > %d)) {" % (
+ Naming.args_cname, max_positional_args))
+ code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, PyTuple_GET_SIZE(%s)); ' % (
+ self.name.utf8encode(), min_positional_args,
+ max_positional_args, Naming.args_cname))
+ code.putln(code.error_goto(self.pos))
+ code.putln("}")
+
+ # --- optimised code when we receive keyword arguments
+ code.putln("if (unlikely(%s) && (PyDict_Size(%s) > 0)) {" % (
+ Naming.kwds_cname, Naming.kwds_cname))
+ code.putln("PyObject* values[%d];" % max_positional_args)
+ code.putln("Py_ssize_t kw_args = PyDict_Size(%s), arg;" %
+ Naming.kwds_cname)
+
+ code.putln("for (arg=0; arg < PyTuple_GET_SIZE(%s); arg++) {" %
+ Naming.args_cname)
+ code.putln("values[arg] = PyTuple_GET_ITEM(%s, arg);" %
+ Naming.args_cname)
+ code.putln("if (unlikely(PyDict_GetItemString(%s, %s[arg]))) {" % (
+ Naming.kwds_cname, Naming.kwdlist_cname))
+ code.put( '__Pyx_RaiseDoubleKeywordsError("%s", %s[arg]); ' % (
+ self.name.utf8encode(), Naming.kwdlist_cname))
+ code.putln(code.error_goto(self.pos))
+ code.putln('}')
+ code.putln('}')
+
+ code.putln("for (arg=PyTuple_GET_SIZE(%s); arg < %d; arg++) {" % (
+ Naming.args_cname, max_positional_args))
+ code.putln('values[arg] = PyDict_GetItemString(%s, %s[arg]);' % (
+ Naming.kwds_cname, Naming.kwdlist_cname))
+ code.putln('if (values[arg]) kw_args--;');
+ code.putln('}')
+
+ code.putln('if (unlikely(kw_args > 0)) {')
+ code.put('if (!__Pyx_CheckKeywords(%s, "%s", %s)) ' % (
+ Naming.kwds_cname, self.name.utf8encode(), Naming.kwdlist_cname))
+ code.putln(code.error_goto(self.pos))
+ code.putln('}')
+
+ default_seen = False
+ for i, arg in enumerate(positional_args):
+ if arg.default:
+ default_seen = True
+ if default_seen:
+ code.putln("if (values[%d]) {" % i)
+ self.generate_arg_assignment(arg, "values[%d]" % i, code)
+ if default_seen:
+ if not arg.default:
+ code.putln('} else {')
+ code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, %d); ' % (
+ self.name.utf8encode(), min_positional_args,
+ max_positional_args, i))
+ code.putln(code.error_goto(self.pos))
+ code.putln('}')
+
+ # --- optimised code when we did not receive any keyword arguments
+ code.putln('} else if (unlikely(PyTuple_GET_SIZE(%s) < %d)) {' % (
+ Naming.args_cname, min_positional_args))
+ code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, PyTuple_GET_SIZE(%s)); ' % (
+ self.name.utf8encode(), min_positional_args,
+ max_positional_args, Naming.args_cname))
+ code.putln(code.error_goto(self.pos))
+
+ code.putln('} else {')
+ closing = 0
+ for i, arg in enumerate(positional_args):
+ if arg.default:
+ code.putln('if (PyTuple_GET_SIZE(%s) > %s) {' % (Naming.args_cname, i))
+ closing += 1
+ item = "PyTuple_GET_ITEM(%s, %s)" % (Naming.args_cname, i)
+ self.generate_arg_assignment(arg, item, code)
+ for _ in range(closing):
+ code.putln('}')
+
+ code.putln('}')
+ return
+
+ argformat = '"%s"' % string.join(arg_formats, "")
+ pt_arglist = [Naming.args_cname, Naming.kwds_cname, argformat, Naming.kwdlist_cname] + arg_addrs
+ pt_argstring = string.join(pt_arglist, ", ")
+ code.putln(
+ 'if (unlikely(!PyArg_ParseTupleAndKeywords(%s))) %s' % (
+ pt_argstring,
+ code.error_goto(self.pos)))
+ self.generate_argument_conversion_code(code)
+
+ code.putln('}')
+
def generate_positional_args_check(self, code, nargs):
code.putln("if (unlikely(PyTuple_GET_SIZE(%s) > %d)) {" % (
Naming.args_cname, nargs))
- code.putln("__Pyx_RaiseArgtupleTooLong(%d, PyTuple_GET_SIZE(%s));" % (
- nargs, Naming.args_cname))
+ code.putln('__Pyx_RaiseArgtupleInvalid("%s", 0, %d, PyTuple_GET_SIZE(%s));' % (
+ self.name.utf8encode(), nargs, Naming.args_cname))
code.putln("return %s;" % self.error_value())
code.putln("}")
#------------------------------------------------------------------------------------
#
-# __Pyx_RaiseArgtupleTooLong raises the correct exception when too
-# many positional arguments were found. This handles Py_ssize_t
-# formatting correctly.
+# __Pyx_RaiseArgtupleInvalid raises the correct exception when too
+# many or too few positional arguments were found. This handles
+# Py_ssize_t formatting correctly.
raise_argtuple_too_long_utility_code = [
"""
-static INLINE void __Pyx_RaiseArgtupleTooLong(Py_ssize_t num_expected, Py_ssize_t num_found); /*proto*/
+static INLINE void __Pyx_RaiseArgtupleInvalid(char* func_name,
+ Py_ssize_t num_min, Py_ssize_t num_max, Py_ssize_t num_found); /*proto*/
""","""
-static INLINE void __Pyx_RaiseArgtupleTooLong(
- Py_ssize_t num_expected,
+static INLINE void __Pyx_RaiseArgtupleInvalid(
+ char* func_name,
+ Py_ssize_t num_min,
+ Py_ssize_t num_max,
Py_ssize_t num_found)
{
- const char* error_message =
- #if PY_VERSION_HEX < 0x02050000
- "function takes at most %d positional arguments (%d given)";
- #else
- "function takes at most %zd positional arguments (%zd given)";
- #endif
- PyErr_Format(PyExc_TypeError, error_message, num_expected, num_found);
+ if (num_found < num_min) {
+ PyErr_Format(PyExc_TypeError,
+ #if PY_VERSION_HEX < 0x02050000
+ "%s() takes at least %d positional arguments (%d given)",
+ #elif PY_MAJOR_VERSION >= 3
+ "%U() takes at most %zd positional arguments (%zd given)",
+ #else
+ "%s() takes at least %zd positional arguments (%zd given)",
+ #endif
+ func_name, num_min, num_found);
+ } else {
+ PyErr_Format(PyExc_TypeError,
+ #if PY_VERSION_HEX < 0x02050000
+ "%s() takes at most %d positional arguments (%d given)",
+ #elif PY_MAJOR_VERSION >= 3
+ "%U() takes at most %zd positional arguments (%zd given)",
+ #else
+ "%s() takes at most %zd positional arguments (%zd given)",
+ #endif
+ func_name, num_max, num_found);
+ }
}
"""]
# were passed to a function, or if any keywords were passed to a
# function that does not accept them.
-get_keyword_string_check_utility_code = [
+keyword_string_check_utility_code = [
"""
static int __Pyx_CheckKeywordStrings(PyObject *kwdict, const char* function_name, int kw_allowed); /*proto*/
""","""
}
"""]
+#------------------------------------------------------------------------------------
+#
+# __Pyx_CheckKeywords raises an error if non-string keywords were
+# passed to a function, or if any unsupported keywords were passed to
+# a function.
+
+keyword_check_utility_code = [
+"""
+static int __Pyx_CheckKeywords(PyObject *kwdict, const char* function_name,
+ char** argnames); /*proto*/
+""","""
+static int __Pyx_CheckKeywords(
+ PyObject *kwdict,
+ const char* function_name,
+ char** argnames)
+{
+ PyObject* key = 0;
+ Py_ssize_t pos = 0;
+ char** name;
+ while (PyDict_Next(kwdict, &pos, &key, 0)) {
+ #if PY_MAJOR_VERSION < 3
+ if (unlikely(!PyString_Check(key))) {
+ #else
+ if (unlikely(!PyUnicode_Check(key))) {
+ #endif
+ PyErr_Format(PyExc_TypeError,
+ "%s() keywords must be strings", function_name);
+ return 0;
+ } else if (argnames) {
+ name = argnames;
+ while (*name) {
+ #if PY_MAJOR_VERSION >= 3
+ PyObject* utf8_key = PyUnicode_AsUTF8String(key);
+ if (!utf8_key) return -1;
+ if (strcmp(*name, PyString_AS_STRING(utf8_key)) == 0) {
+ Py_DECREF(utf8_key);
+ break;
+ }
+ Py_DECREF(utf8_key);
+ #else
+ if (strcmp(*name, PyString_AS_STRING(key)) == 0)
+ break;
+ #endif
+ name++;
+ }
+ if (!*name) {
+ PyErr_Format(PyExc_TypeError,
+ #if PY_MAJOR_VERSION < 3
+ "'%s' is an invalid keyword argument for this function",
+ PyString_AS_STRING(key));
+ #else
+ "'%U' is an invalid keyword argument for this function",
+ key);
+ #endif
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+"""]
+
#------------------------------------------------------------------------------------
#
# __Pyx_SplitKeywords splits the kwds dict into two parts one part
# the names in kwd_list, indicating required keyword arguments. If
# any of these are not present in kwds, an exception is raised.
#
+# If num_posargs is greater 0, it denotes the number of positional
+# arguments that were passed and that must therefore not get passed
+# as keyword arguments as well.
+#
-get_splitkeywords_utility_code = [
+split_keywords_utility_code = [
"""
static int __Pyx_SplitKeywords(PyObject **kwds, char *kwd_list[], \
- PyObject **kwds2, char rqd_kwds[]); /*proto*/
+ PyObject **kwds2, char rqd_kwds[],
+ Py_ssize_t num_posargs, char* func_name); /*proto*/
""","""
static int __Pyx_SplitKeywords(
PyObject **kwds,
char *kwd_list[],
PyObject **kwds2,
- char rqd_kwds[])
+ char rqd_kwds[],
+ Py_ssize_t num_posargs,
+ char* func_name)
{
PyObject *s = 0, *x = 0, *kwds1 = 0;
int i;
#endif
x = PyDict_GetItem(*kwds, s);
if (x) {
+ if (i < num_posargs)
+ goto arg_passed_twice;
if (PyDict_SetItem(kwds1, s, x) < 0)
goto bad;
if (PyDict_DelItem(*kwds2, s) < 0)
*kwds = kwds1;
return 0;
+arg_passed_twice:
+ PyErr_Format(PyExc_TypeError,
+ "%s() got multiple values for keyword argument '%s'", func_name, *p);
+ goto bad;
missing_kwarg:
PyErr_Format(PyExc_TypeError,
"required keyword argument '%s' is missing", *p);
}
"""]
-get_checkkeywords_utility_code = [
+check_keywords_utility_code = [
"""
static INLINE int __Pyx_CheckRequiredKeywords(PyObject *kwds, char *kwd_list[],
- char rqd_kwds[]); /*proto*/
+ char rqd_kwds[], Py_ssize_t num_posargs, char* func_name); /*proto*/
""","""
static INLINE int __Pyx_CheckRequiredKeywords(
PyObject *kwds,
char *kwd_list[],
- char rqd_kwds[])
+ char rqd_kwds[],
+ Py_ssize_t num_posargs,
+ char* func_name)
{
int i;
char **p;
if (kwds) {
- for (i = 0, p = kwd_list; *p; i++, p++)
+ p = kwd_list;
+ for (i=0; i < num_posargs && *p; i++, p++) {
+ if (PyDict_GetItemString(kwds, *p))
+ goto arg_passed_twice;
+ }
+ while (*p) {
if (rqd_kwds[i] && !PyDict_GetItemString(kwds, *p))
goto missing_kwarg;
+ i++; p++;
+ }
}
else {
for (i = 0, p = kwd_list; *p; i++, p++)
}
return 0;
+arg_passed_twice:
+ PyErr_Format(PyExc_TypeError,
+ "%s() got multiple values for keyword argument '%s'", func_name, *p);
+ return -1;
missing_kwarg:
PyErr_Format(PyExc_TypeError,
"required keyword argument '%s' is missing", *p);
}
"""]
+check_double_keywords_utility_code = [
+"""
+static INLINE int __Pyx_CheckDoubleKeywords(PyObject *kwds, char *kwd_list[],
+ Py_ssize_t num_posargs, const char* func_name); /*proto*/
+static int __Pyx_RaiseDoubleKeywordsError(const char* func_name,
+ const char* kw_name); /*proto*/
+""","""
+static INLINE int __Pyx_CheckDoubleKeywords(
+ PyObject *kwds,
+ char *kwd_list[],
+ Py_ssize_t num_posargs,
+ const char* func_name)
+{
+ int i;
+ char** p = kwd_list;
+ for (i=0; i < num_posargs && *p; i++, p++) {
+ if (PyDict_GetItemString(kwds, *p))
+ return __Pyx_RaiseDoubleKeywordsError(func_name, *p);
+ }
+
+ return 0;
+}
+
+static int __Pyx_RaiseDoubleKeywordsError(const char* func_name, const char* kw_name) {
+ PyErr_Format(PyExc_TypeError,
+ "%s() got multiple values for keyword argument '%s'", func_name, kw_name);
+ return -1;
+}
+"""]
+
#------------------------------------------------------------------------------------
unraisable_exception_utility_code = [