Skip to content

Commit b6d8766

Browse files
gh-125788: Include signatures in mismatched ctypes callback TypeError
1 parent 3fb88e9 commit b6d8766

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

‎Lib/test/test_ctypes/test_callbacks.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,27 @@ def cb():
559559

560560
self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)
561561

562+
def test_mismatched_callback_type_error_includes_signature(self):
563+
# gh-125788: mismatched callback types used to say
564+
# "expected CFunctionType instance instead of CFunctionType".
565+
Expected = CFUNCTYPE(c_int, c_void_p, c_void_p)
566+
Actual = CFUNCTYPE(c_int, c_int, c_int)
567+
568+
def callback(a, b):
569+
return 0
570+
571+
actual = Actual(callback)
572+
with self.assertRaises(TypeError) as cm:
573+
Expected.from_param(actual)
574+
msg = str(cm.exception)
575+
self.assertIn("signature", msg)
576+
self.assertIn("restype", msg)
577+
self.assertIn("argtypes", msg)
578+
self.assertNotEqual(
579+
msg,
580+
f"expected {Expected.__name__} instance instead of {type(actual).__name__}",
581+
)
582+
562583

563584
if __name__ == '__main__':
564585
unittest.main()

‎Modules/_ctypes/_ctypes.c‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,32 @@ CDataType_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value)
11231123
_Py_LeaveRecursiveCall();
11241124
return value;
11251125
}
1126+
/* Callback function pointer types often share the same tp_name
1127+
("CFunctionType" / "WinFunctionType"), so include signatures when
1128+
both the expected type and the value are callback types (#125788). */
1129+
if (PyCFuncPtrTypeObject_Check(st, type) &&
1130+
PyCFuncPtrObject_Check(st, value)) {
1131+
StgInfo *expected_info;
1132+
StgInfo *actual_info;
1133+
if (PyStgInfo_FromType(st, type, &expected_info) < 0) {
1134+
return NULL;
1135+
}
1136+
if (PyStgInfo_FromType(st, (PyObject *)Py_TYPE(value), &actual_info) < 0) {
1137+
return NULL;
1138+
}
1139+
if (expected_info != NULL && actual_info != NULL) {
1140+
PyErr_Format(PyExc_TypeError,
1141+
"expected %s with signature (restype=%R, argtypes=%R) "
1142+
"but received %s with signature (restype=%R, argtypes=%R)",
1143+
((PyTypeObject *)type)->tp_name,
1144+
expected_info->restype ? expected_info->restype : Py_None,
1145+
expected_info->argtypes ? expected_info->argtypes : Py_None,
1146+
Py_TYPE(value)->tp_name,
1147+
actual_info->restype ? actual_info->restype : Py_None,
1148+
actual_info->argtypes ? actual_info->argtypes : Py_None);
1149+
return NULL;
1150+
}
1151+
}
11261152
PyErr_Format(PyExc_TypeError,
11271153
"expected %s instance instead of %s",
11281154
((PyTypeObject *)type)->tp_name,

0 commit comments

Comments
 (0)