summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Thénault <sylvain.thenault@logilab.fr>2009-03-24 12:57:46 +0100
committerSylvain Thénault <sylvain.thenault@logilab.fr>2009-03-24 12:57:46 +0100
commit89a9270cb3dcf6c9dbcf8dcefd1e26e7517b099e (patch)
tree9a4eb3243705d2e64ff4faa7144d15eec204c1b6
parent3e9d8e5bfac1ec9287c75cd8e1754d1749a2aa48 (diff)
downloadastroid-git-89a9270cb3dcf6c9dbcf8dcefd1e26e7517b099e.tar.gz
introduce UnboundMethod / rename InstanceMethod to BoundMethod, handle .im_func
-rw-r--r--ChangeLog1
-rw-r--r--__init__.py2
-rw-r--r--infutils.py35
-rw-r--r--scoped_nodes.py12
-rw-r--r--test/unittest_inference.py34
5 files changed, 64 insertions, 20 deletions
diff --git a/ChangeLog b/ChangeLog
index 993486a1..90bdf1f5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@ Change log for the astng package
--
* some understanding of the __builtin__.property decorator
+ * inference: introduce UnboundMethod / rename InstanceMethod to BoundMethod
2009-03-19 -- 0.18.0
* major api / tree structure changes to make it works with compiler *and*
diff --git a/__init__.py b/__init__.py
index fd086622..d298ab80 100644
--- a/__init__.py
+++ b/__init__.py
@@ -62,4 +62,4 @@ from logilab.astng import scoped_nodes, lookup, inference
# more stuff available
from logilab.astng import raw_building
-from logilab.astng.infutils import YES, Instance, InstanceMethod
+from logilab.astng.infutils import YES, Instance, BoundMethod, UnboundMethod
diff --git a/infutils.py b/infutils.py
index 127a3846..5df5e399 100644
--- a/infutils.py
+++ b/infutils.py
@@ -273,12 +273,16 @@ class Instance(Proxy):
def _wrap_attr(self, attrs, context=None):
"""wrap bound methods of attrs in a InstanceMethod proxies"""
for attr in attrs:
- if isinstance(attr, Function):
+ if isinstance(attr, UnboundMethod):
if '__builtin__.property' in attr.decoratornames():
for infered in attr.infer_call_result(self, context):
yield infered
- elif attr.type == 'method':
- yield InstanceMethod(attr)
+ elif attr.type in ('method', 'classmethod'):
+ # XXX could get some information from the bound node:
+ # self (if method) or self._proxied (if class method)
+ yield BoundMethod(attr)
+ else:
+ yield attr
else:
yield attr
@@ -311,16 +315,25 @@ class Instance(Proxy):
return self._proxied.qname()
-class InstanceMethod(Proxy):
- """a special node representing a function bound to an instance"""
+class UnboundMethod(Proxy):
+ """a special node representing a method not bound to an instance"""
def __repr__(self):
- instance = self._proxied.parent.frame()
- return '<Bound method %s of %s.%s at 0x%s' % (self._proxied.name,
- instance.root().name,
- instance.name,
- id(self))
- __str__ = __repr__
+ frame = self._proxied.parent.frame()
+ return '<%s %s of %s at 0x%s' % (self.__class__.__name__,
+ self._proxied.name,
+ frame.qname(), id(self))
+
+ def is_bound(self):
+ return False
+
+ def igetattr(self, name, context=None):
+ if name == 'im_func':
+ return iter((self._proxied,))
+ return super(UnboundMethod, self).igetattr(name, context)
+
+class BoundMethod(UnboundMethod):
+ """a special node representing a method bound to an instance"""
def is_bound(self):
return True
diff --git a/scoped_nodes.py b/scoped_nodes.py
index fa1f6dae..10344dd4 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -39,8 +39,8 @@ from logilab.astng.nodes import Arguments, Class, Const, Function, GenExpr, \
From, Lambda, Module, Name, Pass, Raise, Tuple, List, Dict, Yield, \
DelAttr, DelName, const_factory as cf
from logilab.astng.utils import extend_class
-from logilab.astng.infutils import YES, InferenceContext, Instance, copy_context, \
- unpack_infer, _infer_stmts
+from logilab.astng.infutils import YES, InferenceContext, Instance, \
+ UnboundMethod, copy_context, unpack_infer, _infer_stmts
from logilab.astng.nodes_as_string import as_string
@@ -52,6 +52,12 @@ def remove_nodes(func, cls):
return nodes
return wrapper
+def function_to_unbound_method(func):
+ def wrapper(*args, **kwargs):
+ return [isinstance(n, Function) and UnboundMethod(n) or n
+ for n in func(*args, **kwargs)]
+ return wrapper
+
# module class dict/iterator interface ########################################
class LocalsDictMixIn(object):
@@ -718,7 +724,7 @@ class ClassNG(object):
except NotFoundError:
continue
raise NotFoundError(name)
- getattr = remove_nodes(getattr, DelAttr)
+ getattr = function_to_unbound_method(remove_nodes(getattr, DelAttr))
def igetattr(self, name, context=None):
"""infered getattr, need special treatment in class to handle
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
index f105972d..46a8f848 100644
--- a/test/unittest_inference.py
+++ b/test/unittest_inference.py
@@ -18,7 +18,7 @@ from StringIO import StringIO
from logilab.common.testlib import TestCase, unittest_main
from logilab.astng import InferenceError, builder, nodes, inference
-from logilab.astng.infutils import YES, Instance, InstanceMethod, path_wrapper
+from logilab.astng.infutils import YES, Instance, BoundMethod, UnboundMethod, path_wrapper
def get_name_node(start_from, name, index=0):
return [n for n in start_from.nodes_of_class(nodes.Name) if n.name == name][index]
@@ -220,7 +220,7 @@ a, b= b, a # Gasp !
def test_unbound_method_inference(self):
infered = self.astng['m_unbound'].infer()
meth1 = infered.next()
- self.assertIsInstance(meth1, nodes.Function)
+ self.assertIsInstance(meth1, UnboundMethod)
self.failUnlessEqual(meth1.name, 'meth1')
self.failUnlessEqual(meth1.parent.frame().name, 'C')
self.failUnlessRaises(StopIteration, infered.next)
@@ -228,7 +228,7 @@ a, b= b, a # Gasp !
def test_bound_method_inference(self):
infered = self.astng['m_bound'].infer()
meth1 = infered.next()
- self.assertIsInstance(meth1, InstanceMethod)
+ self.assertIsInstance(meth1, BoundMethod)
self.failUnlessEqual(meth1.name, 'meth1')
self.failUnlessEqual(meth1.parent.frame().name, 'C')
self.failUnlessRaises(StopIteration, infered.next)
@@ -912,7 +912,7 @@ def f(x):
# self.assertIsInstance(b, Instance)
# bopen = list(b.igetattr('open'))
# self.assertEquals(len(bopen), 1)
-# self.assertIsInstance(bopen[0], InstanceMethod)
+# self.assertIsInstance(bopen[0], BoundMethod)
# self.failUnless(bopen[0].callable())
def test_property(self):
@@ -948,6 +948,30 @@ print SendMailController().me
self.assertIsInstance(propinfered, Instance)
self.assertEquals(propinfered.name, 'SendMailController')
self.assertEquals(propinfered.root().name, __name__)
-
+
+
+ def test_im_func_unwrap(self):
+ code = '''
+class EnvBasedTC:
+ def pactions(self):
+ print "hop"
+pactions = EnvBasedTC.pactions.im_func
+print pactions
+
+class EnvBasedTC2:
+ pactions = EnvBasedTC.pactions.im_func
+ print pactions
+
+'''
+ astng = builder.string_build(code, __name__, __file__)
+ pactions = get_name_node(astng, 'pactions')
+ infered = list(pactions.infer())
+ self.assertEquals(len(infered), 1)
+ self.assertIsInstance(infered[0], nodes.Function)
+ pactions = get_name_node(astng['EnvBasedTC2'], 'pactions')
+ infered = list(pactions.infer())
+ self.assertEquals(len(infered), 1)
+ self.assertIsInstance(infered[0], nodes.Function)
+
if __name__ == '__main__':
unittest_main()