Skip to content

gh-130821: Add type information to wrong type error messages #130835

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
8 changes: 4 additions & 4 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ async def foo():
return (await Awaitable())

with self.assertRaisesRegex(
TypeError, "__await__.*returned non-iterator of type"):
TypeError, "__await__.*must return an iterator, not"):

run_async(foo())

Expand Down Expand Up @@ -1106,7 +1106,7 @@ async def foo():
return await Awaitable()

with self.assertRaisesRegex(
TypeError, r"__await__\(\) returned a coroutine"):
TypeError, r"__await__\(\) must return an iterator, not coroutine"):
run_async(foo())

c.close()
Expand All @@ -1120,7 +1120,7 @@ async def foo():
return await Awaitable()

with self.assertRaisesRegex(
TypeError, "__await__.*returned non-iterator of type"):
TypeError, "__await__.*must return an iterator, not"):

run_async(foo())

Expand Down Expand Up @@ -2490,7 +2490,7 @@ async def foo():
return (await future)

with self.assertRaisesRegex(
TypeError, "__await__.*returned non-iterator of type 'int'"):
TypeError, "__await__.*must return an iterator, not int"):
self.assertEqual(foo().send(None), 1)


Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def check_annotations(self, f):
print(f.__annotations__)

f.__annotate__ = lambda x: 42
with self.assertRaisesRegex(TypeError, r"__annotate__ returned non-dict of type 'int'"):
with self.assertRaisesRegex(TypeError, r"__annotate__\(\) must return a dict, not int"):
print(f.__annotations__)

f.__annotate__ = lambda x: {"x": x}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Enhance wrong type error messages and make them more consistent. Patch by
Semyon Moroz.
50 changes: 24 additions & 26 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ PyObject_LengthHint(PyObject *o, Py_ssize_t defaultvalue)
return defaultvalue;
}
if (!PyLong_Check(result)) {
PyErr_Format(PyExc_TypeError, "__length_hint__ must be an integer, not %.100s",
Py_TYPE(result)->tp_name);
PyErr_Format(PyExc_TypeError,
"%T.__length_hint__() must return an int, not %T",
o, result);
Py_DECREF(result);
return -1;
}
Expand All @@ -143,7 +144,8 @@ PyObject_LengthHint(PyObject *o, Py_ssize_t defaultvalue)
return -1;
}
if (res < 0) {
PyErr_Format(PyExc_ValueError, "__length_hint__() should return >= 0");
PyErr_Format(PyExc_ValueError,
"%T.__length_hint__() must return a positive int", o);
return -1;
}
return res;
Expand Down Expand Up @@ -887,8 +889,8 @@ PyObject_Format(PyObject *obj, PyObject *format_spec)

if (result && !PyUnicode_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__format__ must return a str, not %.200s",
Py_TYPE(result)->tp_name);
"%T.__format__() must return a str, not %T",
obj, result);
Py_SETREF(result, NULL);
goto done;
}
Expand Down Expand Up @@ -1421,17 +1423,17 @@ _PyNumber_Index(PyObject *item)

if (!PyLong_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__index__ returned non-int (type %.200s)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much reason to change this error message either.

Copy link
Contributor Author

@donBarbos donBarbos Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to the more common way as you recommended elsewhere: __method__() must return an int, not ...
and added type info before method name, like for other methods

Py_TYPE(result)->tp_name);
"%T.__index__() must return an int, not %T",
item, result);
Py_DECREF(result);
return NULL;
}
/* Issue #17576: warn if 'result' not of exact type int. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"__index__ returned non-int (type %.200s). "
"%T.__index__() must return an int, not %T. "
"The ability to return an instance of a strict subclass of int "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(result)->tp_name)) {
item, result)) {
Py_DECREF(result);
return NULL;
}
Expand Down Expand Up @@ -1531,17 +1533,17 @@ PyNumber_Long(PyObject *o)

if (!PyLong_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__int__ returned non-int (type %.200s)",
Py_TYPE(result)->tp_name);
"%T.__int__() must return an int, not %T",
o, result);
Py_DECREF(result);
return NULL;
}
/* Issue #17576: warn if 'result' not of exact type int. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"__int__ returned non-int (type %.200s). "
"%T.__int__() must return an int, not %T. "
"The ability to return an instance of a strict subclass of int "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(result)->tp_name)) {
o, result)) {
Py_DECREF(result);
return NULL;
}
Expand Down Expand Up @@ -1609,17 +1611,16 @@ PyNumber_Float(PyObject *o)

if (!PyFloat_Check(res)) {
PyErr_Format(PyExc_TypeError,
"%.50s.__float__ returned non-float (type %.50s)",
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name);
"%T.__float__() must return a float, not %T", o, res);
Py_DECREF(res);
return NULL;
}
/* Issue #26983: warn if 'res' not of exact type float. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"%.50s.__float__ returned non-float (type %.50s). "
"%T.__float__() must return a float, not %T. "
"The ability to return an instance of a strict subclass of float "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name)) {
o, res)) {
Py_DECREF(res);
return NULL;
}
Expand Down Expand Up @@ -2435,10 +2436,8 @@ method_output_as_list(PyObject *o, PyObject *meth)
PyThreadState *tstate = _PyThreadState_GET();
if (_PyErr_ExceptionMatches(tstate, PyExc_TypeError)) {
_PyErr_Format(tstate, PyExc_TypeError,
"%.200s.%U() returned a non-iterable (type %.200s)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I strongly prefer the old message. Iterable is not a type, it's a category of types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about new message: "%T.%U() must return an iterable, not %T"

Py_TYPE(o)->tp_name,
meth,
Py_TYPE(meth_output)->tp_name);
"%T.%U() must return an iterable, not %T",
o, meth, meth_output);
}
Py_DECREF(meth_output);
return NULL;
Expand Down Expand Up @@ -2818,9 +2817,8 @@ PyObject_GetIter(PyObject *o)
PyObject *res = (*f)(o);
if (res != NULL && !PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"iter() returned non-iterator "
"of type '%.100s'",
Py_TYPE(res)->tp_name);
"%T.iter() must return an iterator, not %T",
o, res);
Py_SETREF(res, NULL);
}
return res;
Expand All @@ -2839,8 +2837,8 @@ PyObject_GetAIter(PyObject *o) {
PyObject *it = (*f)(o);
if (it != NULL && !PyAIter_Check(it)) {
PyErr_Format(PyExc_TypeError,
"aiter() returned not an async iterator of type '%.100s'",
Py_TYPE(it)->tp_name);
"%T.aiter() must return an async iterator, not %T",
o, it);
Py_SETREF(it, NULL);
}
return it;
Expand Down
8 changes: 4 additions & 4 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -566,8 +566,8 @@ format_obj(PyObject *v, const char **pbuf, Py_ssize_t *plen)
return NULL;
if (!PyBytes_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__bytes__ returned non-bytes (type %.200s)",
Py_TYPE(result)->tp_name);
"%T.__bytes__() must return a bytes, not %T",
v, result);
Py_DECREF(result);
return NULL;
}
Expand Down Expand Up @@ -2788,8 +2788,8 @@ bytes_new_impl(PyTypeObject *type, PyObject *x, const char *encoding,
return NULL;
if (!PyBytes_Check(bytes)) {
PyErr_Format(PyExc_TypeError,
"__bytes__ returned non-bytes (type %.200s)",
Py_TYPE(bytes)->tp_name);
"%T.__bytes__() must return a bytes, not %T",
x, bytes);
Py_DECREF(bytes);
return NULL;
}
Expand Down
8 changes: 4 additions & 4 deletions Objects/complexobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,17 +499,17 @@ try_complex_special_method(PyObject *op)
}
if (!PyComplex_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__complex__ returned non-complex (type %.200s)",
Py_TYPE(res)->tp_name);
"%T.__complex__() must return a complex, not %T",
op, res);
Py_DECREF(res);
return NULL;
}
/* Issue #29894: warn if 'res' not of exact type complex. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"__complex__ returned non-complex (type %.200s). "
"%T.__complex__() must return a complex, not %T. "
"The ability to return an instance of a strict subclass of complex "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(res)->tp_name)) {
op, res)) {
Py_DECREF(res);
return NULL;
}
Expand Down
8 changes: 4 additions & 4 deletions Objects/fileobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ PyFile_GetLine(PyObject *f, int n)
}
if (result != NULL && !PyBytes_Check(result) &&
!PyUnicode_Check(result)) {
PyErr_Format(PyExc_TypeError,
"%T.readline() must return a str, not %T", f, result);
Py_SETREF(result, NULL);
PyErr_SetString(PyExc_TypeError,
"object.readline() returned non-string");
}

if (n < 0 && result != NULL && PyBytes_Check(result)) {
Expand Down Expand Up @@ -193,8 +193,8 @@ PyObject_AsFileDescriptor(PyObject *o)
Py_DECREF(fno);
}
else {
PyErr_SetString(PyExc_TypeError,
"fileno() returned a non-integer");
PyErr_Format(PyExc_TypeError,
"%T.fileno() must return an int, not %T", o, fno);
Py_DECREF(fno);
return -1;
}
Expand Down
8 changes: 4 additions & 4 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -288,16 +288,16 @@ PyFloat_AsDouble(PyObject *op)
if (!PyFloat_CheckExact(res)) {
if (!PyFloat_Check(res)) {
PyErr_Format(PyExc_TypeError,
"%.50s.__float__ returned non-float (type %.50s)",
Py_TYPE(op)->tp_name, Py_TYPE(res)->tp_name);
"%T.__float__() must return a float, not %T",
op, res);
Py_DECREF(res);
return -1;
}
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"%.50s.__float__ returned non-float (type %.50s). "
"%T.__float__() must return a float, not %T. "
"The ability to return an instance of a strict subclass of float "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(op)->tp_name, Py_TYPE(res)->tp_name)) {
op, res)) {
Py_DECREF(res);
return -1;
}
Expand Down
5 changes: 3 additions & 2 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,9 @@ func_get_annotation_dict(PyFunctionObject *op)
return NULL;
}
if (!PyDict_Check(ann_dict)) {
PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
Py_TYPE(ann_dict)->tp_name);
PyErr_Format(PyExc_TypeError,
"__annotate__() must return a dict, not %T",
ann_dict);
Py_DECREF(ann_dict);
return NULL;
}
Expand Down
10 changes: 5 additions & 5 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1082,14 +1082,14 @@ _PyCoro_GetAwaitableIter(PyObject *o)
if (PyCoro_CheckExact(res) || gen_is_coroutine(res)) {
/* __await__ must return an *iterator*, not
a coroutine or another awaitable (see PEP 492) */
PyErr_SetString(PyExc_TypeError,
"__await__() returned a coroutine");
PyErr_Format(PyExc_TypeError,
"%T.__await__() must return an iterator, "
"not coroutine", o);
Py_CLEAR(res);
} else if (!PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__await__() returned non-iterator "
"of type '%.100s'",
Py_TYPE(res)->tp_name);
"%T.__await__() must return an iterator, "
"not %T", o, res);
Py_CLEAR(res);
}
}
Expand Down
5 changes: 3 additions & 2 deletions Objects/iterobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,9 @@ anextawaitable_getiter(anextawaitableobject *obj)
}
Py_SETREF(awaitable, new_awaitable);
if (!PyIter_Check(awaitable)) {
PyErr_SetString(PyExc_TypeError,
"__await__ returned a non-iterable");
PyErr_Format(PyExc_TypeError,
"%T.__await__() must return an iterable, not %T",
obj, awaitable);
Py_DECREF(awaitable);
return NULL;
}
Expand Down
5 changes: 3 additions & 2 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,9 @@ module_get_annotations(PyObject *self, void *Py_UNUSED(ignored))
return NULL;
}
if (!PyDict_Check(annotations)) {
PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
Py_TYPE(annotations)->tp_name);
PyErr_Format(PyExc_TypeError,
"__annotate__() must return a dict, not %T",
annotations);
Py_DECREF(annotate);
Py_DECREF(annotations);
Py_DECREF(dict);
Expand Down
10 changes: 4 additions & 6 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -784,8 +784,7 @@ PyObject_Repr(PyObject *v)
}
if (!PyUnicode_Check(res)) {
_PyErr_Format(tstate, PyExc_TypeError,
"__repr__ returned non-string (type %.200s)",
Py_TYPE(res)->tp_name);
"%T.__repr__() must return a str, not %T", v, res);
Py_DECREF(res);
return NULL;
}
Expand Down Expand Up @@ -827,8 +826,7 @@ PyObject_Str(PyObject *v)
}
if (!PyUnicode_Check(res)) {
_PyErr_Format(tstate, PyExc_TypeError,
"__str__ returned non-string (type %.200s)",
Py_TYPE(res)->tp_name);
"%T.__str__() must return a str, not %T", v, res);
Py_DECREF(res);
return NULL;
}
Expand Down Expand Up @@ -883,8 +881,8 @@ PyObject_Bytes(PyObject *v)
return NULL;
if (!PyBytes_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__bytes__ returned non-bytes (type %.200s)",
Py_TYPE(result)->tp_name);
"%T.__bytes__() must return a bytes, not %T",
v, result);
Py_DECREF(result);
return NULL;
}
Expand Down
Loading
Loading