diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/idlelib/NEWS.txt | 3 | ||||
-rw-r--r-- | Lib/idlelib/calltip.py | 18 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_calltip.py | 24 |
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) |