Skip to content

Commit

Permalink
Add use-set-for-membership check
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed Aug 19, 2021
1 parent d8069dd commit ab6dd5d
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 0 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Expand Up @@ -147,6 +147,11 @@ Release date: TBA

* Emit ``consider-using-tuple`` even if list contains a ``starred`` expression.

* Added ``use-set-for-membership``: Emitted when using an in-place defined ``list``
or ``tuple`` to do a membership test. ``sets`` are better optimized for that.

Closes #4776

* Ignore decorators lines by similarities checker when ignore signatures flag enabled

Closes #4839
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.10.rst
Expand Up @@ -44,6 +44,11 @@ Extensions

* Emit ``consider-using-tuple`` even if list contains a ``starred`` expression.

* Added ``use-set-for-membership``: Emitted when using an in-place defined ``list``
or ``tuple`` to do a membership test. ``sets`` are better optimized for that.

Closes #4776


Other Changes
=============
Expand Down
20 changes: 20 additions & 0 deletions pylint/extensions/code_style.py
Expand Up @@ -41,6 +41,12 @@ class CodeStyleChecker(BaseChecker):
"Emitted where an in-place defined ``list`` can be replaced by a ``tuple``. "
"Due to optimizations by CPython, there is no performance benefit from it.",
),
"R6103": (
"Consider using set for membership test",
"use-set-for-membership",
"Membership tests are more efficient when performed on "
"a lookup optimized datatype like ``sets``.",
),
}

def __init__(self, linter: PyLinter) -> None:
Expand All @@ -59,6 +65,12 @@ def visit_for(self, node: nodes.For) -> None:
def visit_comprehension(self, node: nodes.Comprehension) -> None:
self._check_inplace_defined_list(node)

@check_messages("use-set-for-membership")
def visit_compare(self, node: nodes.Compare) -> None:
for op, comparator in node.ops:
if op == "in":
self._check_in_comparison(comparator)

def _check_dict_consider_namedtuple_dataclass(self, node: nodes.Dict) -> None:
"""Check if dictionary values can be replaced by Namedtuple or Dataclass."""
if not (
Expand Down Expand Up @@ -140,6 +152,14 @@ def _check_inplace_defined_list(
if isinstance(node.iter, nodes.List):
self.add_message("consider-using-tuple", node=node.iter)

def _check_in_comparison(self, comparator: nodes.NodeNG) -> None:
"""Checks for membership comparisons with in-place container objects."""
if not isinstance(comparator, nodes._BaseContainer):
return

if not isinstance(comparator, nodes.Set):
self.add_message("use-set-for-membership", node=comparator)


def register(linter: PyLinter) -> None:
linter.register_checker(CodeStyleChecker(linter))
33 changes: 33 additions & 0 deletions tests/functional/ext/code_style/code_style_use_set_membership.py
@@ -0,0 +1,33 @@
# pylint: disable=invalid-name,missing-docstring,pointless-statement,unnecessary-comprehension

x = 1
var = frozenset({1, 2, 3})

x in var
x in {1, 2, 3}
x in (1, 2, 3) # [use-set-for-membership]
x in [1, 2, 3] # [use-set-for-membership]

if x in var:
pass
if x in {1, 2, 3}:
pass
if x in (1, 2, 3): # [use-set-for-membership]
pass
if x in [1, 2, 3]: # [use-set-for-membership]
pass

x in {*var}
x in (*var,) # [use-set-for-membership]
x in [*var] # [use-set-for-membership]

42 if x in [1, 2, 3] else None # [use-set-for-membership]
assert x in [1, 2, 3] # [use-set-for-membership]
(x for x in var if x in [1, 2, 3]) # [use-set-for-membership]
while x in [1, 2, 3]: # [use-set-for-membership]
break

# Stacked operators, rightmost pair is evaluated first
# Doesn't make much sense in practice since `in` will only return `bool`
True == x in [1, 2, 3] # [use-set-for-membership] # noqa: E712
1 >= x in [1, 2, 3] # [use-set-for-membership] # noqa: E712
@@ -0,0 +1,2 @@
[MASTER]
load-plugins=pylint.extensions.code_style
12 changes: 12 additions & 0 deletions tests/functional/ext/code_style/code_style_use_set_membership.txt
@@ -0,0 +1,12 @@
use-set-for-membership:8:5::Consider using set for membership test:HIGH
use-set-for-membership:9:5::Consider using set for membership test:HIGH
use-set-for-membership:15:8::Consider using set for membership test:HIGH
use-set-for-membership:17:8::Consider using set for membership test:HIGH
use-set-for-membership:21:5::Consider using set for membership test:HIGH
use-set-for-membership:22:5::Consider using set for membership test:HIGH
use-set-for-membership:24:11::Consider using set for membership test:HIGH
use-set-for-membership:25:12::Consider using set for membership test:HIGH
use-set-for-membership:26:24::Consider using set for membership test:HIGH
use-set-for-membership:27:11::Consider using set for membership test:HIGH
use-set-for-membership:32:13::Consider using set for membership test:HIGH
use-set-for-membership:33:10::Consider using set for membership test:HIGH

0 comments on commit ab6dd5d

Please sign in to comment.