Skip to content

Commit

Permalink
Fix a crash in the unsupported-membership-test checker involving …
Browse files Browse the repository at this point in the history
…tuple unpacking (#6367)

An assumption of direct parentage between `AssignName` and `Assign` was violated by tuple unpacking.
  • Loading branch information
jacobtylerwalls authored and Pierre-Sassoulas committed Apr 20, 2022
1 parent 664049c commit 7231dd5
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 7 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ What's New in Pylint 2.13.6?
============================
Release date: TBA

* Fix a crash in the ``unsupported-membership-test`` checker when assigning
multiple constants to class attributes including ``__iter__`` via unpacking.

Closes #6366

* Asterisks are no longer required in Sphinx and Google style parameter documentation
for ``missing-param-doc`` and are parsed correctly.

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 @@ -587,6 +587,11 @@ Other Changes

Closes #5803

* Fix a crash in the ``unsupported-membership-test`` checker when assigning
multiple constants to class attributes including ``__iter__`` via unpacking.

Closes #6366

* Fix false positive for ``used-before-assignment`` for assignments taking place via
nonlocal declarations after an earlier type annotation.

Expand Down
13 changes: 12 additions & 1 deletion pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1051,8 +1051,19 @@ def _supports_protocol_method(value: nodes.NodeNG, attr: str) -> bool:
return False

first = attributes[0]

# Return False if a constant is assigned
if isinstance(first, nodes.AssignName):
if isinstance(first.parent.value, nodes.Const):
this_assign_parent = get_node_first_ancestor_of_type(
first, (nodes.Assign, nodes.NamedExpr)
)
if this_assign_parent is None: # pragma: no cover
# Cannot imagine this being None, but return True to avoid false positives
return True
if isinstance(this_assign_parent.value, nodes.BaseContainer):
if all(isinstance(n, nodes.Const) for n in this_assign_parent.value.elts):
return False
if isinstance(this_assign_parent.value, nodes.Const):
return False
return True

Expand Down
11 changes: 11 additions & 0 deletions tests/functional/n/none_dunder_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,21 @@ class NonContainerClass(metaclass=MetaContainer):
__iter__ = None


class MultipleAssignmentNonesClass(metaclass=MetaContainer):
__len__, __iter__ = [None, None]


class MultipleAssignmentLambdasClass(metaclass=MetaContainer):
"""https://github.com/PyCQA/pylint/issues/6366"""
__len__, __iter__ = [lambda x: x] * 2


def test():
1 in NonIterableClass # [unsupported-membership-test]
1 in OldNonIterableClass # [unsupported-membership-test]
1 in NonContainerClass # [unsupported-membership-test]
1 in NonIterableClass() # [unsupported-membership-test]
1 in OldNonIterableClass() # [unsupported-membership-test]
1 in NonContainerClass() # [unsupported-membership-test]
1 in MultipleAssignmentNonesClass() # [unsupported-membership-test]
1 in MultipleAssignmentLambdasClass()
13 changes: 7 additions & 6 deletions tests/functional/n/none_dunder_protocols.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
unsupported-membership-test:29:9:29:25:test:Value 'NonIterableClass' doesn't support membership test:UNDEFINED
unsupported-membership-test:30:9:30:28:test:Value 'OldNonIterableClass' doesn't support membership test:UNDEFINED
unsupported-membership-test:31:9:31:26:test:Value 'NonContainerClass' doesn't support membership test:UNDEFINED
unsupported-membership-test:32:9:32:27:test:Value 'NonIterableClass()' doesn't support membership test:UNDEFINED
unsupported-membership-test:33:9:33:30:test:Value 'OldNonIterableClass()' doesn't support membership test:UNDEFINED
unsupported-membership-test:34:9:34:28:test:Value 'NonContainerClass()' doesn't support membership test:UNDEFINED
unsupported-membership-test:38:9:38:25:test:Value 'NonIterableClass' doesn't support membership test:UNDEFINED
unsupported-membership-test:39:9:39:28:test:Value 'OldNonIterableClass' doesn't support membership test:UNDEFINED
unsupported-membership-test:40:9:40:26:test:Value 'NonContainerClass' doesn't support membership test:UNDEFINED
unsupported-membership-test:41:9:41:27:test:Value 'NonIterableClass()' doesn't support membership test:UNDEFINED
unsupported-membership-test:42:9:42:30:test:Value 'OldNonIterableClass()' doesn't support membership test:UNDEFINED
unsupported-membership-test:43:9:43:28:test:Value 'NonContainerClass()' doesn't support membership test:UNDEFINED
unsupported-membership-test:44:9:44:39:test:Value 'MultipleAssignmentNonesClass()' doesn't support membership test:UNDEFINED
12 changes: 12 additions & 0 deletions tests/functional/n/none_dunder_protocols_py38.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# pylint: disable=missing-docstring,too-few-public-methods,expression-not-assigned
class MetaContainer(type):
__contains__ = None


class NamedExpressionClass(metaclass=MetaContainer):
if (__iter__ := lambda x: x):
pass


def test():
1 in NamedExpressionClass()
2 changes: 2 additions & 0 deletions tests/functional/n/none_dunder_protocols_py38.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.8

0 comments on commit 7231dd5

Please sign in to comment.