diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2015-11-04 12:48:16 +0200 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2015-11-04 12:48:16 +0200 |
commit | 4a871042e457bbbe1f6290dcfe38c1d771385cde (patch) | |
tree | 49252f2c45fe8f70af7abab31e19906b1fa4df4d | |
parent | f5c3e703ec3de5d6e4f50583475d7b18fb0d6beb (diff) | |
parent | 6ae9890de48b18ea7f4a2cad8a5cb6dd44eab155 (diff) | |
download | astroid-4a871042e457bbbe1f6290dcfe38c1d771385cde.tar.gz |
Merge bookmark heads.
-rw-r--r-- | astroid/arguments.py | 10 | ||||
-rw-r--r-- | astroid/protocols.py | 10 | ||||
-rw-r--r-- | astroid/tests/unittest_inference.py | 42 |
3 files changed, 59 insertions, 3 deletions
diff --git a/astroid/arguments.py b/astroid/arguments.py index 5670fa8..e80f5f4 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -183,6 +183,16 @@ class CallSite(object): else: # XXX can do better ? boundnode = funcnode.parent.frame() + + if isinstance(boundnode, nodes.ClassDef): + # Verify that we're accessing a method + # of the metaclass through a class, as in + # `cls.metaclass_method`. In this case, the + # first argument is always the class. + method_scope = funcnode.parent.scope() + if method_scope is boundnode.metaclass(): + return iter((boundnode, )) + if funcnode.type == 'method': if not isinstance(boundnode, bases.Instance): boundnode = bases.Instance(boundnode) diff --git a/astroid/protocols.py b/astroid/protocols.py index 7c9e4b9..28b1dd2 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -280,12 +280,16 @@ def _arguments_infer_argname(self, name, context): # first argument of instance/class method if self.args and getattr(self.args[0], 'name', None) == name: functype = self.parent.type + cls = self.parent.parent.scope() + is_metaclass = isinstance(cls, nodes.ClassDef) and cls.type == 'metaclass' + # If this is a metaclass, then the first argument will always + # be the class, not an instance. + if is_metaclass or functype == 'classmethod': + yield cls + return if functype == 'method': yield bases.Instance(self.parent.parent.frame()) return - if functype == 'classmethod': - yield self.parent.parent.frame() - return if context and context.callcontext: call_site = arguments.CallSite(context.callcontext) diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py index fa090f8..0f45d3a 100644 --- a/astroid/tests/unittest_inference.py +++ b/astroid/tests/unittest_inference.py @@ -3143,6 +3143,48 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertIsInstance(third_c, Instance) self.assertEqual(third_c.name, 'A') + def test_metaclass_subclasses_arguments_are_classes_not_instances(self): + ast_node = test_utils.extract_node(''' + class A(type): + def test(cls): + return cls + import six + @six.add_metaclass(A) + class B(object): + pass + + B.test() #@ + ''') + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, nodes.ClassDef) + self.assertEqual(inferred.name, 'B') + + def test_infer_cls_in_class_methods(self): + ast_nodes = test_utils.extract_node(''' + class A(type): + def __call__(cls): + cls #@ + class B(object): + def __call__(cls): + cls #@ + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, nodes.ClassDef) + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, Instance) + + @unittest.expectedFailure + def test_metaclass_arguments_are_classes_not_instances(self): + ast_node = test_utils.extract_node(''' + class A(type): + def test(cls): return cls + A.test() #@ + ''') + # This is not supported yet + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, ClassDef) + self.assertEqual(inferred.name, 'A') + class GetattrTest(unittest.TestCase): |