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 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 25 additions & 26 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,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 type int (not %T)",
o, result);
Py_DECREF(result);
return -1;
}
Expand All @@ -140,7 +141,9 @@ 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 positive int (not %T)",
o, result);
return -1;
}
return res;
Expand Down Expand Up @@ -884,8 +887,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 type str (not %T)",
obj, result);
Py_SETREF(result, NULL);
goto done;
}
Expand Down Expand Up @@ -1418,17 +1421,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 type 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 type 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 @@ -1528,17 +1531,16 @@ 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 type 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 type 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 @@ -1606,17 +1608,17 @@ 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 type 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 type 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 @@ -2432,10 +2434,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 type iterable (not %T)",
o, meth, meth_output);
}
Py_DECREF(meth_output);
return NULL;
Expand Down Expand Up @@ -2815,9 +2815,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 type iterator of type '%T'",
o, res);
Py_SETREF(res, NULL);
}
return res;
Expand All @@ -2836,8 +2835,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 type async iterator of type '%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 @@ -567,8 +567,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 type bytes (not %T)",
v, result);
Py_DECREF(result);
return NULL;
}
Expand Down Expand Up @@ -2762,8 +2762,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 type 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 type 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 type 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 @@ -66,9 +66,9 @@ PyFile_GetLine(PyObject *f, int n)
}
if (result != NULL && !PyBytes_Check(result) &&
!PyUnicode_Check(result)) {
PyErr_Format(PyExc_TypeError,
"%T.readline() must return type 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 @@ -191,8 +191,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 type 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 @@ -297,11 +297,11 @@
if (nb && nb->nb_index) {
PyObject *res = _PyNumber_Index(op);
if (!res) {
return -1;

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (arm64)

syntax error: missing ')' before '{' [C:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (x64)

syntax error: missing ')' before '{' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Windows (free-threading) / Build and test (x64)

syntax error: missing ')' before '{' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Windows (free-threading) / Build and test (arm64)

syntax error: missing ')' before '{' [C:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

expected ‘)’ before ‘{’ token

Check failure on line 300 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

expected ‘)’ before ‘{’ token
}
double val = PyLong_AsDouble(res);
Py_DECREF(res);
return val;

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

expected expression before ‘}’ token

Check failure on line 304 in Objects/floatobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

expected expression before ‘}’ token
}
PyErr_Format(PyExc_TypeError, "must be real number, not %.50s",
Py_TYPE(op)->tp_name);
Expand All @@ -315,16 +315,16 @@
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 type 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 type 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 @@ -556,8 +556,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 type dict of type '%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 @@ -1076,14 +1076,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__ returned a 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 type iterator "
"of type '%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 @@ -342,8 +342,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 type iterable (not %T)",
obj, awaitable);
Py_DECREF(awaitable);
return NULL;
}
Expand Down
6 changes: 4 additions & 2 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1258,8 +1258,10 @@ 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,
"%T.__annotate__ must return type dict "
"of type '%T'",
self, 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 @@ -779,8 +779,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 type str (not %T)", v, res);
Py_DECREF(res);
return NULL;
}
Expand Down Expand Up @@ -822,8 +821,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 type str (not %T)", v, res);
Py_DECREF(res);
return NULL;
}
Expand Down Expand Up @@ -878,8 +876,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 type bytes (not %T)",
v, result);
Py_DECREF(result);
return NULL;
}
Expand Down
18 changes: 8 additions & 10 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2010,8 +2010,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,
"%T.__annotate__ must return type dict of "
"type '%T'", tp, annotations);
Py_DECREF(annotations);
Py_DECREF(annotate);
Py_DECREF(dict);
Expand Down Expand Up @@ -3238,10 +3239,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,
"%T.mro() must return class (not %T)", type, obj);
return -1;
}
PyTypeObject *base = (PyTypeObject*)obj;
Expand Down Expand Up @@ -9920,9 +9919,7 @@ 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 type bool (not %T)", self, value);
result = -1;
}

Expand Down Expand Up @@ -10447,7 +10444,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 type memoryview (not %T)",
self, ret);
goto fail;
}

Expand Down
Loading
Loading