diff -r af9f3d737d4a Include/longobject.h --- a/Include/longobject.h Tue Dec 10 10:21:51 2013 +0200 +++ b/Include/longobject.h Tue Dec 10 13:46:57 2013 +0200 @@ -165,6 +165,12 @@ unsigned char* bytes, size_t n, int little_endian, int is_signed); +/* _PyLong_FromNbInt: Convert the given object to a PyLongObject + using the nb_int slot, if available. Raise TypeError if either the + nb_int slot is not available or the result of the call to nb_int + returns something not of type int. +*/ +PyAPI_FUNC(PyLongObject *)_PyLong_FromNbInt(PyObject *); /* _PyLong_Format: Convert the long to a string object with given base, appending a base prefix of 0[box] if base is 2, 8 or 16. */ diff -r af9f3d737d4a Lib/test/test_getargs2.py --- a/Lib/test/test_getargs2.py Tue Dec 10 10:21:51 2013 +0200 +++ b/Lib/test/test_getargs2.py Tue Dec 10 13:46:57 2013 +0200 @@ -52,12 +52,33 @@ def __int__(self): return 99 +class IntSubclass(int): + def __int__(self): + return 99 + +class BadInt: + def __int__(self): + return 1.0 + +class BadInt2: + def __int__(self): + return True + +class BadInt3(int): + def __int__(self): + return self + + class Unsigned_TestCase(unittest.TestCase): def test_b(self): from _testcapi import getargs_b # b returns 'unsigned char', and does range checking (0 ... UCHAR_MAX) self.assertRaises(TypeError, getargs_b, 3.14) self.assertEqual(99, getargs_b(Int())) + self.assertEqual(99, getargs_b(IntSubclass())) + self.assertRaises(TypeError, getargs_b, BadInt()) + self.assertWarns(DeprecationWarning, getargs_b, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_b, BadInt3()) self.assertRaises(OverflowError, getargs_b, -1) self.assertEqual(0, getargs_b(0)) @@ -72,6 +93,10 @@ # B returns 'unsigned char', no range checking self.assertRaises(TypeError, getargs_B, 3.14) self.assertEqual(99, getargs_B(Int())) + self.assertEqual(99, getargs_B(IntSubclass())) + self.assertRaises(TypeError, getargs_B, BadInt()) + self.assertWarns(DeprecationWarning, getargs_B, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_B, BadInt3()) self.assertEqual(UCHAR_MAX, getargs_B(-1)) self.assertEqual(0, getargs_B(0)) @@ -86,6 +111,10 @@ # H returns 'unsigned short', no range checking self.assertRaises(TypeError, getargs_H, 3.14) self.assertEqual(99, getargs_H(Int())) + self.assertEqual(99, getargs_H(IntSubclass())) + self.assertRaises(TypeError, getargs_H, BadInt()) + self.assertWarns(DeprecationWarning, getargs_H, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_H, BadInt3()) self.assertEqual(USHRT_MAX, getargs_H(-1)) self.assertEqual(0, getargs_H(0)) @@ -101,6 +130,10 @@ # I returns 'unsigned int', no range checking self.assertRaises(TypeError, getargs_I, 3.14) self.assertEqual(99, getargs_I(Int())) + self.assertEqual(99, getargs_I(IntSubclass())) + self.assertRaises(TypeError, getargs_I, BadInt()) + self.assertWarns(DeprecationWarning, getargs_I, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_I, BadInt3()) self.assertEqual(UINT_MAX, getargs_I(-1)) self.assertEqual(0, getargs_I(0)) @@ -117,6 +150,10 @@ # it does not accept float, or instances with __int__ self.assertRaises(TypeError, getargs_k, 3.14) self.assertRaises(TypeError, getargs_k, Int()) + self.assertEqual(99, getargs_k(IntSubclass())) + self.assertRaises(TypeError, getargs_k, BadInt()) + self.assertRaises(TypeError, getargs_k, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_k, BadInt3()) self.assertEqual(ULONG_MAX, getargs_k(-1)) self.assertEqual(0, getargs_k(0)) @@ -133,6 +170,10 @@ # h returns 'short', and does range checking (SHRT_MIN ... SHRT_MAX) self.assertRaises(TypeError, getargs_h, 3.14) self.assertEqual(99, getargs_h(Int())) + self.assertEqual(99, getargs_h(IntSubclass())) + self.assertRaises(TypeError, getargs_h, BadInt()) + self.assertWarns(DeprecationWarning, getargs_h, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_h, BadInt3()) self.assertRaises(OverflowError, getargs_h, SHRT_MIN-1) self.assertEqual(SHRT_MIN, getargs_h(SHRT_MIN)) @@ -147,6 +188,10 @@ # i returns 'int', and does range checking (INT_MIN ... INT_MAX) self.assertRaises(TypeError, getargs_i, 3.14) self.assertEqual(99, getargs_i(Int())) + self.assertEqual(99, getargs_i(IntSubclass())) + self.assertRaises(TypeError, getargs_i, BadInt()) + self.assertWarns(DeprecationWarning, getargs_i, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_i, BadInt3()) self.assertRaises(OverflowError, getargs_i, INT_MIN-1) self.assertEqual(INT_MIN, getargs_i(INT_MIN)) @@ -161,6 +206,10 @@ # l returns 'long', and does range checking (LONG_MIN ... LONG_MAX) self.assertRaises(TypeError, getargs_l, 3.14) self.assertEqual(99, getargs_l(Int())) + self.assertEqual(99, getargs_l(IntSubclass())) + self.assertRaises(TypeError, getargs_l, BadInt()) + self.assertWarns(DeprecationWarning, getargs_l, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_l, BadInt3()) self.assertRaises(OverflowError, getargs_l, LONG_MIN-1) self.assertEqual(LONG_MIN, getargs_l(LONG_MIN)) @@ -176,6 +225,10 @@ # (PY_SSIZE_T_MIN ... PY_SSIZE_T_MAX) self.assertRaises(TypeError, getargs_n, 3.14) self.assertRaises(TypeError, getargs_n, Int()) + self.assertEqual(0, getargs_n(IntSubclass())) + self.assertRaises(TypeError, getargs_n, BadInt()) + self.assertRaises(TypeError, getargs_n, BadInt2()) + self.assertEqual(0, getargs_n(BadInt3())) self.assertRaises(OverflowError, getargs_n, PY_SSIZE_T_MIN-1) self.assertEqual(PY_SSIZE_T_MIN, getargs_n(PY_SSIZE_T_MIN)) @@ -195,6 +248,10 @@ self.assertRaises(TypeError, getargs_L, 3.14) self.assertRaises(TypeError, getargs_L, "Hello") self.assertEqual(99, getargs_L(Int())) + self.assertEqual(99, getargs_L(IntSubclass())) + self.assertRaises(TypeError, getargs_L, BadInt()) + self.assertWarns(DeprecationWarning, getargs_L, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_L, BadInt3()) self.assertRaises(OverflowError, getargs_L, LLONG_MIN-1) self.assertEqual(LLONG_MIN, getargs_L(LLONG_MIN)) @@ -209,6 +266,11 @@ # K return 'unsigned long long', no range checking self.assertRaises(TypeError, getargs_K, 3.14) self.assertRaises(TypeError, getargs_K, Int()) + self.assertEqual(99, getargs_K(IntSubclass())) + self.assertRaises(TypeError, getargs_K, BadInt()) + self.assertRaises(TypeError, getargs_K, BadInt2()) + self.assertWarns(DeprecationWarning, getargs_K, BadInt3()) + self.assertEqual(ULLONG_MAX, getargs_K(ULLONG_MAX)) self.assertEqual(0, getargs_K(0)) self.assertEqual(0, getargs_K(ULLONG_MAX+1)) diff -r af9f3d737d4a Lib/test/test_index.py --- a/Lib/test/test_index.py Tue Dec 10 10:21:51 2013 +0200 +++ b/Lib/test/test_index.py Tue Dec 10 13:46:57 2013 +0200 @@ -9,7 +9,7 @@ class TrapInt(int): def __index__(self): - return self + return int(self) class BaseTestCase(unittest.TestCase): def setUp(self): @@ -55,6 +55,31 @@ self.assertRaises(TypeError, slice(self.o).indices, 0) self.assertRaises(TypeError, slice(self.n).indices, 0) + def test_int_subclass_with_index(self): + # __index__ should be used when computing indices, even for int + # subclasses. See issue #17576. + class MyInt(int): + def __index__(self): + return int(self) + 1 + + my_int = MyInt(7) + direct_index = my_int.__index__() + operator_index = operator.index(my_int) + self.assertEqual(direct_index, 8) + self.assertEqual(operator_index, 8) + # Both results should be of exact type int. + self.assertIs(type(direct_index), int) + self.assertIs(type(operator_index), int) + + def test_index_returns_int_subclass(self): + class BadInt: + def __index__(self): + return True + + bad_int = BadInt() + with self.assertWarns(DeprecationWarning): + operator.index(bad_int) + class SeqTestCase: # This test case isn't run directly. It just defines common tests diff -r af9f3d737d4a Lib/test/test_int.py --- a/Lib/test/test_int.py Tue Dec 10 10:21:51 2013 +0200 +++ b/Lib/test/test_int.py Tue Dec 10 13:46:57 2013 +0200 @@ -312,10 +312,6 @@ def __int__(self): return 42 - class Foo3(int): - def __int__(self): - return self - class Foo4(int): def __int__(self): return 42 @@ -327,7 +323,6 @@ self.assertEqual(int(Foo0()), 42) self.assertEqual(int(Foo1()), 42) self.assertEqual(int(Foo2()), 42) - self.assertEqual(int(Foo3()), 0) self.assertEqual(int(Foo4()), 42) self.assertRaises(TypeError, int, Foo5()) @@ -392,6 +387,49 @@ with self.assertRaises(TypeError): int(TruncReturnsBadInt()) + def test_int_subclass_with_int(self): + class MyInt(int): + def __int__(self): + return 42 + + my_int = MyInt(7) + self.assertEqual(my_int, 7) + self.assertEqual(int(my_int), 42) + + def test_int_returns_int_subclass(self): + class BadInt: + def __int__(self): + return True + + class Foo3(int): + def __int__(self): + return self + + class TruncReturnsBadInt: + def __trunc__(self): + return BadInt() + + class TruncReturnsIntSubclass: + def __trunc__(self): + return True + + bad_int = BadInt() + with self.assertWarns(DeprecationWarning): + int(bad_int) + + bad_int = Foo3() + with self.assertWarns(DeprecationWarning): + int(bad_int) + + bad_int = TruncReturnsBadInt() + with self.assertWarns(DeprecationWarning): + int(bad_int) + + good_int = TruncReturnsIntSubclass() + n = int(good_int) + self.assertIs(type(n), int) + self.assertEqual(n, 1) + def test_error_message(self): def check(s, base=None): with self.assertRaises(ValueError, diff -r af9f3d737d4a Objects/abstract.c --- a/Objects/abstract.c Tue Dec 10 10:21:51 2013 +0200 +++ b/Objects/abstract.c Tue Dec 10 13:46:57 2013 +0200 @@ -1155,7 +1155,7 @@ return type_error("bad operand type for abs(): '%.200s'", o); } -/* Return a Python int from the object item +/* Return a Python int from the object item. Raise TypeError if the result is not an int or if the object cannot be interpreted as an index. */ @@ -1165,25 +1165,34 @@ PyObject *result = NULL; if (item == NULL) return null_error(); - if (PyLong_Check(item)) { + if (PyLong_CheckExact(item)) { Py_INCREF(item); return item; } - if (PyIndex_Check(item)) { - result = item->ob_type->tp_as_number->nb_index(item); - if (result && !PyLong_Check(result)) { - PyErr_Format(PyExc_TypeError, - "__index__ returned non-int " - "(type %.200s)", - result->ob_type->tp_name); - Py_DECREF(result); - return NULL; - } - } - else { + if (!PyIndex_Check(item)) { PyErr_Format(PyExc_TypeError, "'%.200s' object cannot be interpreted " "as an integer", item->ob_type->tp_name); + return NULL; + } + result = item->ob_type->tp_as_number->nb_index(item); + if (!result || PyLong_CheckExact(result)) + return result; + if (!PyLong_Check(result)) { + PyErr_Format(PyExc_TypeError, + "__index__ returned non-int (type %.200s)", + result->ob_type->tp_name); + Py_CLEAR(result); + } + else { + /* Issue #17576: warn if 'result' not of exact type int. */ + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "__index__ returned non-int (type %.200s). " + "The ability to return an instance of a strict subclass of int " + "is deprecated, and may be removed in a future version of Python.", + result->ob_type->tp_name)) + Py_CLEAR(result); } return result; } @@ -1235,34 +1244,6 @@ } -/* - Returns the Integral instance converted to an int. The instance is expected - to be an int or have an __int__ method. Steals integral's - reference. error_format will be used to create the TypeError if integral - isn't actually an Integral instance. error_format should be a format string - that can accept a char* naming integral's type. -*/ -static PyObject * -convert_integral_to_int(PyObject *integral, const char *error_format) -{ - PyNumberMethods *nb; - if (PyLong_Check(integral)) - return integral; - nb = Py_TYPE(integral)->tp_as_number; - if (nb->nb_int) { - PyObject *as_int = nb->nb_int(integral); - if (!as_int || PyLong_Check(as_int)) { - Py_DECREF(integral); - return as_int; - } - Py_DECREF(as_int); - } - PyErr_Format(PyExc_TypeError, error_format, Py_TYPE(integral)->tp_name); - Py_DECREF(integral); - return NULL; -} - - PyObject * PyNumber_Long(PyObject *o) { @@ -1280,18 +1261,8 @@ } m = o->ob_type->tp_as_number; if (m && m->nb_int) { /* This should include subclasses of int */ - PyObject *res = m->nb_int(o); - if (res && !PyLong_Check(res)) { - PyErr_Format(PyExc_TypeError, - "__int__ returned non-int (type %.200s)", - res->ob_type->tp_name); - Py_DECREF(res); - return NULL; - } - return res; + return (PyObject *)_PyLong_FromNbInt(o); } - if (PyLong_Check(o)) /* An int subclass without nb_int */ - return _PyLong_Copy((PyLongObject *)o); trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__); if (trunc_func) { PyObject *truncated = PyEval_CallObject(trunc_func, NULL); @@ -1301,8 +1272,17 @@ return NULL; /* __trunc__ is specified to return an Integral type, but int() needs to return a int. */ - int_instance = convert_integral_to_int(truncated, - "__trunc__ returned non-Integral (type %.200s)"); + m = truncated->ob_type->tp_as_number; + if (m == NULL || m->nb_int == NULL) { + PyErr_Format( + PyExc_TypeError, + "__trunc__ returned non-Integral (type %.200s)", + truncated->ob_type->tp_name); + Py_DECREF(truncated); + return NULL; + } + int_instance = (PyObject *)_PyLong_FromNbInt(truncated); + Py_DECREF(truncated); return int_instance; } if (PyErr_Occurred()) diff -r af9f3d737d4a Objects/longobject.c --- a/Objects/longobject.c Tue Dec 10 10:21:51 2013 +0200 +++ b/Objects/longobject.c Tue Dec 10 13:46:57 2013 +0200 @@ -122,6 +122,69 @@ return v; } +#define _PyLong_IsCompatible(obj) \ + (PyLong_CheckExact((obj)) || \ + (PyLong_Check((obj)) && \ + (obj)->ob_type->tp_as_number->nb_int == \ + PyLong_Type.tp_as_number->nb_int)) + +/* _PyLong_FromNbInt: Convert the given object to a PyLongObject + using the nb_int slot, if available. Raise TypeError if either the + nb_int slot is not available or the result of the call to nb_int + returns something not of type int. +*/ +PyLongObject * +_PyLong_FromNbInt(PyObject *integral) +{ + PyNumberMethods *nb; + PyObject *result; + + /* Fast path for the case that we already have an int. */ + if (PyLong_CheckExact(integral)) { + Py_INCREF(integral); + return (PyLongObject *)integral; + } + + nb = Py_TYPE(integral)->tp_as_number; + if (nb == NULL || nb->nb_int == NULL) { + PyErr_Format(PyExc_TypeError, + "an integer is required (got type %.200s)", + Py_TYPE(integral)->tp_name); + return NULL; + } + + /* Convert using the nb_int slot, which should return something + of exact type int. */ + result = nb->nb_int(integral); + if (result && !PyLong_CheckExact(result)) { + int exception_raised = 0; + if (!PyLong_Check(result)) { + PyErr_Format(PyExc_TypeError, + "__int__ returned non-int " + "(type %.200s)", + result->ob_type->tp_name); + exception_raised = 1; + } + else { + /* Issue #17576: warn if 'result' not of exact type int. */ + exception_raised = PyErr_WarnFormat( + PyExc_DeprecationWarning, + 1, + "__int__ returned non-int " + "(type %.200s). The ability to return an instance of " + "a strict subclass of int is deprecated, and " + "may be removed in a future version of Python.", + result->ob_type->tp_name); + } + if (exception_raised) { + Py_DECREF(result); + return NULL; + } + } + return (PyLongObject *)result; +} + + /* Allocate a new int object with size digits. Return NULL and set exception if we run out of memory. */ @@ -353,28 +416,17 @@ return -1; } - if (!PyLong_Check(vv)) { - PyNumberMethods *nb; - nb = vv->ob_type->tp_as_number; - if (nb == NULL || nb->nb_int == NULL) { - PyErr_SetString(PyExc_TypeError, - "an integer is required"); - return -1; - } - vv = (*nb->nb_int) (vv); - if (vv == NULL) + if (_PyLong_IsCompatible(vv)) { + v = (PyLongObject *)vv; + } + else { + v = _PyLong_FromNbInt(vv); + if (v == NULL) return -1; do_decref = 1; - if (!PyLong_Check(vv)) { - Py_DECREF(vv); - PyErr_SetString(PyExc_TypeError, - "nb_int should return int object"); - return -1; - } } res = -1; - v = (PyLongObject *)vv; i = Py_SIZE(v); switch (i) { @@ -418,7 +470,7 @@ } exit: if (do_decref) { - Py_DECREF(vv); + Py_DECREF(v); } return res; } @@ -636,36 +688,25 @@ unsigned long PyLong_AsUnsignedLongMask(PyObject *op) { - PyNumberMethods *nb; PyLongObject *lo; unsigned long val; - if (op && PyLong_Check(op)) + if (op == NULL) { + PyErr_BadInternalCall(); + return (unsigned long)-1; + } + + if (_PyLong_IsCompatible(op)) { return _PyLong_AsUnsignedLongMask(op); - - if (op == NULL || (nb = op->ob_type->tp_as_number) == NULL || - nb->nb_int == NULL) { - PyErr_SetString(PyExc_TypeError, "an integer is required"); - return (unsigned long)-1; - } - - lo = (PyLongObject*) (*nb->nb_int) (op); + } + + lo = _PyLong_FromNbInt(op); if (lo == NULL) return (unsigned long)-1; - if (PyLong_Check(lo)) { - val = _PyLong_AsUnsignedLongMask((PyObject *)lo); - Py_DECREF(lo); - if (PyErr_Occurred()) - return (unsigned long)-1; - return val; - } - else - { - Py_DECREF(lo); - PyErr_SetString(PyExc_TypeError, - "nb_int should return int object"); - return (unsigned long)-1; - } + + val = _PyLong_AsUnsignedLongMask((PyObject *)lo); + Py_DECREF(lo); + return val; } int @@ -1167,40 +1208,41 @@ PyLongObject *v; PY_LONG_LONG bytes; int res; + int do_decref = 0; /* if nb_int was called */ if (vv == NULL) { PyErr_BadInternalCall(); return -1; } - if (!PyLong_Check(vv)) { - PyNumberMethods *nb; - PyObject *io; - if ((nb = vv->ob_type->tp_as_number) == NULL || - nb->nb_int == NULL) { - PyErr_SetString(PyExc_TypeError, "an integer is required"); + + if (_PyLong_IsCompatible(vv)) { + v = (PyLongObject *)vv; + } + else { + v = _PyLong_FromNbInt(vv); + if (v == NULL) return -1; - } - io = (*nb->nb_int) (vv); - if (io == NULL) - return -1; - if (PyLong_Check(io)) { - bytes = PyLong_AsLongLong(io); - Py_DECREF(io); - return bytes; - } - Py_DECREF(io); - PyErr_SetString(PyExc_TypeError, "integer conversion failed"); - return -1; - } - - v = (PyLongObject*)vv; + do_decref = 1; + } + + res = 0; switch(Py_SIZE(v)) { - case -1: return -(sdigit)v->ob_digit[0]; - case 0: return 0; - case 1: return v->ob_digit[0]; - } - res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes, - SIZEOF_LONG_LONG, PY_LITTLE_ENDIAN, 1); + case -1: + bytes = -(sdigit)v->ob_digit[0]; + break; + case 0: + bytes = 0; + break; + case 1: + bytes = v->ob_digit[0]; + break; + default: + res = _PyLong_AsByteArray((PyLongObject *)v, (unsigned char *)&bytes, + SIZEOF_LONG_LONG, PY_LITTLE_ENDIAN, 1); + } + if (do_decref) { + Py_DECREF(v); + } /* Plan 9 can't handle PY_LONG_LONG in ? : expressions */ if (res < 0) @@ -1280,36 +1322,25 @@ unsigned PY_LONG_LONG PyLong_AsUnsignedLongLongMask(PyObject *op) { - PyNumberMethods *nb; PyLongObject *lo; unsigned PY_LONG_LONG val; - if (op && PyLong_Check(op)) + if (op == NULL) { + PyErr_BadInternalCall(); + return (unsigned long)-1; + } + + if (_PyLong_IsCompatible(op)) { return _PyLong_AsUnsignedLongLongMask(op); - - if (op == NULL || (nb = op->ob_type->tp_as_number) == NULL || - nb->nb_int == NULL) { - PyErr_SetString(PyExc_TypeError, "an integer is required"); - return (unsigned PY_LONG_LONG)-1; - } - - lo = (PyLongObject*) (*nb->nb_int) (op); + } + + lo = _PyLong_FromNbInt(op); if (lo == NULL) return (unsigned PY_LONG_LONG)-1; - if (PyLong_Check(lo)) { - val = _PyLong_AsUnsignedLongLongMask((PyObject *)lo); - Py_DECREF(lo); - if (PyErr_Occurred()) - return (unsigned PY_LONG_LONG)-1; - return val; - } - else - { - Py_DECREF(lo); - PyErr_SetString(PyExc_TypeError, - "nb_int should return int object"); - return (unsigned PY_LONG_LONG)-1; - } + + val = _PyLong_AsUnsignedLongLongMask((PyObject *)lo); + Py_DECREF(lo); + return val; } /* Get a C long long int from an int object or any object that has an @@ -1339,28 +1370,17 @@ return -1; } - if (!PyLong_Check(vv)) { - PyNumberMethods *nb; - nb = vv->ob_type->tp_as_number; - if (nb == NULL || nb->nb_int == NULL) { - PyErr_SetString(PyExc_TypeError, - "an integer is required"); - return -1; - } - vv = (*nb->nb_int) (vv); - if (vv == NULL) + if (_PyLong_IsCompatible(vv)) { + v = (PyLongObject *)vv; + } + else { + v = _PyLong_FromNbInt(vv); + if (v == NULL) return -1; do_decref = 1; - if (!PyLong_Check(vv)) { - Py_DECREF(vv); - PyErr_SetString(PyExc_TypeError, - "nb_int should return int object"); - return -1; - } } res = -1; - v = (PyLongObject *)vv; i = Py_SIZE(v); switch (i) { @@ -1404,7 +1424,7 @@ } exit: if (do_decref) { - Py_DECREF(vv); + Py_DECREF(v); } return res; }