summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Lishman <jake@binhbar.com>2021-12-15 14:11:06 +0000
committerGitHub <noreply@github.com>2021-12-15 15:11:06 +0100
commit7b79386b64db66c38c2095c17fa7d6f7e6083892 (patch)
tree6a5ea86d5a3769bffe9bccece700808fe9dba31f
parentfe547fbc47d9f8389260d38d4237b1b003722035 (diff)
downloadpylint-git-7b79386b64db66c38c2095c17fa7d6f7e6083892.tar.gz
Fix assigning-non-slot false positive with setattr (#5457)
* Fix assigning-non-slot false positive with setattr Previously, if a class was slotted and overrode `__setattr__`, `assigning-non-slot` would be issued when assigning to attributes. With `__setattr__` defined, we cannot infer if it is an error to assign to an attribute, so we suppress the error. Fix #3793 Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.13.rst5
-rw-r--r--pylint/checkers/classes.py8
-rw-r--r--tests/functional/a/assign/assigning_non_slot.py27
5 files changed, 47 insertions, 0 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index f50f8e470..275225424 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -585,6 +585,8 @@ contributors:
* Felix von Drigalski (felixvd): contributor
+* Jake Lishman (jakelishman): contributor
+
* Philipp Albrecht (pylbrecht): contributor
* Allan Chandler (allanc65): contributor
diff --git a/ChangeLog b/ChangeLog
index c2f77182e..069868d98 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,11 @@ Release date: TBA
Closes #85, #2615
+* Fixed a false positive for ``assigning-non-slot`` when the slotted class
+ defined ``__setattr__``.
+
+ Closes #3793
+
* ``used-before-assignment`` now assumes that assignments in except blocks
may not have occurred and warns accordingly.
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index da595d225..31b6d4e36 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -51,6 +51,11 @@ Other Changes
Closes #85, #2615
+* Fix a false positive for ``assigning-non-slot`` when the slotted class
+ defined ``__setattr__``.
+
+ Closes #3793
+
* ``used-before-assignment`` now assumes that assignments in except blocks
may not have occurred and warns accordingly.
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 4bbc45f20..478197b0f 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -1487,6 +1487,14 @@ a metaclass class method.",
return
if "__slots__" not in klass.locals or not klass.newstyle:
return
+ # If `__setattr__` is defined on the class, then we can't reason about
+ # what will happen when assigning to an attribute.
+ if any(
+ base.locals.get("__setattr__")
+ for base in klass.mro()
+ if base.qname() != "builtins.object"
+ ):
+ return
# If 'typing.Generic' is a base of bases of klass, the cached version
# of 'slots()' might have been evaluated incorrectly, thus deleted cache entry.
diff --git a/tests/functional/a/assign/assigning_non_slot.py b/tests/functional/a/assign/assigning_non_slot.py
index cf673692f..2cd1483e0 100644
--- a/tests/functional/a/assign/assigning_non_slot.py
+++ b/tests/functional/a/assign/assigning_non_slot.py
@@ -173,3 +173,30 @@ class Cls(Generic[TYPE]):
def __init__(self, value):
self.value = value
+
+
+class ClassDefiningSetattr(object):
+ __slots__ = ["foobar"]
+
+ def __init__(self):
+ self.foobar = {}
+
+ def __setattr__(self, name, value):
+ if name == "foobar":
+ super().__setattr__(name, value)
+ else:
+ self.foobar[name] = value
+
+
+class ClassWithParentDefiningSetattr(ClassDefiningSetattr):
+ __slots__ = []
+
+
+def dont_emit_for_defined_setattr():
+ inst = ClassDefiningSetattr()
+ # This should not emit because we can't reason about what happens with
+ # classes defining __setattr__
+ inst.non_existent = "non-existent"
+
+ child = ClassWithParentDefiningSetattr()
+ child.non_existent = "non-existent"