diff options
Diffstat (limited to 'Lib/idlelib')
| -rw-r--r-- | Lib/idlelib/CallTips.py | 193 | ||||
| -rw-r--r-- | Lib/idlelib/NEWS.txt | 8 | 
2 files changed, 131 insertions, 70 deletions
| diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/CallTips.py index 22a8a29c1f..8afe455238 100644 --- a/Lib/idlelib/CallTips.py +++ b/Lib/idlelib/CallTips.py @@ -100,52 +100,53 @@ class CallTips:              return rpcclt.remotecall("exec", "get_the_calltip",                                       (expression,), {})          else: -            entity = self.get_entity(expression) -            return get_argspec(entity) +            return get_argspec(get_entity(expression)) -    def get_entity(self, expression): -        """Return the object corresponding to expression evaluated -        in a namespace spanning sys.modules and __main.dict__. -        """ -        if expression: -            namespace = sys.modules.copy() -            namespace.update(__main__.__dict__) -            try: -                return eval(expression, namespace) -            except BaseException: -                # An uncaught exception closes idle, and eval can raise any -                # exception, especially if user classes are involved. -                return None - -def _find_constructor(class_ob): -    "Find the nearest __init__() in the class tree." -    try: -        return class_ob.__init__.__func__ -    except AttributeError: -        for base in class_ob.__bases__: -            init = _find_constructor(base) -            if init: -                return init -        return None +def get_entity(expression): +    """Return the object corresponding to expression evaluated +    in a namespace spanning sys.modules and __main.dict__. +    """ +    if expression: +        namespace = sys.modules.copy() +        namespace.update(__main__.__dict__) +        try: +            return eval(expression, namespace) +        except BaseException: +            # An uncaught exception closes idle, and eval can raise any +            # exception, especially if user classes are involved. +            return None + +# The following are used in both get_argspec and tests +_self_pat = re.compile('self\,?\s*') +_default_callable_argspec = "No docstring, see docs."  def get_argspec(ob): -    """Get a string describing the arguments for the given object, -       only if it is callable.""" +    '''Return a string describing the arguments and return of a callable object. + +    For Python-coded functions and methods, the first line is introspected. +    Delete 'self' parameter for classes (.__init__) and bound methods. +    The last line is the first line of the doc string.  For builtins, this typically +    includes the arguments in addition to the return value. + +    '''      argspec = "" -    if ob is not None and hasattr(ob, '__call__'): +    if hasattr(ob, '__call__'):          if isinstance(ob, type): -            fob = _find_constructor(ob) -            if fob is None: -                fob = lambda: None -        elif isinstance(ob, types.MethodType): -            fob = ob.__func__ +            fob = getattr(ob, '__init__', None) +        elif isinstance(ob.__call__, types.MethodType): +            fob = ob.__call__          else:              fob = ob -        if isinstance(fob, (types.FunctionType, types.LambdaType)): +        if isinstance(fob, (types.FunctionType, types.MethodType)):              argspec = inspect.formatargspec(*inspect.getfullargspec(fob)) -            pat = re.compile('self\,?\s*') -            argspec = pat.sub("", argspec) -        doc = getattr(ob, "__doc__", "") +            if (isinstance(ob, (type, types.MethodType)) or +                    isinstance(ob.__call__, types.MethodType)): +                argspec = _self_pat.sub("", argspec) + +        if isinstance(ob.__call__, types.MethodType): +            doc = ob.__call__.__doc__ +        else: +            doc = getattr(ob, "__doc__", "")          if doc:              doc = doc.lstrip()              pos = doc.find("\n") @@ -154,13 +155,16 @@ def get_argspec(ob):              if argspec:                  argspec += "\n"              argspec += doc[:pos] +        if not argspec: +            argspec = _default_callable_argspec      return argspec  #################################################  # -# Test code -# +# Test code tests CallTips.fetch_tip, get_entity, and get_argspec +  def main(): +    # Putting expected in docstrings results in doubled tips for test      def t1(): "()"      def t2(a, b=None): "(a, b=None)"      def t3(a, *args): "(a, *args)" @@ -170,39 +174,88 @@ def main():      class TC(object):          "(ai=None, *b)" -        def __init__(self, ai=None, *b): "(ai=None, *b)" -        def t1(self): "()" -        def t2(self, ai, b=None): "(ai, b=None)" -        def t3(self, ai, *args): "(ai, *args)" -        def t4(self, *args): "(*args)" -        def t5(self, ai, *args): "(ai, *args)" -        def t6(self, ai, b=None, *args, **kw): "(ai, b=None, *args, **kw)" - -    __main__.__dict__.update(locals()) - -    def test(tests): -        ct = CallTips() -        failed=[] -        for t in tests: -            expected = t.__doc__ + "\n" + t.__doc__ -            name = t.__name__ -            # exercise fetch_tip(), not just get_argspec() -            try: -                qualified_name = "%s.%s" % (t.__self__.__class__.__name__, name) -            except AttributeError: -                qualified_name = name -            argspec = ct.fetch_tip(qualified_name) -            if argspec != expected: -                failed.append(t) -                fmt = "%s - expected %s, but got %s" -                print(fmt % (t.__name__, expected, get_argspec(t))) -        print("%d of %d tests failed" % (len(failed), len(tests))) +        def __init__(self, ai=None, *b): "(self, ai=None, *b)" +        def t1(self): "(self)" +        def t2(self, ai, b=None): "(self, ai, b=None)" +        def t3(self, ai, *args): "(self, ai, *args)" +        def t4(self, *args): "(self, *args)" +        def t5(self, ai, *args): "(self, ai, *args)" +        def t6(self, ai, b=None, *args, **kw): "(self, ai, b=None, *args, **kw)" +        @classmethod +        def cm(cls, a): "(cls, a)" +        @staticmethod +        def sm(b): "(b)" +        def __call__(self, ci): "(ci)"      tc = TC() -    tests = (t1, t2, t3, t4, t5, t6, -             TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6) -    test(tests) +    # Python classes that inherit builtin methods +    class Int(int):  "Int(x[, base]) -> integer" +    class List(list): "List() -> new empty list" +    # Simulate builtin with no docstring for default argspec test +    class SB:  __call__ = None + +    __main__.__dict__.update(locals())  # required for get_entity eval() + +    num_tests = num_fail = 0 +    tip = CallTips().fetch_tip + +    def test(expression, expected): +        nonlocal num_tests, num_fail +        num_tests += 1 +        argspec = tip(expression) +        if argspec != expected: +            num_fail += 1 +            fmt = "%s - expected\n%r\n - but got\n%r" +            print(fmt % (expression, expected, argspec)) + +    def test_builtins(): +        # if first line of a possibly multiline compiled docstring changes, +        # must change corresponding test string +        test('int',  "int(x[, base]) -> integer") +        test('Int',  Int.__doc__) +        test('types.MethodType', "method(function, instance)") +        test('list', "list() -> new empty list") +        test('List', List.__doc__) +        test('list.__new__', +               'T.__new__(S, ...) -> a new object with type S, a subtype of T') +        test('list.__init__', +               'x.__init__(...) initializes x; see help(type(x)) for signature') +        append_doc =  "L.append(object) -> None -- append object to end" +        test('list.append', append_doc) +        test('[].append', append_doc) +        test('List.append', append_doc) +        test('SB()', _default_callable_argspec) + +    def test_funcs(): +        for func  in (t1, t2, t3, t4, t5, t6, TC,): +            fdoc = func.__doc__ +            test(func.__name__, fdoc + "\n" + fdoc) +        for func in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.cm, TC.sm): +            fdoc = func.__doc__ +            test('TC.'+func.__name__, fdoc + "\n" + fdoc) + +    def test_methods(): +        for func  in (tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6): +            fdoc = func.__doc__ +            test('tc.'+func.__name__, _self_pat.sub("", fdoc) + "\n" + fdoc) +        fdoc = tc.__call__.__doc__ +        test('tc', fdoc + "\n" + fdoc) + +    def test_non_callables(): +        # expression evaluates, but not to a callable +        for expr in ('0', '0.0' 'num_tests', b'num_tests', '[]', '{}'): +            test(expr, '') +        # expression does not evaluate, but raises an exception +        for expr in ('1a', 'xyx', 'num_tests.xyz', '[int][1]', '{0:int}[1]'): +            test(expr, '') + +    test_builtins() +    test_funcs() +    test_non_callables() +    test_methods() + +    print("%d of %d tests failed" % (num_fail, num_tests))  if __name__ == '__main__':      main() diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index de9f55d2f7..d57237d36b 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -1,6 +1,14 @@  What's New in IDLE 3.2.4?  ========================= +- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE. +  Erroneous tool tips have been corrected. Default added for callables. + +- Issue10365: File open dialog now works instead of crashing even when +  parent window is closed while dialog is open. + +- Issue 14876: use user-selected font for highlight configuration. +  - Issue #14937: Perform auto-completion of filenames in strings even for    non-ASCII filenames. Likewise for identifiers. | 
