]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-103899: Provide a hint when accidentally calling a module (GH-103900)
authorBrandt Bucher <brandtbucher@microsoft.com>
Thu, 4 May 2023 22:07:42 +0000 (15:07 -0700)
committerGitHub <noreply@github.com>
Thu, 4 May 2023 22:07:42 +0000 (15:07 -0700)
Lib/test/test_call.py
Misc/NEWS.d/next/Core and Builtins/2023-04-26-15-14-23.gh-issue-103899.1pqKPF.rst [new file with mode: 0644]
Objects/call.c

index aab7b1580eaf35932ba85e2468322592aeaa4519..12759c53bb662c19e2b5f7a7be527c5dad866fcf 100644 (file)
@@ -10,6 +10,7 @@ import itertools
 import gc
 import contextlib
 import sys
+import types
 
 
 class BadStr(str):
@@ -202,6 +203,37 @@ class CFunctionCallsErrorMessages(unittest.TestCase):
         msg = r"count\(\) takes no keyword arguments"
         self.assertRaisesRegex(TypeError, msg, [].count, x=2, y=2)
 
+    def test_object_not_callable(self):
+        msg = r"^'object' object is not callable$"
+        self.assertRaisesRegex(TypeError, msg, object())
+
+    def test_module_not_callable_no_suggestion_0(self):
+        msg = r"^'module' object is not callable$"
+        self.assertRaisesRegex(TypeError, msg, types.ModuleType("mod"))
+
+    def test_module_not_callable_no_suggestion_1(self):
+        msg = r"^'module' object is not callable$"
+        mod = types.ModuleType("mod")
+        mod.mod = 42
+        self.assertRaisesRegex(TypeError, msg, mod)
+
+    def test_module_not_callable_no_suggestion_2(self):
+        msg = r"^'module' object is not callable$"
+        mod = types.ModuleType("mod")
+        del mod.__name__
+        self.assertRaisesRegex(TypeError, msg, mod)
+
+    def test_module_not_callable_no_suggestion_3(self):
+        msg = r"^'module' object is not callable$"
+        mod = types.ModuleType("mod")
+        mod.__name__ = 42
+        self.assertRaisesRegex(TypeError, msg, mod)
+
+    def test_module_not_callable_suggestion(self):
+        msg = r"^'module' object is not callable\. Did you mean: 'mod\.mod\(\.\.\.\)'\?$"
+        mod = types.ModuleType("mod")
+        mod.mod = lambda: ...
+        self.assertRaisesRegex(TypeError, msg, mod)
 
 
 class TestCallingConventions(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-04-26-15-14-23.gh-issue-103899.1pqKPF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-04-26-15-14-23.gh-issue-103899.1pqKPF.rst
new file mode 100644 (file)
index 0000000..c12a6b9
--- /dev/null
@@ -0,0 +1,3 @@
+Provide a helpful hint in the :exc:`TypeError` message when accidentally
+calling a :term:`module` object that has a callable attribute of the same
+name (such as :func:`dis.dis` or :class:`datetime.datetime`).
index cf6e357a990441e3cb7163b6abdfc7fefedcd3fb..0d548dcd5e1aed796a5c329123b1f5198ed6e22b 100644 (file)
@@ -157,6 +157,42 @@ PyObject_VectorcallDict(PyObject *callable, PyObject *const *args,
     return _PyObject_FastCallDictTstate(tstate, callable, args, nargsf, kwargs);
 }
 
+static void
+object_is_not_callable(PyThreadState *tstate, PyObject *callable)
+{
+    if (Py_IS_TYPE(callable, &PyModule_Type)) {
+        // >>> import pprint
+        // >>> pprint(thing)
+        // Traceback (most recent call last):
+        //   File "<stdin>", line 1, in <module>
+        // TypeError: 'module' object is not callable. Did you mean: 'pprint.pprint(...)'?
+        PyObject *name = PyModule_GetNameObject(callable);
+        if (name == NULL) {
+            _PyErr_Clear(tstate);
+            goto basic_type_error;
+        }
+        PyObject *attr;
+        int res = _PyObject_LookupAttr(callable, name, &attr);
+        if (res < 0) {
+            _PyErr_Clear(tstate);
+        }
+        else if (res > 0 && PyCallable_Check(attr)) {
+            _PyErr_Format(tstate, PyExc_TypeError,
+                          "'%.200s' object is not callable. "
+                          "Did you mean: '%U.%U(...)'?",
+                          Py_TYPE(callable)->tp_name, name, name);
+            Py_DECREF(attr);
+            Py_DECREF(name);
+            return;
+        }
+        Py_XDECREF(attr);
+        Py_DECREF(name);
+    }
+basic_type_error:
+    _PyErr_Format(tstate, PyExc_TypeError, "'%.200s' object is not callable",
+                  Py_TYPE(callable)->tp_name);
+}
+
 
 PyObject *
 _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable,
@@ -171,9 +207,7 @@ _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable,
      * temporary dictionary for keyword arguments (if any) */
     ternaryfunc call = Py_TYPE(callable)->tp_call;
     if (call == NULL) {
-        _PyErr_Format(tstate, PyExc_TypeError,
-                      "'%.200s' object is not callable",
-                      Py_TYPE(callable)->tp_name);
+        object_is_not_callable(tstate, callable);
         return NULL;
     }
 
@@ -322,9 +356,7 @@ _PyObject_Call(PyThreadState *tstate, PyObject *callable,
     else {
         call = Py_TYPE(callable)->tp_call;
         if (call == NULL) {
-            _PyErr_Format(tstate, PyExc_TypeError,
-                          "'%.200s' object is not callable",
-                          Py_TYPE(callable)->tp_name);
+            object_is_not_callable(tstate, callable);
             return NULL;
         }