summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Marek <tmarek@google.com>2013-11-03 09:50:19 -0800
committerTorsten Marek <tmarek@google.com>2013-11-03 09:50:19 -0800
commit1aace548e776880a1a27802cbbbfeba93ec4bf47 (patch)
tree884c561cab60952ecc2f134d3bf1336ae836046e
parent733e49e853db89dbb5cb8434de9d2a571e7c1b04 (diff)
downloadastroid-1aace548e776880a1a27802cbbbfeba93ec4bf47.tar.gz
Add support for inferring the arguments to namedtuple invocations, to support cases like this:
fields = ['a', 'b', 'c'] A = collections.namedtuple('A', fields)
-rw-r--r--ChangeLog2
-rw-r--r--brain/py2stdlib.py22
-rw-r--r--test/unittest_brain.py25
3 files changed, 44 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 3328549..4df1b16 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,8 @@ Change log for the astroid package (used to be astng)
=====================================================
--
+ * Add support for inferring arguments to namedtuple invocations.
+
* Make sure that objects returned for namedtuple
inference have parents.
diff --git a/brain/py2stdlib.py b/brain/py2stdlib.py
index 31a2477..27507ec 100644
--- a/brain/py2stdlib.py
+++ b/brain/py2stdlib.py
@@ -5,7 +5,8 @@ Currently help understanding of :
* hashlib.md5 and hashlib.sha1
"""
-from astroid import MANAGER, AsStringRegexpPredicate, UseInferenceDefault, inference_tip
+from astroid import MANAGER, AsStringRegexpPredicate, UseInferenceDefault, inference_tip, YES
+from astroid import exceptions
from astroid import nodes
from astroid.builder import AstroidBuilder
@@ -183,6 +184,16 @@ MODULE_TRANSFORMS['subprocess'] = subprocess_transform
def infer_named_tuple(node, context=None):
"""Specific inference function for namedtuple CallFunc node"""
+ def infer_first(node):
+ try:
+ value = node.infer().next()
+ if value is YES:
+ raise UseInferenceDefault()
+ else:
+ return value
+ except StopIteration:
+ raise InferenceError()
+
# node is a CallFunc node, class name as first argument and generated class
# attributes as second argument
if len(node.args) != 2:
@@ -191,12 +202,13 @@ def infer_named_tuple(node, context=None):
# namedtuple list of attributes can be a list of strings or a
# whitespace-separate string
try:
- name = node.args[0].value
+ name = infer_first(node.args[0]).value
+ names = infer_first(node.args[1])
try:
- attributes = node.args[1].value.split()
+ attributes = names.value.split()
except AttributeError:
- attributes = [const.value for const in node.args[1].elts]
- except AttributeError:
+ attributes = [infer_first(const).value for const in names.elts]
+ except (AttributeError, exceptions.InferenceError):
raise UseInferenceDefault()
# we want to return a Class node instance with proper attributes set
class_node = nodes.Class(name, 'docstring')
diff --git a/test/unittest_brain.py b/test/unittest_brain.py
index dac25e6..7b750cf 100644
--- a/test/unittest_brain.py
+++ b/test/unittest_brain.py
@@ -19,6 +19,7 @@
from logilab.common.testlib import TestCase, unittest_main
from astroid import MANAGER
+from astroid import bases
from astroid import test_utils
import astroid
@@ -52,5 +53,29 @@ class NamedTupleTest(TestCase):
for anc in klass.ancestors():
self.assertFalse(anc.parent is None)
+ def test_namedtuple_inference(self):
+ klass = test_utils.extract_node("""
+ from collections import namedtuple
+
+ name = "X"
+ fields = ["a", "b", "c"]
+ class X(namedtuple(name, fields)):
+ pass
+ """)
+ for base in klass.ancestors():
+ if base.name == 'X':
+ break
+ self.assertItemsEqual(["a", "b", "c"], base.instance_attrs.keys())
+
+ def test_namedtuple_inference_failure(self):
+ klass = test_utils.extract_node("""
+ from collections import namedtuple
+
+ def foo(fields):
+ return __(namedtuple("foo", fields))
+ """)
+ self.assertIs(bases.YES, klass.infer().next())
+
+
if __name__ == '__main__':
unittest_main()