summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--astroid/scoped_nodes.py20
-rw-r--r--tests/unittest_inference.py21
3 files changed, 40 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 1ca51f73..0580a146 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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()