diff options
| -rw-r--r-- | ChangeLog | 4 | ||||
| -rw-r--r-- | astroid/scoped_nodes.py | 20 | ||||
| -rw-r--r-- | tests/unittest_inference.py | 21 |
3 files changed, 40 insertions, 5 deletions
@@ -6,6 +6,10 @@ What's New in astroid 2.4.0? ============================ Release Date: TBA +* Infer qualified ``classmethod`` as a classmethod. + + Close PyCQA/pylint#3417 + * Numpy `datetime64.astype` return value is inferred as a `ndarray`. Close PyCQA/pylint#3332 diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index ad727b13..0abf03d4 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -50,6 +50,9 @@ BUILTINS = builtins.__name__ ITER_METHODS = ("__iter__", "__getitem__") EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"}) objects = util.lazy_import("objects") +BUILTIN_DESCRIPTORS = frozenset( + {"classmethod", "staticmethod", "builtins.classmethod", "builtins.staticmethod"} +) def _c3_merge(sequences, cls, context): @@ -1423,17 +1426,17 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): return decorators @decorators_mod.cachedproperty - def type(self): # pylint: disable=invalid-overridden-method + def type( + self + ): # pylint: disable=invalid-overridden-method,too-many-return-statements """The function type for this node. Possible values are: method, function, staticmethod, classmethod. :type: str """ - builtin_descriptors = {"classmethod", "staticmethod"} - for decorator in self.extra_decorators: - if decorator.func.name in builtin_descriptors: + if decorator.func.name in BUILTIN_DESCRIPTORS: return decorator.func.name frame = self.parent.frame() @@ -1451,8 +1454,15 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): for node in self.decorators.nodes: if isinstance(node, node_classes.Name): - if node.name in builtin_descriptors: + if node.name in BUILTIN_DESCRIPTORS: return node.name + if ( + isinstance(node, node_classes.Attribute) + and isinstance(node.expr, node_classes.Name) + and node.expr.name == BUILTINS + and node.attrname in BUILTIN_DESCRIPTORS + ): + return node.attrname if isinstance(node, node_classes.Call): # Handle the following case: diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index 008e3d1f..0a0b5a66 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -5534,5 +5534,26 @@ def test_inferaugassign_picking_parent_instead_of_stmt(): assert inferred.name == "SomeClass" +def test_classmethod_from_builtins_inferred_as_bound(): + code = """ + import builtins + + class Foo(): + @classmethod + def bar1(cls, text): + pass + + @builtins.classmethod + def bar2(cls, text): + pass + + Foo.bar1 #@ + Foo.bar2 #@ + """ + first_node, second_node = extract_node(code) + assert isinstance(next(first_node.infer()), BoundMethod) + assert isinstance(next(second_node.infer()), BoundMethod) + + if __name__ == "__main__": unittest.main() |
