Skip to content

Commit

Permalink
class attrs should not emit assigning-non-slot msg (#7987)
Browse files Browse the repository at this point in the history
  • Loading branch information
clavedeluna authored Dec 30, 2022
1 parent b15c502 commit 1baa10e
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 15 deletions.
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/6001.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix false positive ``assigning-non-slot`` when a class attribute is re-assigned.

Closes #6001
20 changes: 13 additions & 7 deletions pylint/checkers/classes/class_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,10 @@ def _check_in_slots(self, node: nodes.AssignAttr) -> None:
# Properties circumvent the slots mechanism,
# so we should not emit a warning for them.
return
if node.attrname != "__class__" and utils.is_class_attr(
node.attrname, klass
):
return
if node.attrname in klass.locals:
for local_name in klass.locals.get(node.attrname):
statement = local_name.statement(future=True)
Expand All @@ -1645,7 +1649,12 @@ def _check_in_slots(self, node: nodes.AssignAttr) -> None:
slots, node.parent.value
):
return
self.add_message("assigning-non-slot", args=(node.attrname,), node=node)
self.add_message(
"assigning-non-slot",
args=(node.attrname,),
node=node,
confidence=INFERENCE,
)

@only_required_for_messages(
"protected-access", "no-classmethod-decorator", "no-staticmethod-decorator"
Expand Down Expand Up @@ -1780,7 +1789,7 @@ def _check_protected_attribute_access(
if (
self._is_classmethod(node.frame(future=True))
and self._is_inferred_instance(node.expr, klass)
and self._is_class_attribute(attrname, klass)
and self._is_class_or_instance_attribute(attrname, klass)
):
return

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

@staticmethod
def _is_class_attribute(name: str, klass: nodes.ClassDef) -> bool:
def _is_class_or_instance_attribute(name: str, klass: nodes.ClassDef) -> bool:
"""Check if the given attribute *name* is a class or instance member of the
given *klass*.
Returns ``True`` if the name is a property in the given klass,
``False`` otherwise.
"""

try:
klass.getattr(name)
if utils.is_class_attr(name, klass):
return True
except astroid.NotFoundError:
pass

try:
klass.instance_attr(name)
Expand Down
8 changes: 8 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2190,3 +2190,11 @@ def is_terminating_func(node: nodes.Call) -> bool:
pass

return False


def is_class_attr(name: str, klass: nodes.ClassDef) -> bool:
try:
klass.getattr(name)
return True
except astroid.NotFoundError:
return False
43 changes: 41 additions & 2 deletions tests/functional/a/assigning/assigning_non_slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
will trigger assigning-non-slot warning.
"""
# pylint: disable=too-few-public-methods, missing-docstring, import-error, redundant-u-string-prefix, unnecessary-dunder-call
# pylint: disable=attribute-defined-outside-init

from collections import deque

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


class ClassWithSlots:
Expand All @@ -147,7 +149,8 @@ class ClassReassingingInvalidLayoutClass:
__slots__ = []

def release(self):
self.__class__ = ClassWithSlots # [assigning-non-slot]
self.__class__ = ClassWithSlots # [assigning-non-slot]
self.test = 'test' # [assigning-non-slot]


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

child = ClassWithParentDefiningSetattr()
child.non_existent = "non-existent"

class ColorCls:
__slots__ = ()
COLOR = "red"


class Child(ColorCls):
__slots__ = ()


repro = Child()
Child.COLOR = "blue"

class MyDescriptor:
"""Basic descriptor."""

def __get__(self, instance, owner):
return 42

def __set__(self, instance, value):
pass


# Regression test from https://github.com/PyCQA/pylint/issues/6001
class Base:
__slots__ = ()

attr2 = MyDescriptor()


class Repro(Base):
__slots__ = ()


repro = Repro()
repro.attr2 = "anything"
10 changes: 5 additions & 5 deletions tests/functional/a/assigning/assigning_non_slot.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
assigning-non-slot:18:8:18:20:Bad.__init__:Assigning to attribute 'missing' not defined in class slots:UNDEFINED
assigning-non-slot:26:8:26:20:Bad2.__init__:Assigning to attribute 'missing' not defined in class slots:UNDEFINED
assigning-non-slot:36:8:36:20:Bad3.__init__:Assigning to attribute 'missing' not defined in class slots:UNDEFINED
assigning-non-slot:132:4:132:28:dont_emit_for_descriptors:Assigning to attribute 'non_data_descriptor' not defined in class slots:UNDEFINED
assigning-non-slot:150:8:150:22:ClassReassingingInvalidLayoutClass.release:Assigning to attribute '__class__' not defined in class slots:UNDEFINED
assigning-non-slot:20:8:20:20:Bad.__init__:Assigning to attribute 'missing' not defined in class slots:INFERENCE
assigning-non-slot:28:8:28:20:Bad2.__init__:Assigning to attribute 'missing' not defined in class slots:INFERENCE
assigning-non-slot:38:8:38:20:Bad3.__init__:Assigning to attribute 'missing' not defined in class slots:INFERENCE
assigning-non-slot:152:8:152:22:ClassReassingingInvalidLayoutClass.release:Assigning to attribute '__class__' not defined in class slots:INFERENCE
assigning-non-slot:153:8:153:17:ClassReassingingInvalidLayoutClass.release:Assigning to attribute 'test' not defined in class slots:INFERENCE
2 changes: 1 addition & 1 deletion tests/functional/a/assigning/assigning_non_slot_4509.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
assigning-non-slot:18:8:18:17:Foo.__init__:Assigning to attribute '_bar' not defined in class slots:UNDEFINED
assigning-non-slot:18:8:18:17:Foo.__init__:Assigning to attribute '_bar' not defined in class slots:INFERENCE

0 comments on commit 1baa10e

Please sign in to comment.