summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog8
-rw-r--r--doc/whatsnew/2.4.rst11
-rw-r--r--pylint/checkers/classes.py31
-rw-r--r--pylint/test/functional/slots_checks.py22
-rw-r--r--pylint/test/functional/slots_checks.txt3
5 files changed, 63 insertions, 12 deletions
diff --git a/ChangeLog b/ChangeLog
index 961a707b8..f6dd66bb9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,7 +9,7 @@ Release date: TBA
* Added method arguments to the dot writer for pyreverse.
-Close #2139
+ Close #2139
* Support for linting file from stdin.
@@ -17,6 +17,12 @@ Close #2139
Close #1187
+* Added a new check `class-variable-slots-conflict`
+
+ This check is emitted when ``pylint`` finds a class variable that conflicts with a slot
+ name, which would raise a ``ValueError`` at runtime.
+
+
What's New in Pylint 2.3.0?
===========================
diff --git a/doc/whatsnew/2.4.rst b/doc/whatsnew/2.4.rst
index c232b5418..2245d372c 100644
--- a/doc/whatsnew/2.4.rst
+++ b/doc/whatsnew/2.4.rst
@@ -13,6 +13,17 @@ Summary -- Release highlights
New checkers
============
+* A new check ``class-variable-slots-conflict`` was added.
+
+ This check is emitted when ``pylint`` finds a class variable that conflicts with a slot
+ name, which would raise a ``ValueError`` at runtime.
+
+ For example, the following would raise an error::
+
+ class A:
+ __slots__ = ('first', 'second')
+ first = 1
+
Other Changes
=============
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 2fb152bd1..de255fe6a 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -596,6 +596,11 @@ MSGS = {
"duplicate-bases",
"Used when a class has duplicate bases.",
),
+ "E0242": (
+ "Value %r in slots conflicts with class variable",
+ "class-variable-slots-conflict",
+ "Used when a value in __slots__ conflicts with a class variable, property or method.",
+ ),
"R0202": (
"Consider using a decorator instead of calling classmethod",
"no-classmethod-decorator",
@@ -1050,27 +1055,33 @@ a metaclass class method.",
values = slots.itered()
if values is astroid.Uninferable:
return
-
for elt in values:
try:
- self._check_slots_elt(elt)
+ self._check_slots_elt(elt, node)
except astroid.InferenceError:
continue
- def _check_slots_elt(self, elt):
- for infered in elt.infer():
- if infered is astroid.Uninferable:
+ def _check_slots_elt(self, elt, node):
+ for inferred in elt.infer():
+ if inferred is astroid.Uninferable:
continue
- if not isinstance(infered, astroid.Const) or not isinstance(
- infered.value, str
+ if not isinstance(inferred, astroid.Const) or not isinstance(
+ inferred.value, str
):
self.add_message(
- "invalid-slots-object", args=infered.as_string(), node=elt
+ "invalid-slots-object", args=inferred.as_string(), node=elt
)
continue
- if not infered.value:
+ if not inferred.value:
+ self.add_message(
+ "invalid-slots-object", args=inferred.as_string(), node=elt
+ )
+
+ # Check if we have a conflict with a class variable
+ class_variable = node.locals.get(inferred.value)
+ if class_variable:
self.add_message(
- "invalid-slots-object", args=infered.as_string(), node=elt
+ "class-variable-slots-conflict", args=(inferred.value,), node=elt
)
def leave_functiondef(self, node):
diff --git a/pylint/test/functional/slots_checks.py b/pylint/test/functional/slots_checks.py
index 800a45e2f..64a439f33 100644
--- a/pylint/test/functional/slots_checks.py
+++ b/pylint/test/functional/slots_checks.py
@@ -1,7 +1,7 @@
""" Checks that classes uses valid __slots__ """
# pylint: disable=too-few-public-methods, missing-docstring, no-absolute-import, useless-object-inheritance
-# pylint: disable=using-constant-test, wrong-import-position, no-else-return
+# pylint: disable=using-constant-test, wrong-import-position, no-else-return, line-too-long
from collections import deque
def func():
@@ -83,3 +83,23 @@ class PotentiallyThirdGood(object):
class PotentiallyFourthGood(object):
__slots__ = Good.__slots__
+
+
+class ValueInSlotConflict(object):
+ __slots__ = ('first', 'second', 'third', 'fourth') # [class-variable-slots-conflict, class-variable-slots-conflict, class-variable-slots-conflict]
+ first = None
+
+ @property
+ def third(self):
+ return 42
+
+ def fourth(self):
+ return self.third
+
+
+class Parent(object):
+ first = 42
+
+
+class ChildNotAffectedByValueInSlot(Parent):
+ __slots__ = ('first', )
diff --git a/pylint/test/functional/slots_checks.txt b/pylint/test/functional/slots_checks.txt
index 90a6a80a8..b3d71afcc 100644
--- a/pylint/test/functional/slots_checks.txt
+++ b/pylint/test/functional/slots_checks.txt
@@ -6,3 +6,6 @@ invalid-slots-object:49:FifthBad:"Invalid object ""''"" in __slots__, must conta
single-string-used-for-slots:51:SixthBad:Class __slots__ should be a non-string iterable
single-string-used-for-slots:54:SeventhBad:Class __slots__ should be a non-string iterable
single-string-used-for-slots:57:EighthBad:Class __slots__ should be a non-string iterable
+class-variable-slots-conflict:89:ValueInSlotConflict:Value 'first' in slots conflicts with class variable
+class-variable-slots-conflict:89:ValueInSlotConflict:Value 'fourth' in slots conflicts with class variable
+class-variable-slots-conflict:89:ValueInSlotConflict:Value 'third' in slots conflicts with class variable