From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Sat, 4 Apr 2020 03:24:39 +0000 (-0700) Subject: bpo-38689: avoid IDLE hanging when calltip fails getting a signature (GH-17152) X-Git-Tag: v3.7.8rc1~106 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=681044a0ab6c93554ff8d003c7f9fe5fdb0c83ba;p=thirdparty%2FPython%2Fcpython.git 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 --- diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index dc2aeb96ee02..93084bfc2e55 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 2e0db60d476a..d4092c784718 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 886959b17074..d386b5cd8132 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) 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 000000000000..f4f4a2e9afd8 --- /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.