diff options
author | Jake Lishman <jake@binhbar.com> | 2021-12-15 14:11:06 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-15 15:11:06 +0100 |
commit | 7b79386b64db66c38c2095c17fa7d6f7e6083892 (patch) | |
tree | 6a5ea86d5a3769bffe9bccece700808fe9dba31f | |
parent | fe547fbc47d9f8389260d38d4237b1b003722035 (diff) | |
download | pylint-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.txt | 2 | ||||
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | doc/whatsnew/2.13.rst | 5 | ||||
-rw-r--r-- | pylint/checkers/classes.py | 8 | ||||
-rw-r--r-- | tests/functional/a/assign/assigning_non_slot.py | 27 |
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 @@ -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" |