diff options
author | Sylvain Thénault <sylvain.thenault@logilab.fr> | 2009-03-24 09:35:56 +0100 |
---|---|---|
committer | Sylvain Thénault <sylvain.thenault@logilab.fr> | 2009-03-24 09:35:56 +0100 |
commit | a8ae7b8240ec5884cf70945370e4fd9bcbfb9c4e (patch) | |
tree | 4696bc26b8102096ae13f5fba43bed02c8867937 | |
parent | 9f84453ce29952433660704a2ecc5ac8ed53c10e (diff) | |
download | astroid-git-a8ae7b8240ec5884cf70945370e4fd9bcbfb9c4e.tar.gz |
some understanding of the __builtin__.property decorator
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | infutils.py | 22 | ||||
-rw-r--r-- | rebuilder.py | 24 | ||||
-rw-r--r-- | scoped_nodes.py | 16 | ||||
-rw-r--r-- | test/unittest_inference.py | 40 |
5 files changed, 83 insertions, 25 deletions
@@ -1,8 +1,12 @@ Change log for the astng package ================================ +-- + * some understanding of the __builtin__.property decorator + 2009-03-19 -- 0.18.0 - * major api / tree structure changes to make it works with compiler *and* py >= 2.5 _ast module + * major api / tree structure changes to make it works with compiler *and* + python >= 2.5 _ast module * cleanups and refactoring on the way diff --git a/infutils.py b/infutils.py index 26019f5a..f22ad714 100644 --- a/infutils.py +++ b/infutils.py @@ -256,26 +256,28 @@ class Instance(Proxy): try: # XXX frame should be self._proxied, or not ? return _infer_stmts( - self._wrap_attr(self.getattr(name, context, lookupclass=False)), + self._wrap_attr(self.getattr(name, context, lookupclass=False), context), context, frame=self) except NotFoundError: try: # fallback to class'igetattr since it has some logic to handle # descriptors - return self._wrap_attr(self._proxied.igetattr(name, context)) + return self._wrap_attr(self._proxied.igetattr(name, context), context) except NotFoundError: raise InferenceError(name) - def _wrap_attr(self, attrs): + def _wrap_attr(self, attrs, context=None): """wrap bound methods of attrs in a InstanceMethod proxies""" - # Guess which attrs are used in inference. - def wrap(attr): - if isinstance(attr, Function) and attr.type == 'method': - return InstanceMethod(attr) + for attr in attrs: + if isinstance(attr, Function): + if '__builtin__.property' in attr.decoratornames(): + for infered in attr.infer_call_result(self, context): + yield infered + elif attr.type == 'method': + yield InstanceMethod(attr) else: - return attr - return imap(wrap, attrs) - + yield attr + def infer_call_result(self, caller, context=None): """infer what's a class instance is returning when called""" infered = False diff --git a/rebuilder.py b/rebuilder.py index 80ec28cb..90047215 100644 --- a/rebuilder.py +++ b/rebuilder.py @@ -130,18 +130,22 @@ class RebuildVisitor(ASTVisitor): def leave_assign(self, node): """leave an Assign node to become astng""" klass = node.parent.frame() - if isinstance(klass, nodes.Class) and \ - isinstance(node.value, nodes.CallFunc) and \ - isinstance(node.value.func, nodes.Name): + if (isinstance(klass, nodes.Class) + and isinstance(node.value, nodes.CallFunc) + and isinstance(node.value.func, nodes.Name)): func_name = node.value.func.name - if func_name in ('classmethod', 'staticmethod'): - for ass_node in node.targets: - try: - meth = klass[ass_node.name] - if isinstance(meth, nodes.Function): + for ass_node in node.targets: + try: + meth = klass[ass_node.name] + if isinstance(meth, nodes.Function): + if func_name in ('classmethod', 'staticmethod'): meth.type = func_name - except (AttributeError, KeyError): - continue + try: + meth.extra_decorators.append(node.value) + except AttributeError: + meth.extra_decorators = [node.value] + except (AttributeError, KeyError): + continue elif getattr(node.targets[0], 'name', None) == '__metaclass__': # XXX check more... self._metaclass[-1] = 'type' # XXX get the actual metaclass diff --git a/scoped_nodes.py b/scoped_nodes.py index e42d3cee..fa1f6dae 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -31,6 +31,7 @@ __doctype__ = "restructuredtext en" import sys from logilab.common.compat import chain, set +from logilab.common.decorators import cached from logilab.astng import MANAGER, NotFoundError, NoDefault, \ ASTNGBuildingException, InferenceError @@ -378,8 +379,21 @@ class FunctionNG(object): # (eg pylint...) when is_method() return True return self.type != 'function' and isinstance(self.parent.frame(), Class) + @cached + def decoratornames(self): + """return a list of decorator qualified names""" + result = set() + decoratornodes = [] + if self.decorators is not None: + decoratornodes += self.decorators.nodes + decoratornodes += getattr(self, 'extra_decorators', []) + for decnode in decoratornodes: + for infnode in decnode.infer(): + result.add(infnode.qname()) + return result + def argnames(self): - """return argument names if there are any arguments""" + """return a list of argument names""" if self.args.args: # maybe None with builtin functions names = _rec_get_names(self.args.args) else: diff --git a/test/unittest_inference.py b/test/unittest_inference.py index e91efa14..9131769b 100644 --- a/test/unittest_inference.py +++ b/test/unittest_inference.py @@ -870,20 +870,20 @@ def f(x): if sys.version_info < (2, 5): self.skip('require py >= 2.5') data = "from ...common import date; print date" - astng = builder.string_build(data, 'logilab.astng.test.unittest_inference', __file__) + astng = builder.string_build(data, __name__, __file__) infered = get_name_node(astng, 'date').infer().next() self.assertIsInstance(infered, nodes.Module) self.assertEquals(infered.name, 'logilab.common.date') def test_python25_no_relative_import(self): data = 'import unittest_lookup; print unittest_lookup' - astng = builder.string_build(data, 'logilab.astng.test.unittest_inference', __file__) + astng = builder.string_build(data, __name__, __file__) self.failIf(astng.absolute_import_activated()) # infered = get_name_node(astng, 'unittest_lookup').infer().next() # self.assertIsInstance(infered, nodes.Module) # failed to import unittest_lookup since absolute_import is activated data = 'from __future__ import absolute_import; import unittest_lookup; print unittest_lookup' - astng = builder.string_build(data, 'logilab.astng.test.unittest_inference', __file__) + astng = builder.string_build(data, __name__, __file__) self.failUnless(astng.absolute_import_activated(), True) # infered = get_name_node(astng, 'unittest_lookup').infer().next() # # failed to import unittest_lookup since absolute_import is activated @@ -914,6 +914,40 @@ def f(x): # self.assertEquals(len(bopen), 1) # self.assertIsInstance(bopen[0], InstanceMethod) # self.failUnless(bopen[0].callable()) + + def test_property(self): + code = ''' +from smtplib import SMTP +class SendMailController(object): + + @property + def smtp(self): + return SMTP(mailhost, port) + + @property + def me(self): + return self +print SendMailController().smtp +print SendMailController().me +''' + astng = builder.string_build(code, __name__, __file__) + self.assertEquals(astng['SendMailController']['smtp'].decoratornames(), + set(('__builtin__.property',))) + propinfered = list(astng.body[2].values[0].infer()) + self.assertEquals(len(propinfered), 1) + propinfered = propinfered[0] + self.assertIsInstance(propinfered, Instance) + self.assertEquals(propinfered.name, 'SMTP') + self.assertEquals(propinfered.root().name, 'smtplib') + self.assertEquals(astng['SendMailController']['me'].decoratornames(), + set(('__builtin__.property',))) + propinfered = list(astng.body[3].values[0].infer()) + self.assertEquals(len(propinfered), 1) + propinfered = propinfered[0] + self.assertIsInstance(propinfered, Instance) + self.assertEquals(propinfered.name, 'SendMailController') + self.assertEquals(propinfered.root().name, __name__) + if __name__ == '__main__': unittest_main() |