Index: Python/bltinmodule.c =================================================================== --- Python/bltinmodule.c (revision 80842) +++ Python/bltinmodule.c (working copy) @@ -1745,15 +1745,54 @@ return -1; } +/* Helper function for handle_range_longs. If arg is int or long + object, returns it with incremented reference count. If arg is + float, raises type error. As a last resort, creates a new int by + calling arg type's nb_int method if it is defined. Returns NULL + and sets exception on error. + + Returns a new reference to an int object. */ +static PyObject * +get_range_long_argument(PyObject *arg, const char *name) +{ + PyObject *v; + PyNumberMethods *nb; + if (PyInt_Check(arg) || PyLong_Check(arg)) { + Py_INCREF(arg); + return arg; + } + if (PyFloat_Check(arg) || + (nb = Py_TYPE(arg)->tp_as_number) == NULL || + nb->nb_int == NULL) { + PyErr_Format(PyExc_TypeError, + "range() integer %s argument expected, got %s.", + name, arg->ob_type->tp_name); + return NULL; + } + v = nb->nb_int(arg); + if (v == NULL) + return NULL; + if (PyInt_Check(v) || PyLong_Check(v)) + return v; + Py_DECREF(v); + PyErr_SetString(PyExc_TypeError, + "__int__ should return int object"); + return NULL; +} + /* An extension of builtin_range() that handles the case when PyLong * arguments are given. */ static PyObject * -handle_range_longs(PyObject *self, PyObject *args) +handle_range_longs(PyObject *self, PyObject *args) { - PyObject *ilow; + PyObject *ilow = NULL; PyObject *ihigh = NULL; PyObject *istep = NULL; + PyObject *low = NULL; + PyObject *high = NULL; + PyObject *step = NULL; + PyObject *curnum = NULL; PyObject *v = NULL; long bign; @@ -1772,7 +1811,7 @@ /* Figure out which way we were called, supply defaults, and be * sure to incref everything so that the decrefs at the end - * are correct. + * are correct. NB: ilow, ihigh and istep are borrowed references. */ assert(ilow != NULL); if (ihigh == NULL) { @@ -1780,47 +1819,35 @@ ihigh = ilow; ilow = NULL; } + + /* convert ihigh if necessary */ assert(ihigh != NULL); - Py_INCREF(ihigh); + high = get_range_long_argument(ihigh, "end"); + if (high == NULL) + goto Fail; /* ihigh correct now; do ilow */ - if (ilow == NULL) - ilow = zero; - Py_INCREF(ilow); - - /* ilow and ihigh correct now; do istep */ - if (istep == NULL) { - istep = PyLong_FromLong(1L); - if (istep == NULL) - goto Fail; + if (ilow == NULL) { + Py_INCREF(zero); + low = zero; } else { - Py_INCREF(istep); + low = get_range_long_argument(ilow, "start"); + if (low == NULL) + goto Fail; } - if (!PyInt_Check(ilow) && !PyLong_Check(ilow)) { - PyErr_Format(PyExc_TypeError, - "range() integer start argument expected, got %s.", - ilow->ob_type->tp_name); + /* ilow and ihigh correct now; do istep */ + if (istep == NULL) + step = PyLong_FromLong(1); + else + step = get_range_long_argument(istep, "step"); + if (step == NULL) goto Fail; - } - if (!PyInt_Check(ihigh) && !PyLong_Check(ihigh)) { - PyErr_Format(PyExc_TypeError, - "range() integer end argument expected, got %s.", - ihigh->ob_type->tp_name); + if (PyObject_Cmp(step, zero, &cmp_result) == -1) goto Fail; - } - if (!PyInt_Check(istep) && !PyLong_Check(istep)) { - PyErr_Format(PyExc_TypeError, - "range() integer step argument expected, got %s.", - istep->ob_type->tp_name); - goto Fail; - } - - if (PyObject_Cmp(istep, zero, &cmp_result) == -1) - goto Fail; if (cmp_result == 0) { PyErr_SetString(PyExc_ValueError, "range() step argument must not be zero"); @@ -1828,13 +1855,13 @@ } if (cmp_result > 0) - bign = get_len_of_range_longs(ilow, ihigh, istep); + bign = get_len_of_range_longs(low, high, step); else { - PyObject *neg_istep = PyNumber_Negative(istep); - if (neg_istep == NULL) + PyObject *neg_step = PyNumber_Negative(step); + if (neg_step == NULL) goto Fail; - bign = get_len_of_range_longs(ihigh, ilow, neg_istep); - Py_DECREF(neg_istep); + bign = get_len_of_range_longs(high, low, neg_step); + Py_DECREF(neg_step); } n = (int)bign; @@ -1848,7 +1875,7 @@ if (v == NULL) goto Fail; - curnum = ilow; + curnum = low; Py_INCREF(curnum); for (i = 0; i < n; i++) { @@ -1859,24 +1886,24 @@ PyList_SET_ITEM(v, i, w); - tmp_num = PyNumber_Add(curnum, istep); + tmp_num = PyNumber_Add(curnum, step); if (tmp_num == NULL) goto Fail; Py_DECREF(curnum); curnum = tmp_num; } - Py_DECREF(ilow); - Py_DECREF(ihigh); - Py_DECREF(istep); + Py_DECREF(low); + Py_DECREF(high); + Py_DECREF(step); Py_DECREF(zero); Py_DECREF(curnum); return v; Fail: - Py_DECREF(ilow); - Py_DECREF(ihigh); - Py_XDECREF(istep); + Py_XDECREF(low); + Py_XDECREF(high); + Py_XDECREF(step); Py_DECREF(zero); Py_XDECREF(curnum); Py_XDECREF(v); Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 80842) +++ Misc/NEWS (working copy) @@ -12,6 +12,12 @@ Core and Builtins ----------------- +- Issue #1533: fix inconsistency in range function argument + processing: any non-float non-integer argument is now converted to + an integer (if possible) using its __int__ method. Previously, only + small arguments were treated this way; larger arguments (those whose + __int__ was outside the range of a C long) would produce a TypeError. + - Issue #8417: Raise an OverflowError when an integer larger than sys.maxsize is passed to bytearray. Index: Lib/test/test_builtin.py =================================================================== --- Lib/test/test_builtin.py (revision 80842) +++ Lib/test/test_builtin.py (working copy) @@ -3,7 +3,7 @@ import platform import test.test_support, unittest from test.test_support import fcmp, have_unicode, TESTFN, unlink, \ - run_unittest, run_with_locale + run_unittest, run_with_locale, check_warnings from operator import neg import sys, warnings, cStringIO, random, fractions, UserDict @@ -1074,6 +1074,10 @@ # Reject floats when it would require PyLongs to represent. # (smaller floats still accepted, but deprecated) self.assertRaises(TypeError, range, 1e100, 1e101, 1e101) + with check_warnings() as w: + warnings.simplefilter("always") + self.assertEqual(range(1.0), [0]) + self.assertEqual(w.category, DeprecationWarning) self.assertRaises(TypeError, range, 0, "spam") self.assertRaises(TypeError, range, 0, 42, "spam") @@ -1081,6 +1085,54 @@ self.assertRaises(OverflowError, range, -sys.maxint, sys.maxint) self.assertRaises(OverflowError, range, 0, 2*sys.maxint) + bignum = 2*sys.maxint + smallnum = 42 + # Old-style user-defined class with __int__ method + class I0: + def __init__(self, n): + self.n = int(n) + def __int__(self): + return self.n + self.assertEqual(range(I0(bignum), I0(bignum + 1)), [bignum]) + self.assertEqual(range(I0(smallnum), I0(smallnum + 1)), [smallnum]) + + # New-style user-defined class with __int__ method + class I1(object): + def __init__(self, n): + self.n = int(n) + def __int__(self): + return self.n + self.assertEqual(range(I1(bignum), I1(bignum + 1)), [bignum]) + self.assertEqual(range(I1(smallnum), I1(smallnum + 1)), [smallnum]) + + # New-style user-defined class with failing __int__ method + class IX(object): + def __int__(self): + raise RuntimeError + self.assertRaises(RuntimeError, range, IX()) + + # New-style user-defined class with invalid __int__ method + class IN(object): + def __int__(self): + return "not a number" + self.assertRaises(TypeError, range, IN()) + + # Exercise various combinations of bad arguments, to check + # refcounting logic + self.assertRaises(TypeError, range, 1e100) + + self.assertRaises(TypeError, range, 0, 1e100) + self.assertRaises(TypeError, range, 1e100, 0) + self.assertRaises(TypeError, range, 1e100, 1e100) + + self.assertRaises(TypeError, range, 0, 0, 1e100) + self.assertRaises(TypeError, range, 0, 1e100, 1) + self.assertRaises(TypeError, range, 0, 1e100, 1e100) + self.assertRaises(TypeError, range, 1e100, 0, 1) + self.assertRaises(TypeError, range, 1e100, 0, 1e100) + self.assertRaises(TypeError, range, 1e100, 1e100, 1) + self.assertRaises(TypeError, range, 1e100, 1e100, 1e100) + def test_input_and_raw_input(self): self.write_testfile() fp = open(TESTFN, 'r')