Skip to content

Commit 1baa10e

Browse files
authored
class attrs should not emit assigning-non-slot msg (#7987)
1 parent b15c502 commit 1baa10e

File tree

6 files changed

+71
-15
lines changed

6 files changed

+71
-15
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix false positive ``assigning-non-slot`` when a class attribute is re-assigned.
2+
3+
Closes #6001

pylint/checkers/classes/class_checker.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,10 @@ def _check_in_slots(self, node: nodes.AssignAttr) -> None:
16301630
# Properties circumvent the slots mechanism,
16311631
# so we should not emit a warning for them.
16321632
return
1633+
if node.attrname != "__class__" and utils.is_class_attr(
1634+
node.attrname, klass
1635+
):
1636+
return
16331637
if node.attrname in klass.locals:
16341638
for local_name in klass.locals.get(node.attrname):
16351639
statement = local_name.statement(future=True)
@@ -1645,7 +1649,12 @@ def _check_in_slots(self, node: nodes.AssignAttr) -> None:
16451649
slots, node.parent.value
16461650
):
16471651
return
1648-
self.add_message("assigning-non-slot", args=(node.attrname,), node=node)
1652+
self.add_message(
1653+
"assigning-non-slot",
1654+
args=(node.attrname,),
1655+
node=node,
1656+
confidence=INFERENCE,
1657+
)
16491658

16501659
@only_required_for_messages(
16511660
"protected-access", "no-classmethod-decorator", "no-staticmethod-decorator"
@@ -1780,7 +1789,7 @@ def _check_protected_attribute_access(
17801789
if (
17811790
self._is_classmethod(node.frame(future=True))
17821791
and self._is_inferred_instance(node.expr, klass)
1783-
and self._is_class_attribute(attrname, klass)
1792+
and self._is_class_or_instance_attribute(attrname, klass)
17841793
):
17851794
return
17861795

@@ -1827,19 +1836,16 @@ def _is_inferred_instance(expr: nodes.NodeNG, klass: nodes.ClassDef) -> bool:
18271836
return inferred._proxied is klass
18281837

18291838
@staticmethod
1830-
def _is_class_attribute(name: str, klass: nodes.ClassDef) -> bool:
1839+
def _is_class_or_instance_attribute(name: str, klass: nodes.ClassDef) -> bool:
18311840
"""Check if the given attribute *name* is a class or instance member of the
18321841
given *klass*.
18331842
18341843
Returns ``True`` if the name is a property in the given klass,
18351844
``False`` otherwise.
18361845
"""
18371846

1838-
try:
1839-
klass.getattr(name)
1847+
if utils.is_class_attr(name, klass):
18401848
return True
1841-
except astroid.NotFoundError:
1842-
pass
18431849

18441850
try:
18451851
klass.instance_attr(name)

pylint/checkers/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,3 +2190,11 @@ def is_terminating_func(node: nodes.Call) -> bool:
21902190
pass
21912191

21922192
return False
2193+
2194+
2195+
def is_class_attr(name: str, klass: nodes.ClassDef) -> bool:
2196+
try:
2197+
klass.getattr(name)
2198+
return True
2199+
except astroid.NotFoundError:
2200+
return False

tests/functional/a/assigning/assigning_non_slot.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
will trigger assigning-non-slot warning.
33
"""
44
# pylint: disable=too-few-public-methods, missing-docstring, import-error, redundant-u-string-prefix, unnecessary-dunder-call
5+
# pylint: disable=attribute-defined-outside-init
6+
57
from collections import deque
68

79
from missing import Unknown
@@ -129,7 +131,7 @@ def dont_emit_for_descriptors():
129131
# This should not emit, because attr is
130132
# a data descriptor
131133
inst.data_descriptor = 'foo'
132-
inst.non_data_descriptor = 'lala' # [assigning-non-slot]
134+
inst.non_data_descriptor = 'lala'
133135

134136

135137
class ClassWithSlots:
@@ -147,7 +149,8 @@ class ClassReassingingInvalidLayoutClass:
147149
__slots__ = []
148150

149151
def release(self):
150-
self.__class__ = ClassWithSlots # [assigning-non-slot]
152+
self.__class__ = ClassWithSlots # [assigning-non-slot]
153+
self.test = 'test' # [assigning-non-slot]
151154

152155

153156
# pylint: disable=attribute-defined-outside-init
@@ -200,3 +203,39 @@ def dont_emit_for_defined_setattr():
200203

201204
child = ClassWithParentDefiningSetattr()
202205
child.non_existent = "non-existent"
206+
207+
class ColorCls:
208+
__slots__ = ()
209+
COLOR = "red"
210+
211+
212+
class Child(ColorCls):
213+
__slots__ = ()
214+
215+
216+
repro = Child()
217+
Child.COLOR = "blue"
218+
219+
class MyDescriptor:
220+
"""Basic descriptor."""
221+
222+
def __get__(self, instance, owner):
223+
return 42
224+
225+
def __set__(self, instance, value):
226+
pass
227+
228+
229+
# Regression test from https://github.com/PyCQA/pylint/issues/6001
230+
class Base:
231+
__slots__ = ()
232+
233+
attr2 = MyDescriptor()
234+
235+
236+
class Repro(Base):
237+
__slots__ = ()
238+
239+
240+
repro = Repro()
241+
repro.attr2 = "anything"
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
assigning-non-slot:18:8:18:20:Bad.__init__:Assigning to attribute 'missing' not defined in class slots:UNDEFINED
2-
assigning-non-slot:26:8:26:20:Bad2.__init__:Assigning to attribute 'missing' not defined in class slots:UNDEFINED
3-
assigning-non-slot:36:8:36:20:Bad3.__init__:Assigning to attribute 'missing' not defined in class slots:UNDEFINED
4-
assigning-non-slot:132:4:132:28:dont_emit_for_descriptors:Assigning to attribute 'non_data_descriptor' not defined in class slots:UNDEFINED
5-
assigning-non-slot:150:8:150:22:ClassReassingingInvalidLayoutClass.release:Assigning to attribute '__class__' not defined in class slots:UNDEFINED
1+
assigning-non-slot:20:8:20:20:Bad.__init__:Assigning to attribute 'missing' not defined in class slots:INFERENCE
2+
assigning-non-slot:28:8:28:20:Bad2.__init__:Assigning to attribute 'missing' not defined in class slots:INFERENCE
3+
assigning-non-slot:38:8:38:20:Bad3.__init__:Assigning to attribute 'missing' not defined in class slots:INFERENCE
4+
assigning-non-slot:152:8:152:22:ClassReassingingInvalidLayoutClass.release:Assigning to attribute '__class__' not defined in class slots:INFERENCE
5+
assigning-non-slot:153:8:153:17:ClassReassingingInvalidLayoutClass.release:Assigning to attribute 'test' not defined in class slots:INFERENCE
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
assigning-non-slot:18:8:18:17:Foo.__init__:Assigning to attribute '_bar' not defined in class slots:UNDEFINED
1+
assigning-non-slot:18:8:18:17:Foo.__init__:Assigning to attribute '_bar' not defined in class slots:INFERENCE

0 commit comments

Comments
 (0)