Kaydet (Commit) 21334e72 authored tarafından Terry Jan Reedy's avatar Terry Jan Reedy

Issue #16630: Make Idle calltips work even when __getattr__ raises.

Initial patch by Roger Serwy.
üst 3ce5255a
...@@ -134,56 +134,60 @@ def get_arg_text(ob): ...@@ -134,56 +134,60 @@ def get_arg_text(ob):
"""Get a string describing the arguments for the given object, """Get a string describing the arguments for the given object,
only if it is callable.""" only if it is callable."""
arg_text = "" arg_text = ""
if ob is not None and hasattr(ob, '__call__'): try:
arg_offset = 0 ob_call = ob.__call__
if type(ob) in (types.ClassType, types.TypeType): except BaseException:
# Look for the highest __init__ in the class chain. return arg_text
fob = _find_constructor(ob)
if fob is None: arg_offset = 0
fob = lambda: None if type(ob) in (types.ClassType, types.TypeType):
else: # Look for the highest __init__ in the class chain.
arg_offset = 1 fob = _find_constructor(ob)
elif type(ob) == types.MethodType: if fob is None:
# bit of a hack for methods - turn it into a function fob = lambda: None
# and drop the "self" param for bound methods
fob = ob.im_func
if ob.im_self:
arg_offset = 1
elif type(ob.__call__) == types.MethodType:
# a callable class instance
fob = ob.__call__.im_func
arg_offset = 1
else:
fob = ob
# Try to build one for Python defined functions
if type(fob) in [types.FunctionType, types.LambdaType]:
argcount = fob.func_code.co_argcount
real_args = fob.func_code.co_varnames[arg_offset:argcount]
defaults = fob.func_defaults or []
defaults = list(map(lambda name: "=%s" % repr(name), defaults))
defaults = [""] * (len(real_args) - len(defaults)) + defaults
items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
if fob.func_code.co_flags & 0x4:
items.append("*args")
if fob.func_code.co_flags & 0x8:
items.append("**kwds")
arg_text = ", ".join(items)
arg_text = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", arg_text)
# See if we can use the docstring
if isinstance(ob.__call__, types.MethodType):
doc = ob.__call__.__doc__
else: else:
doc = getattr(ob, "__doc__", "") arg_offset = 1
if doc: elif type(ob) == types.MethodType:
doc = doc.lstrip() # bit of a hack for methods - turn it into a function
pos = doc.find("\n") # and drop the "self" param for bound methods
if pos < 0 or pos > 70: fob = ob.im_func
pos = 70 if ob.im_self:
if arg_text: arg_offset = 1
arg_text += "\n" elif type(ob_call) == types.MethodType:
arg_text += doc[:pos] # a callable class instance
fob = ob_call.im_func
arg_offset = 1
else:
fob = ob
# Try to build one for Python defined functions
if type(fob) in [types.FunctionType, types.LambdaType]:
argcount = fob.func_code.co_argcount
real_args = fob.func_code.co_varnames[arg_offset:argcount]
defaults = fob.func_defaults or []
defaults = list(map(lambda name: "=%s" % repr(name), defaults))
defaults = [""] * (len(real_args) - len(defaults)) + defaults
items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
if fob.func_code.co_flags & 0x4:
items.append("*args")
if fob.func_code.co_flags & 0x8:
items.append("**kwds")
arg_text = ", ".join(items)
arg_text = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", arg_text)
# See if we can use the docstring
if isinstance(ob_call, types.MethodType):
doc = ob_call.__doc__
else:
doc = getattr(ob, "__doc__", "")
if doc:
doc = doc.lstrip()
pos = doc.find("\n")
if pos < 0 or pos > 70:
pos = 70
if arg_text:
arg_text += "\n"
arg_text += doc[:pos]
return arg_text return arg_text
if __name__ == '__main__': if __name__ == '__main__':
from unittest import main from unittest import main
main('idlelib.idle_test.test_calltips', verbosity=2, exit=False) main('idlelib.idle_test.test_calltips', verbosity=2)
...@@ -3,6 +3,7 @@ import idlelib.CallTips as ct ...@@ -3,6 +3,7 @@ import idlelib.CallTips as ct
CTi = ct.CallTips() # needed for get_entity test in 2.7 CTi = ct.CallTips() # needed for get_entity test in 2.7
import types import types
default_tip = ''
# Test Class TC is used in multiple get_argspec test methods # Test Class TC is used in multiple get_argspec test methods
class TC(object): class TC(object):
...@@ -41,7 +42,6 @@ class Get_signatureTest(unittest.TestCase): ...@@ -41,7 +42,6 @@ class Get_signatureTest(unittest.TestCase):
# but a red buildbot is better than a user crash (as has happened). # but a red buildbot is better than a user crash (as has happened).
# For a simple mismatch, change the expected output to the actual. # For a simple mismatch, change the expected output to the actual.
def test_builtins(self): def test_builtins(self):
# 2.7 puts '()\n' where 3.x does not, other minor differences # 2.7 puts '()\n' where 3.x does not, other minor differences
...@@ -65,8 +65,7 @@ class Get_signatureTest(unittest.TestCase): ...@@ -65,8 +65,7 @@ class Get_signatureTest(unittest.TestCase):
gtest(List.append, append_doc) gtest(List.append, append_doc)
gtest(types.MethodType, '()\ninstancemethod(function, instance, class)') gtest(types.MethodType, '()\ninstancemethod(function, instance, class)')
gtest(SB(), '') gtest(SB(), default_tip)
def test_functions(self): def test_functions(self):
def t1(): 'doc' def t1(): 'doc'
...@@ -92,9 +91,8 @@ class Get_signatureTest(unittest.TestCase): ...@@ -92,9 +91,8 @@ class Get_signatureTest(unittest.TestCase):
def test_bound_methods(self): def test_bound_methods(self):
# test that first parameter is correctly removed from argspec # test that first parameter is correctly removed from argspec
for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"), for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"),
(TC.cm, "(a)"),): (tc.__call__, '(ci)'), (tc, '(ci)'), (TC.cm, "(a)"),):
self.assertEqual(signature(meth), mtip + "\ndoc") self.assertEqual(signature(meth), mtip + "\ndoc")
self.assertEqual(signature(tc), "(ci)\ndoc")
def test_no_docstring(self): def test_no_docstring(self):
def nd(s): pass def nd(s): pass
...@@ -103,6 +101,17 @@ class Get_signatureTest(unittest.TestCase): ...@@ -103,6 +101,17 @@ class Get_signatureTest(unittest.TestCase):
self.assertEqual(signature(TC.nd), "(s)") self.assertEqual(signature(TC.nd), "(s)")
self.assertEqual(signature(tc.nd), "()") self.assertEqual(signature(tc.nd), "()")
def test_attribute_exception(self):
class NoCall(object):
def __getattr__(self, name):
raise BaseException
class Call(NoCall):
def __call__(self, ci):
pass
for meth, mtip in ((NoCall, '()'), (Call, '()'),
(NoCall(), ''), (Call(), '(ci)')):
self.assertEqual(signature(meth), mtip)
def test_non_callables(self): def test_non_callables(self):
for obj in (0, 0.0, '0', b'0', [], {}): for obj in (0, 0.0, '0', b'0', [], {}):
self.assertEqual(signature(obj), '') self.assertEqual(signature(obj), '')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment