Skip to content

Commit

Permalink
Better inference of class and static methods decorated with custom me…
Browse files Browse the repository at this point in the history
…thods

Close pylint-dev/pylint#3209
  • Loading branch information
PCManticore committed Mar 3, 2020
1 parent 3eed22d commit 627886c
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 0 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Release Date: TBA

Close PyCQA/pylint#3414

* Better inference of class and static methods decorated with custom methods

Close PyCQA/pylint#3209

* Numpy `datetime64.astype` return value is inferred as a `ndarray`.

Close PyCQA/pylint#3332
Expand Down
15 changes: 15 additions & 0 deletions astroid/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,21 @@ def _infer_decorator_callchain(node):
return "classmethod"
if result.is_subtype_of("%s.staticmethod" % BUILTINS):
return "staticmethod"
if isinstance(result, FunctionDef):
if not result.decorators:
return None
# Determine if this function is decorated with one of the builtin descriptors we want.
for decorator in result.decorators.nodes:
if isinstance(decorator, node_classes.Name):
if decorator.name in BUILTIN_DESCRIPTORS:
return decorator.name
if (
isinstance(decorator, node_classes.Attribute)
and isinstance(decorator.expr, node_classes.Name)
and decorator.expr.name == BUILTINS
and decorator.attrname in BUILTIN_DESCRIPTORS
):
return decorator.attrname
return None


Expand Down
78 changes: 78 additions & 0 deletions tests/unittest_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5566,5 +5566,83 @@ def test_infer_dict_passes_context():
assert inferred.qname() == "builtins.dict"


@pytest.mark.parametrize(
"code,obj,obj_type",
[
(
"""
def klassmethod1(method):
@classmethod
def inner(cls):
return method(cls)
return inner
class X(object):
@klassmethod1
def x(cls):
return 'X'
X.x
""",
BoundMethod,
"classmethod",
),
(
"""
def staticmethod1(method):
@staticmethod
def inner(cls):
return method(cls)
return inner
class X(object):
@staticmethod1
def x(cls):
return 'X'
X.x
""",
nodes.FunctionDef,
"staticmethod",
),
(
"""
def klassmethod1(method):
def inner(cls):
return method(cls)
return classmethod(inner)
class X(object):
@klassmethod1
def x(cls):
return 'X'
X.x
""",
BoundMethod,
"classmethod",
),
(
"""
def staticmethod1(method):
def inner(cls):
return method(cls)
return staticmethod(inner)
class X(object):
@staticmethod1
def x(cls):
return 'X'
X.x
""",
nodes.FunctionDef,
"staticmethod",
),
],
)
def test_custom_decorators_for_classmethod_and_staticmethods(code, obj, obj_type):
node = extract_node(code)
inferred = next(node.infer())
assert isinstance(inferred, obj)
assert inferred.type == obj_type


if __name__ == "__main__":
unittest.main()

0 comments on commit 627886c

Please sign in to comment.