summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-04-13 21:27:14 +0300
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-04-13 21:27:14 +0300
commitb32aab809b265b536805242b7ecdb9c785edfaae (patch)
treed7335628ade0fe7762ac44a32bdbb0beae6d506c
parent12e750e6308896e8b81b7e63aeacbeb051fa1d67 (diff)
downloadastroid-b32aab809b265b536805242b7ecdb9c785edfaae.tar.gz
Understand partially the 3-argument form of `type`.
The only change is that astroid understands members passed in as dictionaries as the third argument. This improves the understanding of classes generated on-the-fly, using the type function.
-rw-r--r--ChangeLog4
-rw-r--r--astroid/scoped_nodes.py57
-rw-r--r--astroid/tests/unittest_scoped_nodes.py15
3 files changed, 57 insertions, 19 deletions
diff --git a/ChangeLog b/ChangeLog
index f4994be..1115b54 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -25,6 +25,10 @@ Change log for the astroid package (used to be astng)
from a context object, leading to many no-member errors
in Pylint.
+ * Understand partially the 3-argument form of `type`.
+ The only change is that astroid understands members
+ passed in as dictionaries as the third argument.
+
2015-03-14 -- 1.3.6
* Class.slots raises NotImplementedError for old style classes.
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index be2ff5c..553ad65 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -1015,27 +1015,46 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
if anc.qname() == type_name:
return True
+ def _infer_type_call(self, caller, context):
+ name_node = next(caller.args[0].infer(context))
+ if (isinstance(name_node, Const) and
+ isinstance(name_node.value, six.string_types)):
+ name = name_node.value
+ else:
+ return YES
+
+ result = Class(name, None)
+
+ # Get the bases of the class.
+ bases = next(caller.args[1].infer(context))
+ if isinstance(bases, (Tuple, List)):
+ result.bases = bases.itered()
+ else:
+ # There is currently no AST node that can represent an 'unknown'
+ # node (YES is not an AST node), therefore we simply return YES here
+ # although we know at least the name of the class.
+ return YES
+
+ # Get the members of the class
+ try:
+ members = next(caller.args[2].infer(context))
+ except InferenceError:
+ members = None
+
+ if members and isinstance(members, Dict):
+ for attr, value in members.items:
+ if (isinstance(attr, Const) and
+ isinstance(attr.value, six.string_types)):
+ result.locals[attr.value] = [value]
+
+ result.parent = caller.parent
+ return result
+
def infer_call_result(self, caller, context=None):
"""infer what a class is returning when called"""
- if self.is_subtype_of('%s.type' % (BUILTINS,), context) and len(caller.args) == 3:
- name_node = next(caller.args[0].infer(context))
- if (isinstance(name_node, Const) and
- isinstance(name_node.value, six.string_types)):
- name = name_node.value
- else:
- yield YES
- return
- result = Class(name, None)
- bases = next(caller.args[1].infer(context))
- if isinstance(bases, (Tuple, List)):
- result.bases = bases.itered()
- else:
- # There is currently no AST node that can represent an 'unknown'
- # node (YES is not an AST node), therefore we simply return YES here
- # although we know at least the name of the class.
- yield YES
- return
- result.parent = caller.parent
+ if (self.is_subtype_of('%s.type' % (BUILTINS,), context)
+ and len(caller.args) == 3):
+ result = self._infer_type_call(caller, context)
yield result
else:
yield Instance(self)
diff --git a/astroid/tests/unittest_scoped_nodes.py b/astroid/tests/unittest_scoped_nodes.py
index 7430c11..ba750c2 100644
--- a/astroid/tests/unittest_scoped_nodes.py
+++ b/astroid/tests/unittest_scoped_nodes.py
@@ -1242,6 +1242,21 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
self.assertIsInstance(result, Generator)
self.assertEqual(result.parent, func)
+ def test_type_three_arguments(self):
+ classes = test_utils.extract_node("""
+ type('A', (object, ), {"a": 1, "b": 2, missing: 3}) #@
+ """)
+ first = next(classes.infer())
+ self.assertIsInstance(first, nodes.Class)
+ self.assertEqual(first.name, "A")
+ self.assertEqual(first.basenames, ["object"])
+ self.assertIsInstance(first["a"], nodes.Const)
+ self.assertEqual(first["a"].value, 1)
+ self.assertIsInstance(first["b"], nodes.Const)
+ self.assertEqual(first["b"].value, 2)
+ with self.assertRaises(NotFoundError):
+ first.getattr("missing")
+
if __name__ == '__main__':
unittest.main()