[pygobject/gsoc2009: 14/160] Rewrite the invocation checks in C



commit 831739b5be1f86b8c41a649d060387522404534f
Author: Simon van der Linden <simon vanderlinden student uclouvain be>
Date:   Tue Jun 23 01:39:03 2009 +0200

    Rewrite the invocation checks in C
    
    Callable.type_check has been implemented in C in
    pyg_argument_from_pyobject_check, which is called by
    _wrap_g_function_info_invoke for each input argument.
    
    Voids are not checkable and booleans don't need checks because every
    Python object has a truth value.
    
    Most numeric types are first tested with Py(Int|Long|Float)_Check.
    
    Integers are also tested against the maximum and minimum value (for both
    Python integers and longs) to be sure they fit into the 8-bit, 16-bit,
    etc. argument. Sometimes, it is simpler since we know that Python
    integers are longs, or because the
    Py(Int|Long)_As(Long|UnsignedLong|...) functions will do the job for us
    and raise an error if it can't convert the value.
    
    Python float and doubles are converted to doubles and their value is
    checked as well.
    
    Strings are only tested with PyString_Check.
    
    All other type checks are mostly incomplete and need further work.

 girepository/bank-argument.c |  282 ++++++++++++++++++++++++++++++++++++++++++
 girepository/bank-info.c     |   71 +++++++++++
 girepository/bank.h          |    9 ++
 girepository/btypes.py       |   86 -------------
 tests/test_girepository.py   |   12 +-
 5 files changed, 368 insertions(+), 92 deletions(-)
---
diff --git a/girepository/bank-argument.c b/girepository/bank-argument.c
index 5700ae9..d58b5cc 100644
--- a/girepository/bank-argument.c
+++ b/girepository/bank-argument.c
@@ -21,6 +21,288 @@
 #include "bank.h"
 #include <pygobject.h>
 
+GQuark
+pyg_argument_from_pyobject_error_quark(void)
+{
+  return g_quark_from_static_string ("pyg-argument-from-pyobject-quark");
+}
+
+gboolean
+pyg_argument_from_pyobject_check(PyObject *object, GITypeInfo *type_info, GError **error)
+{
+    gboolean retval;
+    GITypeTag type_tag;
+    const gchar *py_type_name_expected;
+
+    type_tag = g_type_info_get_tag(type_info);
+
+    retval = TRUE;
+
+    switch(type_tag) {
+        case GI_TYPE_TAG_VOID:
+            /* No check possible. */
+            break;
+        case GI_TYPE_TAG_BOOLEAN:
+            /* No check; every Python object has a truth value. */
+            break;
+        case GI_TYPE_TAG_INT8:
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_INT32:
+        case GI_TYPE_TAG_INT:
+        {
+            long value;
+            gint value_max, value_min;
+
+            if (!(PyInt_Check(object) || PyLong_Check(object))) {
+                py_type_name_expected = "int or long";
+                goto check_error_type;
+            }
+
+            if (type_tag == GI_TYPE_TAG_INT8) {
+                value_min = -128;
+                value_max = 127;
+            } else if (type_tag == GI_TYPE_TAG_INT16) {
+                value_min = -32768;
+                value_max = 32767;
+            } else if (type_tag == GI_TYPE_TAG_INT32) {
+                value_min = -2147483648;
+                value_max = 2147483647;
+            } else if (type_tag == GI_TYPE_TAG_INT) {
+                value_min = G_MININT;
+                value_max = G_MAXINT;
+            } else {
+                g_assert_not_reached();
+                value_max = 0;
+                value_min = 0;
+            }
+
+            value = PyInt_AsLong(object);
+            if (PyErr_Occurred() || value < value_min || value > value_max) {
+                PyErr_Clear();
+                g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                        "Must range from %i to %i", value_min, value_max);
+                retval = FALSE;
+            }
+            break;
+        }
+        case GI_TYPE_TAG_UINT8:
+        case GI_TYPE_TAG_UINT16:
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_UINT:
+        {
+            guint value_max;
+
+            if (type_tag == GI_TYPE_TAG_UINT8) {
+                value_max = 255;
+            } else if (type_tag == GI_TYPE_TAG_UINT16) {
+                value_max = 65535;
+            } else if (type_tag == GI_TYPE_TAG_UINT32) {
+                value_max = 4294967295;
+            } else if (type_tag == GI_TYPE_TAG_UINT) {
+                value_max = G_MAXUINT;
+            } else {
+                g_assert_not_reached();
+                value_max = 0;
+            }
+
+            if (PyInt_Check(object)) {
+                long value = PyInt_AsLong(object);
+                if (PyErr_Occurred() || value < 0 || value > value_max) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from 0 to %u", value_max);
+                    retval = FALSE;
+                }
+            } else if (PyLong_Check(object)) {
+                unsigned long value = PyLong_AsUnsignedLong(object);
+                if (PyErr_Occurred() || value > value_max) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from 0 to %u", value_max);
+                    retval = FALSE;
+                }
+            } else {
+                py_type_name_expected = "int or long";
+                goto check_error_type;
+            }
+            break;
+        }
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_LONG:
+        case GI_TYPE_TAG_SSIZE:
+            if (PyLong_Check(object)) {
+                gint64 value_min, value_max;
+
+                PyErr_Clear();
+                if (type_tag == GI_TYPE_TAG_INT64) {
+                    (void) PyLong_AsLongLong(object);
+                    value_min = -9223372036854775808u;
+                    value_max = 9223372036854775807;
+                } else {
+                    (void) PyLong_AsLong(object);
+
+                    /* Could be different from above on a 64 bit arch. */
+                    value_min = G_MINLONG;
+                    value_max = G_MAXLONG;
+                }
+                if (PyErr_Occurred()) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, value_min, value_max);
+                    retval = FALSE;
+                }
+            } else if (!PyInt_Check(object)) {
+                /* Python Integer objects are implemented with longs, so no possible error if it is one. */
+                py_type_name_expected = "int or long";
+                goto check_error_type;
+            }
+            break;
+        case GI_TYPE_TAG_UINT64:
+        case GI_TYPE_TAG_ULONG:
+        case GI_TYPE_TAG_SIZE:
+        {
+            guint64 value_max;
+
+            if (type_tag == GI_TYPE_TAG_INT64) {
+                value_max = 18446744073709551615u;
+            } else {
+                /* Could be different from above on a 64 bit arch. */
+                value_max = G_MAXULONG;
+            }
+
+            if (PyLong_Check(object)) {
+                PyErr_Clear();
+                if (type_tag == GI_TYPE_TAG_UINT64) {
+                    (void) PyLong_AsUnsignedLongLong(object);
+                } else {
+                    (void) PyLong_AsUnsignedLong(object);
+                }
+                if (PyErr_Occurred()) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from 0 to %" G_GUINT64_FORMAT, value_max);
+                    retval = FALSE;
+                }
+            } else if (PyInt_Check(object)) {
+                long value;
+                value = PyInt_AsLong(object);
+                if (PyErr_Occurred() || value < 0) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from 0 to %" G_GUINT64_FORMAT, value_max);
+                    retval = FALSE;
+                }
+            } else {
+                py_type_name_expected = "int or long";
+                goto check_error_type;
+            }
+            break;
+        }
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+        {
+            gdouble value;
+
+            if (!PyFloat_Check(object)) {
+                py_type_name_expected = "float";
+                goto check_error_type;
+            }
+
+            value = PyFloat_AsDouble(object);
+            if (type_tag == GI_TYPE_TAG_FLOAT) {
+                if (PyErr_Occurred() || value < -G_MAXFLOAT || value > G_MAXFLOAT) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from %f to %f", -G_MAXFLOAT, G_MAXFLOAT);
+                    retval = FALSE;
+                }
+            } else if (type_tag == GI_TYPE_TAG_DOUBLE) {
+                if (PyErr_Occurred()) {
+                    PyErr_Clear();
+                    g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE,
+                            "Must range from %f to %f", -G_MAXDOUBLE, G_MAXDOUBLE);
+                    retval = FALSE;
+                }
+            }
+            break;
+        }
+        case GI_TYPE_TAG_UTF8:
+            if (!PyString_Check(object)) {
+                py_type_name_expected = "string";
+                goto check_error_type;
+            }
+            break;
+        case GI_TYPE_TAG_ARRAY:
+        {
+            gint size;
+
+            if (!PyTuple_Check(object)) {
+                py_type_name_expected = "tuple";
+                goto check_error_type;
+            }
+
+            size = g_type_info_get_array_fixed_size(type_info);
+            if (size != -1 && PyTuple_Size(object) != size) {
+                /* FIXME: Set another more appropriate error. */
+                g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE, "FIXME");
+                retval = FALSE;
+                break;
+            }
+
+            /* TODO: to complete */
+
+            break;
+        }
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *interface_info;
+            GIInfoType interface_info_type;
+
+            interface_info = g_type_info_get_interface(type_info);
+            interface_info_type = g_base_info_get_type(interface_info);
+
+            if (interface_info_type == GI_INFO_TYPE_ENUM) {
+                (void) PyInt_AsLong(object);
+                if (PyErr_Occurred()) {
+                    PyErr_Clear();
+                    py_type_name_expected = "int";
+                    goto check_error_type;
+                }
+                /* XXX: What if the value doesn't correspond to any enum field? */
+            } else {
+                /* TODO */
+            }
+
+            g_base_info_unref(interface_info);
+
+            break;
+        }
+        case GI_TYPE_TAG_TIME_T:
+        case GI_TYPE_TAG_GTYPE:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_GHASH:
+        case GI_TYPE_TAG_ERROR:
+            /* TODO */
+        default:
+            g_assert_not_reached();
+    }
+
+    g_assert(error == NULL || (retval == (*error == NULL)));
+    return retval;
+
+check_error_type:
+    {
+        PyObject *py_type;
+        py_type = PyObject_Type(object);
+        g_set_error(error, PyG_ARGUMENT_FROM_PYOBJECT_ERROR, PyG_ARGUMENT_FROM_PYOBJECT_ERROR_TYPE,
+                "Must be %s, not %s", py_type_name_expected, ((PyTypeObject *)py_type)->tp_name);
+        Py_XDECREF(py_type);
+        return FALSE;
+    }
+}
+
 GArgument
 pyg_argument_from_pyobject(PyObject *object, GITypeInfo *type_info)
 {
diff --git a/girepository/bank-info.c b/girepository/bank-info.c
index 50df71d..ca09f52 100644
--- a/girepository/bank-info.c
+++ b/girepository/bank-info.c
@@ -382,6 +382,77 @@ _wrap_g_function_info_invoke(PyGIBaseInfo *self, PyObject *args)
         g_base_info_unref((GIBaseInfo *)arg_info);
     }
 
+    {
+        Py_ssize_t py_args_pos;
+
+        /* Check the argument count. */
+        if (n_py_args != n_in_args + (is_constructor ? 1 : 0)) {
+            PyErr_Format(PyExc_TypeError, "%s.%s() takes exactly %i argument(s) (%zd given)",
+                    g_base_info_get_namespace((GIBaseInfo *)callable_info),
+                    g_base_info_get_name((GIBaseInfo *)callable_info),
+                    n_in_args + (is_constructor ? 1 : 0), n_py_args);
+            return NULL;
+        }
+
+        /* Check argument types. */
+        py_args_pos = (is_method || is_constructor) ? 1 : 0;
+        /* TODO: check the methods' first argument. */
+        for (i = 0; i < n_args; i++) {
+            GIArgInfo *arg_info;
+            GITypeInfo *type_info;
+            GIDirection direction;
+            PyObject *py_arg;
+            GError *error;
+
+            arg_info = g_callable_info_get_arg(callable_info, i);
+            direction = g_arg_info_get_direction(arg_info);
+            if (!(direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT)) {
+                g_base_info_unref((GIBaseInfo *)arg_info);
+                continue;
+            }
+
+            g_assert(py_args_pos < n_py_args);
+
+            py_arg = PyTuple_GetItem(args, py_args_pos);
+            g_assert(py_arg != NULL);
+
+            error = NULL;
+            type_info = g_arg_info_get_type(arg_info);
+            if (!pyg_argument_from_pyobject_check(py_arg, type_info, &error)) {
+                PyObject *py_error_type;
+                switch(error->code) {
+                    case PyG_ARGUMENT_FROM_PYOBJECT_ERROR_TYPE:
+                        py_error_type = PyExc_TypeError;
+                        break;
+                    case PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE:
+                        py_error_type = PyExc_ValueError;
+                        break;
+                    default:
+                        g_assert_not_reached();
+                        py_error_type = NULL;
+                }
+
+                PyErr_Format(py_error_type, "%s.%s() argument %zd: %s",
+                        g_base_info_get_namespace((GIBaseInfo *)self->info),
+                        g_base_info_get_name((GIBaseInfo *)self->info),
+                        py_args_pos, error->message);
+
+                g_clear_error(&error);
+            }
+
+            g_base_info_unref((GIBaseInfo *)type_info);
+            g_base_info_unref((GIBaseInfo *)arg_info);
+
+            if (PyErr_Occurred()) {
+                return NULL;
+            }
+
+            py_args_pos += 1;
+        }
+
+        g_assert(py_args_pos == n_py_args);
+    }
+
     /* Get the arguments. */
     in_args = g_newa(GArgument, n_in_args);
     out_args = g_newa(GArgument, n_out_args);
diff --git a/girepository/bank.h b/girepository/bank.h
index f5e3839..3f76b77 100644
--- a/girepository/bank.h
+++ b/girepository/bank.h
@@ -78,3 +78,12 @@ PyObject*  pyg_argument_to_pyobject(GArgument *arg,
 				    GITypeInfo *info);
 PyObject*  pyarray_to_pyobject(gpointer array, int length, GITypeInfo *info);
 
+#define PyG_ARGUMENT_FROM_PYOBJECT_ERROR pyg_argument_from_pyobject_error_quark()
+GQuark pyg_argument_from_pyobject_error_quark(void);
+
+typedef enum {
+    PyG_ARGUMENT_FROM_PYOBJECT_ERROR_TYPE,
+    PyG_ARGUMENT_FROM_PYOBJECT_ERROR_VALUE
+} PyGArgumentFromPyObjectError;
+
+gboolean pyg_argument_from_pyobject_check(PyObject *object, GITypeInfo *type_info, GError **error);
diff --git a/girepository/btypes.py b/girepository/btypes.py
index d626b7a..54b5268 100644
--- a/girepository/btypes.py
+++ b/girepository/btypes.py
@@ -37,96 +37,10 @@ class Callable(object):
         self.info = info
         self.call_type = None
 
-    def type_check(self, name, value, argType):
-        tag = argType.getTag()
-        if tag == repo.TYPE_TAG_UTF8:
-            if not isinstance(value, basestring) and value is not None:
-                raise TypeError("%s must be string, not %s" % (
-                        name, type(value).__name__))
-        elif tag in (repo.TYPE_TAG_INT,
-                     repo.TYPE_TAG_INT8,
-                     repo.TYPE_TAG_UINT,
-                     repo.TYPE_TAG_UINT8,
-                     repo.TYPE_TAG_INT16,
-                     repo.TYPE_TAG_UINT16,
-                     repo.TYPE_TAG_INT32):
-            try:
-                int(value)
-            except ValueError:
-                raise TypeError("%s must be int, not %s" % (name, type(value).__name__))
-            if tag in (repo.TYPE_TAG_UINT,
-                       repo.TYPE_TAG_UINT8,
-                       repo.TYPE_TAG_UINT16) and value < 0:
-                raise TypeError("%s must be an unsigned value, not %s", name, value)
-        elif tag in (repo.TYPE_TAG_UINT32,
-                     repo.TYPE_TAG_INT64,
-                     repo.TYPE_TAG_UINT64,
-                     repo.TYPE_TAG_LONG,
-                     repo.TYPE_TAG_ULONG,
-                     repo.TYPE_TAG_SIZE,
-                     repo.TYPE_TAG_SSIZE):
-            try:
-                long(value)
-            except ValueError:
-                raise TypeError("%s must be int or long, not %s" % (name, type(value).__name__))
-            if tag in (repo.TYPE_TAG_UINT32,
-                       repo.TYPE_TAG_UINT64,
-                       repo.TYPE_TAG_ULONG,
-                       repo.TYPE_TAG_SIZE) and value < 0:
-                raise TypeError("%s must be an unsigned value, not %s", name, value)
-        elif tag in (repo.TYPE_TAG_FLOAT,
-                     repo.TYPE_TAG_DOUBLE):
-            try:
-                float(value)
-            except ValueError:
-                raise TypeError("%s must be float, not %s" % (name, type(value).__name__))
-        elif tag == repo.TYPE_TAG_INTERFACE:
-            # TODO
-            pass
-        elif tag == repo.TYPE_TAG_BOOLEAN:
-            try:
-                bool(value)
-            except ValueError:
-                raise TypeError("%s must be bool, not %s" % (name, type(value).__name__))
-        elif tag == repo.TYPE_TAG_ARRAY:
-            if value is not None:
-                raise TypeError("Must pass None for arrays currently")
-        elif tag == repo.TYPE_TAG_ERROR:
-            # TODO
-            pass
-        elif tag == repo.TYPE_TAG_VOID:
-            # TODO
-            pass
-        else:
-            raise NotImplementedError('type checking for tag %d' % tag)
-
     def __call__(self, *args, **kwargs):
-        infoArgs = list(self.info.getArgs())
-        requiredArgs = 0
-        for arg in infoArgs:
-            direct = arg.getDirection()
-            if direct in [repo.DIRECTION_IN, repo.DIRECTION_INOUT]:
-                requiredArgs += 1
-
-        is_method = self.call_type in [self.INSTANCE_METHOD, self.CLASS_METHOD]
-        if is_method:
-            requiredArgs += 1
-
         # TODO: put the kwargs in their right positions
         totalInArgs = args + tuple(kwargs.values())
 
-        if len(totalInArgs) != requiredArgs:
-            raise TypeError('%r requires %d arguments, passed %d instead.' % (
-                self, requiredArgs, len(totalInArgs)))
-
-        for i, value in enumerate(totalInArgs):
-            if not is_method or i > 0:
-                off = is_method and 1 or 0
-                infoArg = infoArgs[i - off]
-                argType = infoArg.getType()
-                name = infoArg.getName()
-                self.type_check(name, value, argType)
-
         retval = self.info.invoke(*totalInArgs)
 
         if self.info.isConstructor():
diff --git a/tests/test_girepository.py b/tests/test_girepository.py
index 7197f0f..b1c4c29 100644
--- a/tests/test_girepository.py
+++ b/tests/test_girepository.py
@@ -89,27 +89,27 @@ class TestGIEverything(unittest.TestCase):
 
     def testUInt(self):
         self.assertEqual(3, Everything.test_uint(3))
-        self.assertRaises(TypeError, Everything.test_uint, -3)
+        self.assertRaises(ValueError, Everything.test_uint, -3)
 
     def testUInt8(self):
         self.assertEqual(3, Everything.test_uint8(3))
         self.assertEqual(UINT8_MAX, Everything.test_uint8(UINT8_MAX))
-        self.assertRaises(TypeError, Everything.test_uint8, -3)
+        self.assertRaises(ValueError, Everything.test_uint8, -3)
 
     def testUInt16(self):
         self.assertEqual(3, Everything.test_uint16(3))
         self.assertEqual(UINT16_MAX, Everything.test_uint16(UINT16_MAX))
-        self.assertRaises(TypeError, Everything.test_uint16, -3)
+        self.assertRaises(ValueError, Everything.test_uint16, -3)
 
     def testUInt32(self):
         self.assertEqual(3, Everything.test_uint32(3))
         self.assertEqual(UINT32_MAX, Everything.test_uint32(UINT32_MAX))
-        self.assertRaises(TypeError, Everything.test_uint32, -3)
+        self.assertRaises(ValueError, Everything.test_uint32, -3)
 
     def testUInt64(self):
         self.assertEqual(3, Everything.test_uint64(3))
         self.assertEqual(UINT64_MAX, Everything.test_uint64(UINT64_MAX))
-        self.assertRaises(TypeError, Everything.test_uint64, -3)
+        self.assertRaises(ValueError, Everything.test_uint64, -3)
 
     def testLong(self):
         self.assertEqual(3, Everything.test_long(3))
@@ -118,7 +118,7 @@ class TestGIEverything(unittest.TestCase):
 
     def testULong(self):
         self.assertEqual(3, Everything.test_ulong(3))
-        self.assertRaises(TypeError, Everything.test_ulong, -3)
+        self.assertRaises(ValueError, Everything.test_ulong, -3)
 
 	def testSSize(self):
 	    self.assertEqual(3, Everything.test_ssize(3))



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]