summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorTal Einat <taleinat+github@gmail.com>2020-04-04 03:05:58 (GMT)
committerGitHub <noreply@github.com>2020-04-04 03:05:58 (GMT)
commit52013e5b6d5ca32eef5a3d65ecdf7db89cefc2fd (patch)
treeb66ad6b00c4ecd03f18bd951d0952ab728f794f9 /Lib
parent6e623ff9d251e0ce86e9b18a01bfd6f067079d7a (diff)
downloadcpython-52013e5b6d5ca32eef5a3d65ecdf7db89cefc2fd.zip
cpython-52013e5b6d5ca32eef5a3d65ecdf7db89cefc2fd.tar.gz
cpython-52013e5b6d5ca32eef5a3d65ecdf7db89cefc2fd.tar.bz2
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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/idlelib/NEWS.txt3
-rw-r--r--Lib/idlelib/calltip.py18
-rw-r--r--Lib/idlelib/idle_test/test_calltip.py24
3 files changed, 30 insertions, 15 deletions
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index 3a4873b..46b1523 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -3,6 +3,9 @@ Released on 2020-10-05?
======================================
+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 2e0db60..d4092c7 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 886959b..d386b5c 100644
--- a/Lib/idlelib/idle_test/test_calltip.py
+++ b/Lib/idlelib/idle_test/test_calltip.py
@@ -219,20 +219,30 @@ bytes() -> empty bytes object''')
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)