diff options
| author | Claudiu Popa <pcmanticore@gmail.com> | 2014-05-07 17:14:48 +0300 |
|---|---|---|
| committer | Claudiu Popa <pcmanticore@gmail.com> | 2014-05-07 17:14:48 +0300 |
| commit | cd08d8724ec41f4630b143a62f0d4e5479ab0607 (patch) | |
| tree | 436e1040e9de8e5f9f4dc58aa72dce1811deac9a | |
| parent | b13ef13e8365348b957e77105fdeeeb5bde69c49 (diff) | |
| download | astroid-git-cd08d8724ec41f4630b143a62f0d4e5479ab0607.tar.gz | |
Function nodes can detect if they are decorated with subclasses of builtin descriptors when determining their type (`classmethod` and `staticmethod`).
--HG--
branch : classmethod_subclasses
| -rw-r--r-- | ChangeLog | 4 | ||||
| -rw-r--r-- | rebuilder.py | 8 | ||||
| -rw-r--r-- | scoped_nodes.py | 34 | ||||
| -rw-r--r-- | test/unittest_scoped_nodes.py | 31 |
4 files changed, 71 insertions, 6 deletions
@@ -2,6 +2,10 @@ Change log for the astroid package (used to be astng) ===================================================== -- + * Function nodes can detect if they are decorated with subclasses + of builtin descriptors when determining their type + (`classmethod` and `staticmethod`). + * `Class.metaclass()` looks in ancestors when the current class does not define explicitly a metaclass. diff --git a/rebuilder.py b/rebuilder.py index ef8e7635..40a614f8 100644 --- a/rebuilder.py +++ b/rebuilder.py @@ -516,16 +516,16 @@ class TreeRebuilder(object): frame = newnode.parent.frame() if isinstance(frame, new.Class): if newnode.name == '__new__': - newnode.type = 'classmethod' + newnode._type = 'classmethod' else: - newnode.type = 'method' + newnode._type = 'method' if newnode.decorators is not None: for decorator_expr in newnode.decorators.nodes: if isinstance(decorator_expr, new.Name): if decorator_expr.name in ('classmethod', 'staticmethod'): - newnode.type = decorator_expr.name + newnode._type = decorator_expr.name elif decorator_expr.name == 'classproperty': - newnode.type = 'classmethod' + newnode._type = 'classmethod' frame.set_local(newnode.name, newnode) return newnode diff --git a/scoped_nodes.py b/scoped_nodes.py index 20bb664f..889baa0e 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -31,13 +31,13 @@ except ImportError: from cStringIO import StringIO as BytesIO from logilab.common.compat import builtins -from logilab.common.decorators import cached +from logilab.common.decorators import cached, cachedproperty from astroid.exceptions import NotFoundError, \ AstroidBuildingException, InferenceError from astroid.node_classes import Const, DelName, DelAttr, \ Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ - LookupMixIn, const_factory as cf, unpack_infer + LookupMixIn, const_factory as cf, unpack_infer, Name from astroid.bases import NodeNG, InferenceContext, Instance,\ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ BUILTINS @@ -476,6 +476,34 @@ else: """class representing a ListComp node""" # Function ################################################################### + +def _function_type(self): + """ + Function type, possible values are: + method, function, staticmethod, classmethod. + """ + # Can't infer that this node is decorated + # with a subclass of `classmethod` where `type` is first set, + # so do it here. + if self.decorators: + for node in self.decorators.nodes: + if not isinstance(node, Name): + continue + try: + for infered in node.infer(): + if not isinstance(infered, Class): + continue + for ancestor in infered.ancestors(): + if isinstance(ancestor, Class): + if (ancestor.name == 'classmethod' and + ancestor.root().name == BUILTINS): + return 'classmethod' + elif (ancestor.name == 'staticmethod' and + ancestor.root().name == BUILTINS): + return 'staticmethod' + except InferenceError: + pass + return self._type class Lambda(LocalsDictNodeNG, FilterStmtsMixin): @@ -539,6 +567,8 @@ class Function(Statement, Lambda): # attributes below are set by the builder module or by raw factories blockstart_tolineno = None decorators = None + _type = "function" + type = cachedproperty(_function_type) def __init__(self, name, doc): self.locals = {} diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py index 6c56c55f..4d0bc594 100644 --- a/test/unittest_scoped_nodes.py +++ b/test/unittest_scoped_nodes.py @@ -380,6 +380,37 @@ test() self.assertIsInstance(one, nodes.Const) self.assertEqual(one.value, 1) + def test_type_builtin_descriptor_subclasses(self): + astroid = abuilder.string_build(dedent(""" + class classonlymethod(classmethod): + pass + class staticonlymethod(staticmethod): + pass + + class Node: + @classonlymethod + def clsmethod_subclass(cls): + pass + @classmethod + def clsmethod(cls): + pass + @staticonlymethod + def staticmethod_subclass(cls): + pass + @staticmethod + def stcmethod(cls): + pass + """)) + node = astroid.locals['Node'][0] + self.assertEqual(node.locals['clsmethod_subclass'][0].type, + 'classmethod') + self.assertEqual(node.locals['clsmethod'][0].type, + 'classmethod') + self.assertEqual(node.locals['staticmethod_subclass'][0].type, + 'staticmethod') + self.assertEqual(node.locals['stcmethod'][0].type, + 'staticmethod') + class ClassNodeTC(TestCase): |
