[Python-checkins] bpo-38689: avoid IDLE hanging when calltip fails getting a signature (GH-17152)

Miss Islington (bot) webhook-mailer at python.org
Fri Apr 3 23:25:13 EDT 2020


https://github.com/python/cpython/commit/15337726e5b92976c2815d05c514804e9aa49a8c
commit: 15337726e5b92976c2815d05c514804e9aa49a8c
branch: 3.8
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2020-04-03T20:25:06-07:00
summary:

bpo-38689: avoid IDLE hanging when calltip fails getting a signature (GH-17152)


Inspect.signature failed on the test case because its isinstance call raised.
(cherry picked from commit 52013e5b6d5ca32eef5a3d65ecdf7db89cefc2fd)

Co-authored-by: Tal Einat <taleinat+github at gmail.com>

files:
A Misc/NEWS.d/next/IDLE/2019-11-14-12-59-19.bpo-38689.Lgfxva.rst
M Lib/idlelib/NEWS.txt
M Lib/idlelib/calltip.py
M Lib/idlelib/idle_test/test_calltip.py

diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index 3951fd8400214..de7543e370198 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -3,6 +3,9 @@ Released on 2019-12-16?
 ======================================
 
 
+bpo-38689: IDLE will no longer freeze when inspect.signature fails
+when fetching a calltip.
+
 bpo-27115: For 'Go to Line', use a Query entry box subclass with
 IDLE standard behavior and improved error checking.
 
diff --git a/Lib/idlelib/calltip.py b/Lib/idlelib/calltip.py
index 2e0db60d476ae..d4092c7847186 100644
--- a/Lib/idlelib/calltip.py
+++ b/Lib/idlelib/calltip.py
@@ -129,20 +129,22 @@ def get_argspec(ob):
     empty line or _MAX_LINES.    For builtins, this typically includes
     the arguments in addition to the return value.
     '''
-    argspec = default = ""
+    # Determine function object fob to inspect.
     try:
         ob_call = ob.__call__
-    except BaseException:
-        return default
-
+    except BaseException:  # Buggy user object could raise anything.
+        return ''  # No popup for non-callables.
     fob = ob_call if isinstance(ob_call, types.MethodType) else ob
 
+    # Initialize argspec and wrap it to get lines.
     try:
         argspec = str(inspect.signature(fob))
-    except ValueError as err:
+    except Exception as err:
         msg = str(err)
         if msg.startswith(_invalid_method):
             return _invalid_method
+        else:
+            argspec = ''
 
     if '/' in argspec and len(argspec) < _MAX_COLS - len(_argument_positional):
         # Add explanation TODO remove after 3.7, before 3.9.
@@ -154,6 +156,7 @@ def get_argspec(ob):
     lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
              if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
 
+    # Augment lines from docstring, if any, and join to get argspec.
     if isinstance(ob_call, types.MethodType):
         doc = ob_call.__doc__
     else:
@@ -167,9 +170,8 @@ def get_argspec(ob):
                 line = line[: _MAX_COLS - 3] + '...'
             lines.append(line)
     argspec = '\n'.join(lines)
-    if not argspec:
-        argspec = _default_callable_argspec
-    return argspec
+
+    return argspec or _default_callable_argspec
 
 
 if __name__ == '__main__':
diff --git a/Lib/idlelib/idle_test/test_calltip.py b/Lib/idlelib/idle_test/test_calltip.py
index 886959b17074f..d386b5cd81321 100644
--- a/Lib/idlelib/idle_test/test_calltip.py
+++ b/Lib/idlelib/idle_test/test_calltip.py
@@ -219,20 +219,30 @@ def test_no_docstring(self):
             with self.subTest(meth=meth, mtip=mtip):
                 self.assertEqual(get_spec(meth), mtip)
 
-    def test_attribute_exception(self):
+    def test_buggy_getattr_class(self):
         class NoCall:
-            def __getattr__(self, name):
-                raise BaseException
+            def __getattr__(self, name):  # Not invoked for class attribute.
+                raise IndexError  # Bug.
         class CallA(NoCall):
-            def __call__(oui, a, b, c):
+            def __call__(self, ci):  # Bug does not matter.
                 pass
         class CallB(NoCall):
-            def __call__(self, ci):
+            def __call__(oui, a, b, c):  # Non-standard 'self'.
                 pass
 
         for meth, mtip  in ((NoCall, default_tip), (CallA, default_tip),
-                            (NoCall(), ''), (CallA(), '(a, b, c)'),
-                            (CallB(), '(ci)')):
+                            (NoCall(), ''), (CallA(), '(ci)'),
+                            (CallB(), '(a, b, c)')):
+            with self.subTest(meth=meth, mtip=mtip):
+                self.assertEqual(get_spec(meth), mtip)
+
+    def test_metaclass_class(self):  # Failure case for issue 38689.
+        class Type(type):  # Type() requires 3 type args, returns class.
+            __class__ = property({}.__getitem__, {}.__setitem__)
+        class Object(metaclass=Type):
+            __slots__ = '__class__'
+        for meth, mtip  in ((Type, default_tip), (Object, default_tip),
+                            (Object(), '')):
             with self.subTest(meth=meth, mtip=mtip):
                 self.assertEqual(get_spec(meth), mtip)
 
diff --git a/Misc/NEWS.d/next/IDLE/2019-11-14-12-59-19.bpo-38689.Lgfxva.rst b/Misc/NEWS.d/next/IDLE/2019-11-14-12-59-19.bpo-38689.Lgfxva.rst
new file mode 100644
index 0000000000000..f4f4a2e9afd85
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2019-11-14-12-59-19.bpo-38689.Lgfxva.rst
@@ -0,0 +1,2 @@
+IDLE will no longer freeze when inspect.signature fails when fetching
+a calltip.



More information about the Python-checkins mailing list