https://github.com/python/cpython/commit/968f6e523afb9840d96cc18ba2eae7fdc7856b44
commit: 968f6e523afb9840d96cc18ba2eae7fdc7856b44
branch: main
author: Semyon Moroz <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-08-14T11:04:41+03:00
summary:

gh-130821: Add type information to error messages for invalid return type 
(GH-130835)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-04-28-18-59-11.gh-issue-130821.B11LU1.rst
M Lib/test/test_coroutines.py
M Lib/test/test_type_annotations.py
M Objects/abstract.c
M Objects/bytesobject.c
M Objects/complexobject.c
M Objects/fileobject.c
M Objects/floatobject.c
M Objects/funcobject.c
M Objects/genobject.c
M Objects/iterobject.c
M Objects/moduleobject.c
M Objects/object.c
M Objects/typeobject.c

diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index 42b13064befaa9..6ad7e7994f32b0 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -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())
 
@@ -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()
@@ -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())
 
@@ -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)
 
 
diff --git a/Lib/test/test_type_annotations.py 
b/Lib/test/test_type_annotations.py
index c66cb058552643..1415bbca22707c 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -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}
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-28-18-59-11.gh-issue-130821.B11LU1.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-28-18-59-11.gh-issue-130821.B11LU1.rst
new file mode 100644
index 00000000000000..09ffaf80a6d535
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-28-18-59-11.gh-issue-130821.B11LU1.rst
@@ -0,0 +1,2 @@
+Enhance wrong type error messages and make them more consistent. Patch by
+Semyon Moroz.
diff --git a/Objects/abstract.c b/Objects/abstract.c
index df96b935eccb44..8adad8407d04d4 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -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;
     }
@@ -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 non-negative int", o);
         return -1;
     }
     return res;
@@ -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;
     }
@@ -1421,17 +1423,17 @@ _PyNumber_Index(PyObject *item)
 
     if (!PyLong_Check(result)) {
         PyErr_Format(PyExc_TypeError,
-                     "__index__ returned non-int (type %.200s)",
-                     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;
     }
@@ -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;
         }
@@ -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;
         }
@@ -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)",
-                          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;
@@ -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;
@@ -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;
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 87ea1162e03513..933a371f6bbcc0 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -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;
         }
@@ -2793,8 +2793,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;
         }
diff --git a/Objects/complexobject.c b/Objects/complexobject.c
index b66ebe131ae605..9a5e11289a30a7 100644
--- a/Objects/complexobject.c
+++ b/Objects/complexobject.c
@@ -515,17 +515,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;
         }
diff --git a/Objects/fileobject.c b/Objects/fileobject.c
index e624405bd5f62f..05c3e75b4642ee 100644
--- a/Objects/fileobject.c
+++ b/Objects/fileobject.c
@@ -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)) {
@@ -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;
         }
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 93e1973d6b32fc..4e54e0829d3def 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -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;
         }
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 9532c21fc7082e..d8a10075578087 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -560,8 +560,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;
         }
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 3e7d6257006cfd..bcde9e1a7be07e 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -1092,14 +1092,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);
             }
         }
diff --git a/Objects/iterobject.c b/Objects/iterobject.c
index 5712e02ae828ab..e323987601d5d4 100644
--- a/Objects/iterobject.c
+++ b/Objects/iterobject.c
@@ -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;
         }
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index 862395e7881870..47681e4251849c 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -1329,8 +1329,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);
diff --git a/Objects/object.c b/Objects/object.c
index 479f4176a46039..fba86e63cd4a11 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -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;
     }
@@ -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;
     }
@@ -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;
         }
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 14bc5a4bc49f84..fb33bc747d885b 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -2168,8 +2168,9 @@ type_get_annotations(PyObject *tp, void 
*Py_UNUSED(closure))
                 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(annotations);
                 Py_DECREF(annotate);
                 Py_DECREF(dict);
@@ -3510,10 +3511,8 @@ mro_check(PyTypeObject *type, PyObject *mro)
     for (i = 0; i < n; i++) {
         PyObject *obj = PyTuple_GET_ITEM(mro, i);
         if (!PyType_Check(obj)) {
-            PyErr_Format(
-                PyExc_TypeError,
-                "mro() returned a non-class ('%.500s')",
-                Py_TYPE(obj)->tp_name);
+            PyErr_Format(PyExc_TypeError,
+                         "%N.mro() returned a non-class ('%T')", type, obj);
             return -1;
         }
         PyTypeObject *base = (PyTypeObject*)obj;
@@ -3521,8 +3520,8 @@ mro_check(PyTypeObject *type, PyObject *mro)
         if (!is_subtype_with_mro(lookup_tp_mro(solid), solid, 
solid_base(base))) {
             PyErr_Format(
                 PyExc_TypeError,
-                "mro() returned base with unsuitable layout ('%.500s')",
-                base->tp_name);
+                "%N.mro() returned base with unsuitable layout ('%N')",
+                type, base);
             return -1;
         }
     }
@@ -10419,9 +10418,8 @@ slot_nb_bool(PyObject *self)
     }
     else {
         PyErr_Format(PyExc_TypeError,
-                     "__bool__ should return "
-                     "bool, returned %s",
-                     Py_TYPE(value)->tp_name);
+                     "%T.__bool__() must return a bool, not %T",
+                     self, value);
         result = -1;
     }
     Py_DECREF(value);
@@ -10901,7 +10899,8 @@ slot_bf_getbuffer(PyObject *self, Py_buffer *buffer, 
int flags)
     }
     if (!PyMemoryView_Check(ret)) {
         PyErr_Format(PyExc_TypeError,
-                     "__buffer__ returned non-memoryview object");
+                     "%T.__buffer__() must return a memoryview, not %T",
+                     self, ret);
         goto fail;
     }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to