diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-04-13 21:27:14 +0300 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-04-13 21:27:14 +0300 |
commit | b32aab809b265b536805242b7ecdb9c785edfaae (patch) | |
tree | d7335628ade0fe7762ac44a32bdbb0beae6d506c | |
parent | 12e750e6308896e8b81b7e63aeacbeb051fa1d67 (diff) | |
download | astroid-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-- | ChangeLog | 4 | ||||
-rw-r--r-- | astroid/scoped_nodes.py | 57 | ||||
-rw-r--r-- | astroid/tests/unittest_scoped_nodes.py | 15 |
3 files changed, 57 insertions, 19 deletions
@@ -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() |