Skip to content

Commit

Permalink
Merge pull request #5331 from yushao2/fix--false-negative-5323
Browse files Browse the repository at this point in the history
fix(consider-interating-dictionary): fix false negatives
  • Loading branch information
yushao2 committed Nov 30, 2021
2 parents 899e942 + 03bae75 commit 6caab13
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 4 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ Release date: 2021-11-24

Closes #4412 #5287

* Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables
or ``not in`` checks

Closes #5323

* Fix ``install graphiz`` message which isn't needed for puml output format.

* ``MessageTest`` of the unittest ``testutil`` now requires the ``confidence`` attribute
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Extensions
Other Changes
=============

* Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables
or ``not in`` checks

Closes #5323

* Require Python ``3.6.2`` to run pylint.

Closes #5065
9 changes: 5 additions & 4 deletions pylint/checkers/refactoring/recommendation_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,22 @@ def _check_consider_iterating_dictionary(self, node: nodes.Call) -> None:
return
if node.func.attrname != "keys":
return
comp_ancestor = utils.get_node_first_ancestor_of_type(node, (nodes.Compare,))
if (
isinstance(node.parent, (nodes.For, nodes.Comprehension))
or isinstance(node.parent, nodes.Compare)
or comp_ancestor
and any(
op
for op, comparator in node.parent.ops
if op == "in" and comparator is node
for op, comparator in comp_ancestor.ops
if op in {"in", "not in"}
and (comparator in node.node_ancestors() or comparator is node)
)
):
inferred = utils.safe_infer(node.func)
if not isinstance(inferred, astroid.BoundMethod) or not isinstance(
inferred.bound, nodes.Dict
):
return

self.add_message("consider-iterating-dictionary", node=node)

def _check_use_maxsplit_arg(self, node: nodes.Call) -> None:
Expand Down
13 changes: 13 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
Set,
Tuple,
Type,
TypeVar,
Union,
)

Expand Down Expand Up @@ -280,6 +281,8 @@
)
)

T_Node = TypeVar("T_Node", bound=nodes.NodeNG)


class NoSuchArgumentError(Exception):
pass
Expand Down Expand Up @@ -1690,3 +1693,13 @@ def returns_bool(node: nodes.NodeNG) -> bool:
and isinstance(node.value, nodes.Const)
and node.value.value in {True, False}
)


def get_node_first_ancestor_of_type(
node: nodes.NodeNG, ancestor_type: Tuple[Type[T_Node]]
) -> Optional[T_Node]:
"""Return the first parent node that is any of the provided types (or None)"""
for ancestor in node.node_ancestors():
if isinstance(ancestor, ancestor_type):
return ancestor
return None
26 changes: 26 additions & 0 deletions tests/functional/c/consider/consider_iterating_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,29 @@ def keys(self):
VAR = 1 in {}
VAR = 1 in dict()
VAR = [1, 2] == {}.keys() in {False}

# Additional membership checks
# See: https://github.com/PyCQA/pylint/issues/5323
metadata = {}
if "a" not in list(metadata.keys()): # [consider-iterating-dictionary]
print(1)
if "a" not in metadata.keys(): # [consider-iterating-dictionary]
print(1)
if "a" in list(metadata.keys()): # [consider-iterating-dictionary]
print(1)
if "a" in metadata.keys(): # [consider-iterating-dictionary]
print(1)


class AClass:
def a_function(self):
class InnerClass:
def another_function(self):
def inner_function():
another_metadata = {}
print("a" not in list(another_metadata.keys())) # [consider-iterating-dictionary]
print("a" not in another_metadata.keys()) # [consider-iterating-dictionary]
print("a" in list(another_metadata.keys())) # [consider-iterating-dictionary]
print("a" in another_metadata.keys()) # [consider-iterating-dictionary]
return inner_function()
return InnerClass().another_function()
8 changes: 8 additions & 0 deletions tests/functional/c/consider/consider_iterating_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ consider-iterating-dictionary:40:60:40:71::Consider iterating the dictionary dir
consider-iterating-dictionary:43:8:43:21::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:45:8:45:17::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:65:11:65:20::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:73:19:73:34::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:75:14:75:29::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:77:15:77:30::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:79:10:79:25::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:89:42:89:65:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:90:37:90:60:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:91:38:91:61:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
consider-iterating-dictionary:92:33:92:56:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED

0 comments on commit 6caab13

Please sign in to comment.